前言
scrapy是python界出名的一个爬虫框架。Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。
虽然scrapy能做的事情很多,但是要做到大规模的分布式应用则捉襟见肘。有能人改变了scrapy的队列调度,将起始的网址从start_urls里分离出来,改为从redis读取,多个客户端可以同时读取同一个redis,从而实现了分布式的爬虫。就算在同一台电脑上,也可以多进程的运行爬虫,在大规模抓取的过程中非常有效。
分布式原理
scrapy-redis实现分布式,其实从原理上来说很简单,这里为描述方便,我们把自己的核心服务器称为master,而把用于跑爬虫程序的机器称为slave。
我们知道,采用scrapy框架抓取网页,我们需要首先给定它一些start_urls,爬虫首先访问start_urls里面的url,再根据我们的具体逻辑,对里面的元素、或者是其他的二级、三级页面进行抓取。而要实现分布式,我们只需要在这个starts_urls里面做文章就行了。
我们在master上搭建一个redis数据库(注意这个数据库只用作url的存储,不关心爬取的具体数据,不要和后面的mongodb或者mysql混淆),并对每一个需要爬取的网站类型,都开辟一个单独的列表字段。通过设置slave上scrapy-redis获取url的地址为master地址。这样的结果就是,尽管有多个slave,然而大家获取url的地方只有一个,那就是服务器master上的redis数据库。
并且,由于scrapy-redis自身的队列机制,slave获取的链接不会相互冲突。这样各个slave在完成抓取任务之后,再把获取的结果汇总到服务器上(这时的数据存储不再在是redis,而是mongodb或者 mysql等存放具体内容的数据库了)
这种方法的还有好处就是程序移植性强,只要处理好路径问题,把slave上的程序移植到另一台机器上运行,基本上就是复制粘贴的事情。
Scrapy-Redis是一个基于Redis的Scrapy分布式组件。它利用Redis对用于爬取的请求(Requests)进行存储和调度(Schedule),并对爬取产生的项目(items)存储以供后续处理使用。scrapy-redi重写了scrapy一些比较关键的代码,将scrapy变成一个可以在多个主机上同时运行的分布式爬虫。
scrapy_redis项目编写
- 首先需要编写一个scrapy的项目,之后在该项目的基础上修改相关配置即可实现,这里就不再做过多的解释
- 准备好redis数据库,打开,连接上客户端
- 修改settings、以及爬虫文件
settings文件中加入以下配置,并根据自己配置修改IP地址、端口号、以及密码
# 启用 Scrapy-Redis 调度存储请求队列
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 去重规则对应处理的类
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 对保存到 redis 中的数据进行序列化,默认使用pickle
# SCHEDULER_SERIALIZER = "scrapy_redis.picklecompat"
# 不清除 Redis 队列,即是否在关闭时候保留原来的调度器和去重记录。
# True=保留,False=清空。这样可以暂停/恢复 爬取
SCHEDULER_PERSIST = True
# 是否在开始之前清空 调度器和去重记录,True=清空,False=不清空
# SCHEDULER_FLUSH_ON_START = True
DEPTH_PRIORITY = 1 # 广度优先
# DEPTH_PRIORITY = -1 # 深度优先
# 使用优先级调度请求队列 (默认使用)
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue'
# 可选用的其它队列 PriorityQueue(有序集合),FifoQueue(列表)、LifoQueue(列表)
# SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.FifoQueue' # 广度优先
# SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.LifoQueue' # 深度优先
# 最大空闲时间防止分布式爬虫因为等待而关闭
# 去调度器中获取数据时,如果为空,最多等待时间(最后没数据,未获取到)。
# SCHEDULER_IDLE_BEFORE_CLOSE = 10
# 使用 scrapy-redis 的 pipeline 进行数据处理
# 序列化项目管道作为redis Key存储
# REDIS_ITEMS_KEY = '%(spider)s:items'
# 默认使用 ScrapyJSONEncoder进行项目序列化
# You can use any importable path to a callable object.
# REDIS_ITEMS_SERIALIZER = 'json.dumps'
# 指定连接到redis时使用的端口和地址(可选)
# REDIS_HOST = 'localhost'
# REDIS_PORT = 6379
# 指定用于连接 redis 的 URL(可选)。
# 如果设置此项,则此项优先级高于设置的 REDIS_HOST 和 REDIS_PORT。
# 如果没有 user 默认是 root 。
# 示例 REDIS_URL = "redis://root:12345678@192.168.0.100:6379"
# REDIS_URL = 'redis://user:pass@hostname:9001'
# 连接redis
REDIS_HOST = '127.0.0.1' # 主机名
REDIS_PORT = 6379 # 端口
REDIS_PARAMS = {'password': ''} # Redis连接参数。
REDIS_ENCODING = "utf-8" # redis编码类型。默认:'utf-8'
# 或者:
# REDIS_URL = 'redis://user:pass@hostname:6379' # 连接 URL(优先于以上配置)
# 自定义redis客户端类
# REDIS_PARAMS['redis_cls'] = 'myproject.RedisClient'
# 如果为True,则使用redis的'spop'进行操作。
# 如果需要避免起始网址列表出现重复,这个选项非常有用。开启此选项 urls 必须通过sadd添加,否则会出现类型错误。
# REDIS_START_URLS_AS_SET = False
# RedisSpider 和 RedisCrawlSpider 默认 start_usls 键
# REDIS_START_URLS_KEY = '%(name)s:start_urls'
爬虫文件中修改如下:
1.修改继承的父类
2.注销之前写好的 allowed_domains和start_urls
3编写__init__方法,copy下面的代码即可,形式比较固定
4.设置redis_key的键名,并在redis中加入该键名,值为之前
start_urls中的值。
代码为:lpush py21 + start_urls中的值
from scrapy_redis.spiders import RedisCrawlSpider
class OutsideSpider(RedisCrawlSpider):
num = 0
name = 'outside'
# allowed_domains = ['www.chinaseed114.com']
# start_urls = ['https://www.chinaseed114.com/seed/xiaomai']
def __init__(self, *args,**kwargs):
domain = kwargs.pop('domain', '')
self.allowed_domains = list(filter(None, domain.split(',')))
super(OutsideSpider, self).__init__(*args, **kwargs)
redis_key = 'py21'
- 多终端测试
最后可以开启多个终端来测试本次分布式爬虫是否成功。
tips:终端中启动爬虫的命令是scrapy runspider + 爬虫文件名
例如:scrapy runspider outside.py