思路
虽然scrapy支持多线程,但是单机scrapy也是有性能瓶颈的。使用scrapy-redis可以将scrapy改造成分布式的爬虫架构。
改造的原理是什么?
对于原版的scrapy,scrapy-redis修改了四个部分:调度器(Scheduler)、去重(Dupefilter)、管道(Pipeline)、抓取器(Spider)。其核心是使用了redis代替了原本的queue,因为redis可以作为一个消息队列,这样多个爬虫实例就可以通过共享消息队列来实现分布式了。
要实现分布式主要要解决的问题无非就是几个:
- 任务分发,让每个实例知道自己要做什么。
- 共享消息队列,不能出现重复爬取的情况。
这里Scheduler就是用来分发任务的;如果配置了去重,Dupefilter会负责维护一个集合,包含抓取过的url,避免重复抓取;Pipeline可以是自己写的,也可以使用RedisPipeline将结果存储在redis中;最后Spider中需要实现具体爬虫功能的部分,也是写代码的地方。
如果一切顺利,你可以看到Redis中会出现三个序列:
![](https://img.haomeiwen.com/i69520/caef06abdb0d7713.jpg)
Redis中的key都是按照spidername:word
的形式来命名的,其中Dupefiler
用来去重,需要合适的配置项。Requests
包含了所有爬取的序列,是所有各个爬虫节点的任务来源;同时所有在爬取过程中yield出的新request都会被放入这个序列。最后如果你在配置项中使用了RedisPipeline,Spider返回的结果会被存储在Items
中。
除了这三个序列,有时候会有一个名为start_urls
序列。如果你的爬虫只写了处理一个URL时的代码,但是没有其实URL,也就是说爬虫不知道从哪个url开始入手(有了第一个URL之后就会像蛛网一样衍生出更多的URL)。这时候你会需要用到redis-cli
(redis的客户端程序)来手动向redis中添加一条url:
redis-cli -h redis_addr -p redis_port -n redis_db# lpush spidername:start_urls CustomURL
这个项目里我把start_urls直接写在代码里了。
怎么部署这个分布式爬虫?
scrapy本身支持并发和异步IO,通过修改其配置项CONCURRENT_REQUESTS
来实现,默认为16。因为GIL的存在,如果配置合理,此时的爬虫会占满你一个CPU核心;通过手动执行N个爬虫实例(scrapy crawl comicCrawler
),N一般是你的CPU核心数,你可以充分发挥出一台机器的性能。同样,在其他机器上,你可以执行相同的操作,所有的爬虫实例都共享一个redis数据库。通过这种多进程+多线程的模式,理论上可以最大限度地发挥机器的能力。
具体怎么做?
这里只包含了从scrapy转scrapy-redis需要修改的地方。
修改Setting.py
在官网上提供了一个scrapy-redis的完整配置文件,可以按需修改,一般来说,有几个是必须的,你需要在原本的Setting.py的基础上,增加:
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
SCHEDULER_PERSIST = True
REDIS_HOST = '34.229.250.31'
REDIS_PORT = 6379
REDIS_PARAMS = {
'db': 11
}
LOG_LEVEL = 'DEBUG'
几个比较重要参数是:
- SCHEDULER`是核心部分,scrapy-redis重写了调度器用于管理分布式架构。
-
DUPEFILTER_CLASS
用用于去重。 -
SCHEDULER_PERSIST
允许配置是否可以暂停\继续爬虫,互有利弊。 -
SCHEDULER_QUEUE_CLASS
是队列的类型,有三种:PriorityQueue
(默认),FifoQueue
和LifoQueue
。 -
REDIS
系列:Redis有几种配置方法,可以使用REDIS_HOST
+REDIS_PORT
组合,也可使用REDIS_URL
,也可以通过REDIS_PARAMS
字典对具体的某个参数做调整,比如说,例子里,我选择使用redis数据库中的第11个实例,避免与其他爬虫冲突。 - 其他的一般保持默认即可。
除了新增的几个配置项,如果你选择将最终的结果存入redis,你需要向ITEM_PIPELINES
中添加scrapy_redis.pipelines.RedisPipeline
管道。
ITEM_PIPELINES = {
'comicspider.pipelines.ComicspiderPipeline': 300,
'scrapy_redis.pipelines.RedisPipeline': 400
}
修改spider文件
所有涉及到的spider文件都需要修改,scarpy的spider类一般继承于scrapy.Spider
,这里要调整为scrapy_redis.spiders
。然后,没了。
这里没了
是我这个例子中,不需要修改其他代码了。但是每个人可能写的spider的结构不一样,因此调整起来可能还有一点出入。
比如说:
- 在我的例子中,我没有使用scrapy的
Rules
部件,因此在多页面爬取的时候,我需要为每一类请求都指定好合适的callback。你可以通过配置好Rules
,它可以通过匹配请求的URL,然后给每一类URL绑定相应的回调函数。 - 如果你没有在代码中配置
start_urls
,你需要指定一个redis的key值用来之后向其中推送请求,redis_key='comicCrawler:start_urls'
。
代码和效果
代码我放在GitHub上了,我的笔记本是八核的,然后再加一个云主机,我一共起了9个实例,每个实例的并发数是16。最后跑出来,大概一个小时可以抓到27W结果。
网友评论