分布式爬虫的演习。
分布式爬虫问题其实也就是多台机器多个 spider 对 多个 url 的同时处理问题,怎样 schedule 这些 url,怎样汇总 spider 抓取的数据。最简单粗暴的方法就是将 url 进行分片,交给不同机器,最后对不同机器抓取的数据进行汇总。然而这样每个 spider 只能对自己处理的 url 去重,没办法全局的去重,另外性能也很难控制,可能有某台机器很早就跑完了,而别的机器还要跑很久。另一种思路就是把 url 存在某个地方,共享给所有的机器,总的调度器来分配请求,判断 spider 有没有闲置,闲置了就继续给它任务,直到所有的 url 都爬完,这种方法解决了去重问题(下面会具体讲到),也能提高性能,scrapy-redis 就实现了这样一个完整框架,总的来说,这更适合广度优先的爬取。
Scrapyd
Scrapy 并没有提供内置的分布式抓取功能,不过有很多方法可以帮你实现。
如果你有很多个spider,最简单的方式就是启动多个 Scrapyd 实例,然后将spider分布到各个机器上面。
如果你想多个机器运行同一个spider,可以将url分片后交给每个机器上面的spider。比如你把URL分成3份
http://somedomain.com/urls-to-crawl/spider1/part1.list http://somedomain.com/urls-to-crawl/spider1/part2.list http://somedomain.com/urls-to-crawl/spider1/part3.list
然后运行3个 Scrapyd 实例,分别启动它们,并传递part参数
curl http://scrapy1.mycompany.com:6800/schedule.json -d project=myproject -d spider=spider1 -d part=1 curl http://scrapy2.mycompany.com:6800/schedule.json -d project=myproject -d spider=spider1 -d part=2 curl http://scrapy3.mycompany.com:6800/schedule.json -d project=myproject -d spider=spider1
Crawlera
这个,花钱就可以轻易解决~ 直达
Scrapy-redis
Redis 是高性能的 key-value 数据库。我们知道 MongoDB 将数据保存在了硬盘里,而 Redis 的神奇之处在于它将数据保存在了内存中,因此带来了更高的性能。
分布式原理
scrapy-redis实现分布式,其实从原理上来说很简单,这里为描述方便,我们把自己的核心服务器称为 master,而把用于跑爬虫程序的机器称为 slave。
回顾 scrapy 框架,我们首先给定一些start_urls,spider 最先访问 start_urls 里面的 url,再根据我们的 parse 函数,对里面的元素、或者是其他的二级、三级页面进行抓取。而要实现分布式,只需要在这个starts_urls里面做文章就行了。进一步描述如下:
-
master 产生 starts_urls,url 会被封装成 request 放到 redis 中的 spider:requests,总的 scheduler 会从这里分配 request,当这里的 request 分配完后,会继续分配 start_urls 里的 url。
-
slave 从 master 的 redis 中取出待抓取的 request,下载完网页之后就把网页的内容发送回 master 的 redis,key 是 spider:items。scrapy 可以通过 settings 来让 spider 爬取结束之后不自动关闭,而是不断的去询问队列里有没有新的 url,如果有新的 url,那么继续获取 url 并进行爬取,所以这一过程将不断循环。
-
master 里的 reids 还有一个 key 是 “spider:dupefilter” 用来存储抓取过的 url 的 fingerprint(使用哈希函数将url运算后的结果),防止重复抓取,只要 redis 不清空,就可以进行断点续爬。
对于已有的 scrapy 程序,对其扩展成分布式程序还是比较容易的。总的来说就是以下几步:
- 找一台高性能服务器,用于 redis 队列的维护以及数据的存储。
- 扩展 scrapy 程序,让其通过服务器的 redis 来获取 start_urls,并改写 pipeline 里数据存储部分,把存储地址改为服务器地址。
- 在服务器上写一些生成url的脚本,并定期执行。
关于 scheduler 到底是怎么进行调度的,需要看源码进行分析。
源码分析
可能上面的描述还是不够清楚,干脆看一下源码吧,scrapy-redis 主要要一下几个文件。
零件分析
-
connection.py
根据 settings 里的配置实例化 redis 连接,被 dupefilter 和 scheduler 调用。 -
dupefilter.py
对 request 进行去重,使用了 redis 的 set。 -
queue.py
三种 queue, SpiderQueue(FIFO), SpiderPriorityQueue,以及 SpiderStack(LIFI)。默认使用的是第二种。 -
pipelines.py
分布式处理,将 item 存储在 redis 中。 -
scheduler.py
取代 scrapy 自带的 scheduler,实现分布式调度,数据结构来自 queue。 -
spider.py
定义 RedisSpider.py, 继承了 RedisMixin 和 CrawlSpider。
由上可知,scrapy-redis 实现的 爬虫分布式 和 item处理分布式 就是由模块 scheduler 和模块 pipelines 实现。上述其它模块作为为二者辅助的功能模块。
调度过程
初始化
spider 被初始化时,同时会初始化一个对应的 scheduler 对象,这个调度器对象通过读取 settings,配置好自己的调度容器 queue 和判重工具dupefilter。
判重 & 进入调度池
每当一个 spider 产出一个 request 的时候,scrapy 内核会把这个 request 递交给这个 spider 对应的 scheduler 对象进行调度,scheduler 对象通过访问 redis 对 request 进行判重,如果不重复就把他添加进 redis 中的调度池。
调度
当调度条件满足时,scheduler 对象就从 redis 的调度池中取出一个 request 发送给spider,让 spider 爬取,若爬取过程中返回更多的url,那么继续进行直至所有的 request 完成。在这个过程中通过 connect signals.spider_idle 信号对 crawler 状态的监视,scheduler 对象发现 这个 spider 爬取了所有暂时可用 url,对应的 redis 的调度池空了,于是触发信号 spider_idle,spider收到这个信号之后,直接连接 redis 读取 strart_url池,拿去新的一批 url,返回新的 make_requests_from_url(url) 给引擎,进而交给调度器调度。
熟悉了原理其实可以自己来写 scheduler,自己定义调度优先级和顺序,