### Scrapy-redis 分布式
#### Python 邮件监控
##### SMTP 简介
+ **SMTP** (Simple Mail Transfer Protocol : 简单邮件传输协议) : 一组用于由原地址到目的地址传送邮件的规则,由它来控制新建的中转方式
+ python 的 smtplib 提供了一种很方便的途径发送电子邮件,对 smtp 进行了简单的封装
```python
import smtplib
smtpObj = smtplib.SMTP([host [, port [, local_hostname]]])
```
+ 参数说明
1. **host** : SMTP 服务器主机,可以指定主机的 ip 地址或者域名 (可选参数)
2. **port** : 如果提供了 host 需要指定 SMTP 服务使用的端口号, 一般端口号为 25
3. **local_hostname** : 如果 SMTP 在本机上,可以指定服务器地址为 localhost
+ Python SMTP 使用 **sendmail** 方法发送邮件,语法格式:
```python
SMTP.sendmail(from_addr, to_addrs, msg [, mail_options, rcpt_options])
```
+ 参数说明
1. **from_addr** : 邮件发送者地址
2. **to_addrs** : 字符串列表,邮件发送地址
3. **msg** : 发送消息,字符串 (表示邮件)
+ msg 格式 为 smtp 定义固定格式
1. 标题
2. 发信人
3. 收件人
4. 邮件内容
5. 附件
##### 邮件监控五个步骤
+ 五个步骤
```python
# 导入邮件包
import smtplib
# 创建邮件对象
smtpobj = smtplib.SMTP()
# 连接服务器
smtpobj.connect()
# 登录操作
smtpobj.login()
# 发送邮件
smtpobj.sendmail()
# 退出操作
smtpobj.quit()
```
1. 连接服务器 : `smtp.connect(self, host='localhost', port=0, source_address=None)`
2. 登录操作 : `smtplib.login(self, user, password, *, initial_response_ok=True)`
3. 发送邮件 : `smtplib.sendmail(self, from_addr, to_addrs, msg, mail_options=(), rcpt_options=())`
#### Redis 安装
1. 安装步骤
1. 将下载好的 redis 安装包解压到指定目录
2. 将 redis 安装目录配置到环境变量的 path 中
3. 在 cmd 中运行命令 `redis-server` ,查看是否可以启动 redis 数据库
2. redis 重要文件
+ **redis.windows.conf** : redis 的配置文件
+ **redis-cli.exe** : 客户端命令程序
+ **redis-server.exe** : 服务端命令程序
+ 默认端口 : **6379**
3. 一个 reids 配置文件就是一个 redis 数据库
4. 开启多个 redis 服务
1. 复制配置文件,名称后添加需要开启的端口号
2. 打开配置文文件更改端口号
```
# Accept connections on the specified port, default is 6379 (IANA #815344).
# If port 0 is specified Redis will not listen on a TCP socket.
port 6379
```
3. 开启多个命令端口,使用 `redis-server [配置文件]` 启动 redis 服务
4. 链接 redis 数据库 `redis-cli -p 端口号`
5. redis 优点
1. redis 有丰富的数据结构
2. redis 身为内存化数据库,可以持久化保存数据
6. [redis 文档](https://note.youdao.com/ynoteshare1/index.html?id=b25d0db9b99d173ad016f8d84184bd99&type=note)
#### Scrapy-Redis 分布式
##### 分布式爬虫简介
1. 使用分布式的原因
+ 分布式爬虫就是多台计算机上都安装爬虫程序,重点是**联合采集** (例如: 三台服务器上三个爬虫同时爬取一个网页,需要一个 **状态管理器**集中分配,**去重**已爬过的url)
+ **状态管理器** : 也是一个服务,需要部署在某一台服务器上,通过状态管理器集中分配需要抓取的URL 以及去重
+ 分布式爬虫的优点
1. 充分利用多机器的带宽进行加速爬取,一台服务器上带宽有限
2. 充分利用多机器的 IP 加速爬取的速度,一台服务器如果爬取过快容易被封 IP
+ 分布式爬虫 : 多台服务器**有序**的爬取任务为队列的 URL
2. Scrapy 和 Scrapy-Redis 的区别
1. Scrapy 是一个通用爬虫框架,不支持分布式.分布式爬取需要 Scrapy-redis.
2. Scrapy-redis **提供以下四种组件 (components) **,使用分布式爬取时需要对四种组件进行修改
1. Scheduler
2. Duplication Filter
3. Item Pipeline
4. Base Spider
3. 安装 Scrapy-redis : `pip intsall scrapy-redis`
3. Scrapy-redis 架构
1. **Scheduler 调度器** : 负责对新的 request 进行入队操作 (加入 Scrapy queue), 取出下一个要爬取的 request (从 Scrapy queue 中取出)等操作
+ Scrapy 改造了 python 本来的 **collection.deque (双向队列) ** 形成自己的 **Scrapy queue**
+ Scrapy 多个 spider 不能共享待爬取队列 Scrapy queue, 即 Scrapy 本身不支持爬虫分布式,Scrapy-redis 解决就是把 Scrapy queue 换为 **redis 数据库** (也是指 redis 队列),在同一个 redis-server 存放要爬取的 request,便可以让多个 spider 在一个 redis 数据库读取
+ Scrapy-redis 把待爬取的队列按照优先级建立了一个字典结构,然后根据优先级,决定队列顺序,出列时优先级较小的优先出列.
```
{
优先级0 : 队列0,
优先级1 : 队列1,
优先级2 : 队列2,
...
}
```
2. **Duplication Filter (DupeFilter)** : 负责 Scrapy-redis 的去重功能,通过redis 的 **set 不重复** 的特性,实现去重.
+ **Scrapy 去重** : 用集合实现 request 去重,Scrapy 将已将发送的 request 指纹放入一个集合中,把下一个 request 指纹拿到集合中对比,如果指纹存在于集合,说明 request 发送过了,没有则继续操作
```python
def request_seen(self, request):
# self.request_fingerprints(request) 一个指纹集合
fp = self.request_fingerprints(request)
# 核心操作
if fp in self.fingerprints:
return True
self.fingerprints.add(fp)
if self.file:
self.file.write(fp + os.linesep)
```
+ **Scrapy-redis 去重** : 调度器从引擎接受 **request** , 将 request 的指纹存入 redis 的 set 检查是否出现了重复,并将不重复的 request push 写入 redis 的 request queue
+ **Scrapy-redis 请求过程** : 引擎请求 request (Spider 发送出的) 时,调度器从 redis 的 request queue 队列里根据优先级 pop 出一个 request 给引擎,引擎将此 request 发送给 spider 处理
3. Item Pipeline
+ **Scrapy-redis 存储过程** : 引擎将 (spider 返回的) 爬取到的 Item 发送给 spider 处理; Item Pipeline 将爬取到的 Item 存入 redis 的 Items queue
+ 修改过后的 Item Pipeline 可以很方便的 根据 key 从 item queue 提取 item,从而实现 **items processes 集群**
4. Base Spider : 重写后的 RedisSpider 继承了 **Spider** 和 **RedisMixin (从 redis 中读取 url) ** 两个类
+ 当生成一个 Spider 继承 RedisSpider 时, **调用 setup_redis 函数**,这个函数**连接 redis 数据库**,然后会**设置 signals** (信号):
1. 当 **spider 空闲**的时候的 signals : 会调用 spider_idle 函数,这个函数调用 schedule_next_request 函数,保证 spider 一直存活,并且抛出 **DontCloseSpider 异常**
2. **抓取到 item** 时的 signals : 调用 item-scraped 函数,这个函数会调用 schedule_next_request 函数,获取下一个 request
4. **总结**
1. 重写 **schedule (调度器)** 和 **spider 类** : 实现**调度**、**spider 启动**、**redis 的交互**
2. 实现新的 **dupefilter 类** 和 **queue 类** : 达到 **判重**、**调度容器**、**redis 交互**,由于每个主机上的爬虫都会访问同一个 redis 数据库,所以调度和判重都统一管理,达到分布式的目的
3. spider 初始化时会同时初始化一个对应的 scheduler 对象, 这个调度器对象通过读取 settings,配置好自己的**调度容器 queue** 和**判重工具 dupefilter**
4. **Scrapy-redis 开始爬取** : 每当一个 spider 产出一个 request 的时候,Scrapy 引擎会把这个 request 递交给这个 spider 对应的 scheduler 对象进行调度, scheduler 对象通过访问 redis 对 request 进行判重,如果不重复就把其添加到 redis 中的调度器队列里. 当调度条件满足时, scheduler 对象就从 redis 的调度器队列取出一个 request 发送给 spider ,让它爬取
5. **Scrapy-redis 爬取结束之后 ** : spider 爬取了所有暂时可用的 url 之后, scheduler 发现这个 spider 对应的 redis 的调度器队列空了,于是触发信号 **spider_idle**, spider 收到这个信号之后,直接连接 redis 读取 start_urls 池,拿到新一批 url, 重复 工作
#### 增量爬虫
##### 增量爬虫
+ 增量爬虫 : 通过爬虫程序监控某网站的数据更新情况,以便可以爬取到该网站更新出的新数据
+ 增量爬取步骤 (核心 : **去重**, 可以在1、2步随机一个去重)
1. 在发送请求前判断该 URL 是否爬取过 (去重 : 适合不断有新网页出现的网站: 小说网、每日新闻)
2. 解析内容后判断这部分内容是否爬取过 (去重 : 适合页面内容会更新的网站)
3. 写入存储介质时判断内容是否在介质中
+ **去重方法** :
+ 将爬取过程中产生的 url 进行存储,存储在 redis 的 set 中,当下次进行数据爬取时,首先判断即将发起请求的 url 是否存储在 url 的 set 中存在,如果不存在则请求
+ 对爬取到的网页进行唯一标识限定,然后将该唯一标识限定存储至 redis 的 set 中,当下次爬取到网页数据时,在持久化存储时,首先判断该数据唯一标识符在 redis 中的 set 中是否存在,进一步决定是否进行持久化存储