1.概念:分布式爬虫
由于需要爬取的数据量大,任务多,一台机器效率太低,需要多台机器共同协作处理。分布式爬虫将多台主机组合起来, 共同完成一个爬取任务,快速高效地提高爬取效率。
分布式爬虫可以分为若干个分布式层级,不同的应用可能由其中部分层级构成。
大型分布式爬虫主要分为以下3个层级:分布式数据中心、分布式抓取服务器及分布式爬虫程序。整个爬虫系统由全球多个分布式数据中心共同组成,每个数据中心又由多台高速网络连接的抓取服务器构成,而每台服务器又可以部署多个爬虫程序。通过多层级的分布式爬虫体系,才可能保证抓取数据的及时性和全面性。
作为一个分布式爬虫,是需要有一个Master端(核心服务器)的,在Master端,会搭建一个数据库,用来存储start_urls、request、items。Master的职责是负责url指纹判重,Request的分配,以及数据的存储(一般在Master端会安装一个mongodb用来存储items)。出了Master之外,还有一个角色就是slaver(爬虫程序执行端),它主要负责执行爬虫程序爬取数据,并将爬取过程中新的Request提交到Master的数据库中。
如上图,假设我们有四台电脑:A, B, C, D ,其中任意一台电脑都可以作为 Master端 或 Slaver端。整个流程是:
2.背景
Scrapy 是一个通用的爬虫框架,但是不支持分布式。有能人改变了scrapy的队列调度,将起始的网址从start_urls里分离出来,改为从redis数据库读取,多个客户端可以同时读取同一个redis,从而实现了分布式的爬虫。
Redis,非关系型数据库,Key-Value形式存储,结构灵活,是内存中的数据结构存储系统,处理速度快,性能好;并且能提供队列、集合等多种存储结构,方便队列维护。
Scrapy-redis是为了更方便地实现Scrapy分布式爬取,而提供了一些以redis为基础的组件(仅有组件)。通过Scrapy-redis可以快速地实现简单分布式爬虫程序,该组件本质上提供了三大功能:
3.环境配置
4.scrapy-redis架构
scrapy-redis在scrapy的架构上增加了redis,基于redis的特性拓展了如下四种组件:Scheduler,Duplication Filter,Item Pipeline,Base Spider。
scrapy改造了python本来的collection.deque(双向队列)形成了自己的Scrapy queue,但是Scrapy多个spider不能共享待爬取队列Scrapy queue,即Scrapy本身不支持爬虫分布式,scrapy-redis 的解决是把这个Scrapy queue换成redis数据库(也是指redis队列),从同一个redis-server存放要爬取的request,便能让多个spider去同一个数据库里读取。
Scrapy中跟“待爬队列”直接相关的就是调度器Scheduler,它负责对新的request进行入列操作(加入Scrapy queue),取出下一个要爬取的request(从Scrapy queue中取出)等操作。它把待爬队列按照优先级建立了一个字典结构,然后根据request中的优先级,来决定该入哪个队列,出列时则按优先级较小的优先出列。为了管理这个比较高级的队列字典,Scheduler需要提供一系列的方法。但是原来的Scheduler已经无法使用,所以使用Scrapy-redis的scheduler组件。
Scrapy中用集合set( )实现这个request去重功能,Scrapy中把已经发送的request指纹放入到一个集合中,把下一个request的指纹拿到集合中比对,如果该指纹存在于集合中,说明这个request发送过了,如果没有则继续操作。
scrapy-redis中去重是由Duplication Filter组件来实现的,它通过redis的set不重复的特性,巧妙的实现了DuplicationFilter去重。scrapy-redis调度器从引擎接受request,将request的指纹存入redis的set检查是否重复,并将不重复的request push写入redis的 request queue。引擎请求request(Spider发出的)时,调度器从redis的request queue队列里根据优先级pop 出⼀个request 返回给引擎,引擎将此request发给spider处理。
引擎将(Spider返回的)爬取到的Item给Item Pipeline,scrapy-redis 的Item Pipeline将爬取到的 Item 存入redis的 items queue。修改过Item Pipeline可以很方便的根据 key 从 items queue 提取item,从而实现 items processes集群。
不在使用scrapy原有的Spider类,重写的RedisSpider继承了Spider和RedisMixin这两个类,RedisMixin是用来从redis读取url的类。
当我们生成一个Spider继承RedisSpider时,调用setup_redis函数,这个函数会去连接redis数据库,然后会设置signals(信号):一个是当spider空闲时候的signal,会调用spider_idle函数,这个函数调用schedule_next_request函数,保证spider是一直活着的状态,并且抛出DontCloseSpider异常。一个是当抓到一个item时的signal,会调用item_scraped函数,这个函数会调用schedule_next_request函数,获取下一个request。
这套组件通过重写scheduler和spider类,实现了调度、spider启动和redis的交互;
实现新的dupefilter和queue类,达到了判重和调度容器和redis的交互,因为每个主机上的爬虫进程都访问同一个redis数据库,所以调度和判重都统一进行统一管理,达到了分布式爬虫的目的;当spider被初始化时,同时会初始化一个对应的scheduler对象,这个调度器对象通过读取settings,配置好自己的调度容器queue和判重工具dupefilter;
每当一个spider产出一个request的时候,scrapy引擎会把这个reuqest递交给这个spider对应的scheduler对象进行调度,scheduler对象通过访问redis对request进行判重,如果不重复就把他添加进redis中的调度器队列里。当调度条件满足时,scheduler对象就从redis的调度器队列中取出一个request发送给spider,让他爬取;
当spider爬取的所有暂时可用url之后,scheduler发现这个spider对应的redis的调度器队列空了,于是触发信号spider_idle,spider收到这个信号之后,直接连接redis读取strart_url池,拿去新的一批url入口,然后再次重复上边的工作。
Slaver端从Master端拿任务(Request/url/ID)进行数据抓取,在抓取数据的同时也生成新任务,并将任务抛给Master。Master端只有一个Redis数据库,负责对Slaver提交的任务进行去重、加入待爬队列。
优点:scrapy-redis默认使用的就是这种策略,我们实现起来很简单,因为任务调度等工作scrapy-redis都已经帮我们做好了,我们只需要继承RedisSpider、指定redis_key就行了。
缺点:scrapy-redis调度的任务是Request对象,里面信息量比较大(不仅包含url,还有callback函数、headers等信息),导致的结果就是会降低爬虫速度、而且会占用Redis大量的存储空间。当然我们可以重写方法实现调度url。
5.实战演练
tips:redis默认是不能被远程连接的,需要修改文件,并重启服务器。
#注释bind
#bind 127.0.0.1
1.master的爬虫运行时会把提取到的url封装成request放到redis中的数据库:“blogjobbole:requests”,并且从该数据库中提取request后下载网页,再把网页的内容存放到redis的另一个数据库中“blogjobbole:items”
2.slave从master的redis中取出待抓取的request,下载完网页之后就把网页的内容发送回master的redis
3.重复上面的1和2,直到master的redis中的“blogjobbole:requests”数据库为空,再把master的redis中的“blogjobbole:items”数据库写入到mongodb中
4.master里的reids还有一个数据“blogjobbole:dupefilter”是用来存储抓取过的url的指纹(使用哈希函数将url运算后的结果),是防止重复抓取的
要继承redisspider
from scrapy_redis.spiders import RedisSpider
class BlogjobboleSpider(RedisSpider):
name = 'blogjobbole'
allowed_domains = ['blog.jobbole.com']
redis_key = 'blogjobbole:start_urls'#原来spider文件中的start_urls没有了
master(Ubuntu)中setting.py
# 添加配置
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
SCHEDULER_PERSIST = True
REDIS_HOST = "localhost" #主机中为localhost
REDIS_PORT = 6379
# 开启pipeline
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 100,
}
slave(win 7 )中setting.py
# 添加配置
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
SCHEDULER_PERSIST = True
REDIS_HOST = "192.168.1.20 " #slave连接到master主机中,ip地址192.168.1.20
REDIS_PORT = 6379
# 开启pipeline
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 100,
}
1.运行两台主机中的爬虫文件(启动爬虫没有顺序限制)
2.在master(Ubuntu)的终端中输入
redis-cli lpush blogjobbole:start_urls http://blog.jobbole.com/all-posts/
随后,两台主机上就开始爬取数据了,爬取的数据保存在master(Ubuntu)中的redis,需要随后读取出来保存到mongdb。
【注意点】:每次重新爬取需要将redis清空。当爬虫停止后,redis数据库中只剩下dupefilter 和 items。dupefilter 中保存的是已经爬取过的url的md5值。如果未清空,爬虫会过滤掉这些网址,不进行该网址的爬取。因此,如果需要重新爬取,需要将redis清空。
附上项目地址:https://github.com/Damaomaomao/jobbole_redis
参考来源:
网络爬虫 | 你知道分布式爬虫是如何工作的吗?
scrapy-redis分布式爬虫的搭建过程(理论篇)
scrapy-redis分布式爬虫框架详解