分布式爬虫要点
image.pngimage.png爬虫 A、B、C 分别放在三台服务器上,还需要一个 “状态管理器” 来对 URL 进行集中管理、去重等操作,它可以单独部署在一个服务器上面,也可以部署在 A、B、C 任何一台服务器上面
Scrapy 架构图回顾一下 Scrapy 架构图
在一台机器上运行的爬虫中,SPIDER yield 出来的任何一条 REQUEST 都需要进入 SCHEDULER,然后 ENGINE 从 SCHEDULER 取 REQUEST 发送给 DOWNLOADER;还有关于 Scrapy 的去重,是放在内存中的一个 set 里面的。
image.png如果想将 Scrapy 改造成分布式,就会有两个问题必须要解决
①request 队列集中管理
② 去重集中管理
如何解决:
①在 Scrapy 中,SCHEDULER 实际上是放在一个 QUEUE(队列)中的,而这个 QUEUE 实际上就是放在内存中的,如果是分布式爬虫,其他服务器是拿不到当前服务器内存中的内容的,所以 Scrapy 就没法支持分布式,要想办法将这地方的 QUEUE 管理做成一种集中式的管理
②去重也是要做集中管理的,Scrapy 当中有一个去重的扩展,去重原理就是通过内存中的 set 来实现的,所以也没法做成分布式
所以关注的重点就是,将 “去重的 set” 和 “REQUEST 队列 SCHEDULER” 这两个放到第三方的组件来做,这个第三方组件就是分布式爬虫的主角 Redis
Redis 是基于内存的数据库,事实上也可以用 关系型数据库来做分布式,但是效率会很低
redis基础知识
Redis 菜鸟教程:http://www.runoob.com/redis/redis-tutorial.html
Redis 中文文档:http://www.redis.cn/commands.html
scrapy-redis编写分布式爬虫代码
GitHub 地址:https://github.com/rmax/scrapy-redis
需要安装库
pip install redis
Scrapy-Redis 的使用
settings.py 中需要配置
# 调度器(必须)
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 去重(必须)
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 用于将 ITEM 序列化并发送到 Redis(可选)
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 300
}
所有的编写的 Spider 不再继承
scrapy.Spider
,而要继承RedisSpider
from scrapy_redis.spiders import RedisSpider
class MySpider(RedisSpider):
name = 'myspider'
def parse(self, response):
# do stuff
pass
启动 Spider 命令不再是
scrapy crawl myspider
,而是改成scrapy runspider myspider.py
,注意要写 文件名 而不再是 爬虫名
scrapy runspider myspider.py
现在启动爬虫后,所有的 REQUEST 就不再是本地 SCHEDULER 来完成的,而是在 settings.py 中新增的 Scrapy-Redis 的 SCHEDULER
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
启动 Spider 以后,必须要向队列里面放入一个初始化 URL,现在所有 URL 都是放在 Redis 当中的,所以初始化的时候,必须要向 Redis 中 push 一个起始 URL,这样才能够进行爬取
redis-cli lpush myspider:start_urls http://google.com
新建 Scrapy 项目
ScrapyRedisTest
scrapy startproject ScrapyRedisTest
image.png
将 Scrapy-Redis 源码 clone 下来
git clone https://github.com/rmax/scrapy-redis.git
image.png
image.png将 clone 下来的 Scrapy-Redis 源码 复制到 新建的
ScrapyRedisTest
项目中
image.png image.png image.png image.png image.png image.png将 jobbole spider 改造成 基于 Scrapy-Redis 的分布式爬虫
关于
RedisMixin
类中获取下一个 URL 的方法next_requests
Scrapy 中获取
next_requests
的时候是通过 Scrapy 的 SCHEDULER 来完成的,SCHEDULER 是维持一个 QUEUE,是放在内存当中的;现在 Scrapy-Redis 将这个 QUEUE 放到了 Redis 当中,使用 list 或 set 等来完成
可以将原来的 jobbole spider 中 parse 方法逻辑完全拷贝过来
# ScrapyRedisTest/spiders/jobbole.py
from urllib.parse import urljoin
import scrapy
from scrapy_redis.spiders import RedisSpider
class JobboleSpider(RedisSpider):
name = 'jobbole'
allowed_domains = ['jobbole.com']
redis_key = 'jobbole:start_urls'
def parse(self, response):
"""
1. 提取文章列表页中所有文章详情页链接,并交给 parse_detail 方法进行解析
2. 提取下一页链接,并交给 Scrapy 进行下载
Args:
response: 响应信息
Yields:
1. 文章详情页链接,交给 parse_detail 解析
2. 下一页链接,交给 Scrapy 下载
"""
post_nodes = response.xpath('//div[@id="archive"]')
for post_node in post_nodes:
post_url = post_node.xpath('.//div[@class="post-meta"]//a[@class="archive-title"]/@href').extract_first('')
front_img_url = post_node.xpath('.//div[@class="post-thumb"]//img/@src').extract_first('')
yield scrapy.Request(url=urljoin(response.url, post_url), callback=self.parse_detail,
meta={'front_img_url': front_img_url})
next_url = response.xpath('//a[@class="next page-numbers"]/@href').extract_first()
if next_url:
yield scrapy.Request(url=next_url, callback=self.parse)
def parse_detail(self, response):
pass
因为这里只做演示,所以删除了很多逻辑,只有一个简单的 parse 方法
接下来就是 settings.py 中的配置
# ScrapyRedisTest/settings.py
ROBOTSTXT_OBEY = False
# Scrapy-Redis 相关配置
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 300
}
image.png编写启动爬虫主文件 main.py
image.png运行爬虫
image.png向 Redis 里面 push 一个起始 URL
image.png发现 spider 开始进行爬取
image.png image.png为了方便查看 Scrapy-Redis 爬虫的流程,可以在
jobbole.py
和 Scrapy-Redis 源码scheduler.py
中分别打一个断点,进行调试检测
image.png image.pngDeBug 运行 spider,第一次停到断点这里实际上没有起始的 URL,此时向 Redis 中 push 第一个 URL,然后 f9 跳过这个请求,程序又会卡在这个断点
此时查看 Redis,新增了一个
jobbole:requests
键,值为ZSET
类型,为什么使用可排序的ZSET
而不是SET
或者LIST
呢?因为默认情况下jobbole:requests
是有优先级的,我们在程序中是可以设定优先级的,为了满足这个功能,必须使用可排序的ZSET
image.png实际上,存到 Redis 里面的这个 request 是一个 Python 类,将这个类放到了 Redis 中,但是 Redis 中是不可能放 Python 类的,实际上这里存入 Redis 中是经过 Pickle 序列化的,将类序列化成字符串,存入 Redis,后续拿出来使用的时候可以反序列化成类
image.png image.png多按几次 f9,再次查看 Redis,多了一个
jobbole:dupefilter
键,这个键的值类型为SET
,因为是用于去重的,所以就没必要使用ZSET
了
scrapy-redis源码剖析-dupefilter.py、picklecompat.py
image.png- dupefilter.py
image.png image.png image.pngdupefilter.py 是用来去重的文件,Scrapy-Redis 的 dupefilter.py 源码几乎同 Scrapy 的 dupefilter.py 源码一模一样
image.png image.png image.png image.pngScrapy-Redis 在初始化的时候就生成了一个 server,这个 server 就连接到了 Redis,调用了
get_redis_from_settings
方法,这个方法是在 connection.py 中的
- picklecompat.py
image.pngpicklecompat.py 实际上就是调用了 Python 的 pickle 库
scrapy-redis源码剖析- pipelines.py、 queue.py
image.png- pipelines.py
image.pngpipelines.py 文件中会将 ITEM 放到 Redis 当中
image.png image.png image.png image.pngpipelines.py 文件中
RedisPipeline
类的初始化方法同样会初始化一个server
,这个server
同样会调用 connection.py 中的get_redis_from_settings
方法
事实上,将数据存入 Redis 的过程的类
RedisPipeline
不是必须要配置到settings.py
中的,实际上也可以在爬虫爬取完成之后直接存储到本地数据库中,设置保存到 Redis 当中的好处是,可以完成数据共享,将数据放到 Redis 当中就可以多起几个进程,甚至还可以写一个脚本(不是爬虫),直接从 Redis 中读取数据并将其存储到关系型数据库当中
- queue.py
image.pngqueue.py 是供给 schedluer 调度来使用的,共有 3 个 Queue
image.pngFifoQueue 意思是:first in first out 的简写,先进先出,也就是有序队列
image.pngLifoQueue 意思是:last in first out 的简写,后进先出,类似于
栈
image.png image.pngPriorityQueue:默认使用的就是这个 Queue
网友评论