分布式爬虫(scrapy_redis)
- 分布式爬虫是指将一个大型的爬虫任务分解成多个子任务,由多个爬虫进程或者多台机器同时执行的一种爬虫方式。
- 在分布式爬虫中,每个爬虫进程或者机器都具有独立的爬取能力,可以独立地爬取指定的网页或者网站,然后将爬取到的数据进行汇总和处理。
- 分布式爬虫相对于单机爬虫的优势在于:
- 高效性:分布式爬虫可以同时爬取多个网页或者网站,从而大大提高爬取速度和效率。
- 可扩展性:分布式爬虫可以根据需要动态地添加或者删除爬虫进程或者机器,从而适应不同的爬虫任务需求。
- 高稳定性:分布式爬虫可以通过任务分解和数据备份等机制保证任务的连续性和稳定性。
- 高可靠性:分布式爬虫可以通过多个 IP 地址和 User-Agent 等方式来防止 IP 被封锁、反爬等问题。1
(1) scrapy-redis架构
Scheduler
- 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中取出)等操作。
- 它把待爬队列按照优先级建立了一个字典结构,比如:
- {
- 优先级0 : 队列0
- 优先级1 : 队列1
- 优先级2 : 队列2
- }
- 然后根据request中的优先级,来决定该入哪个队列,出列时则按优先级较小的优先出列。
- 为了管理这个比较高级的队列字典,Scheduler需要提供一系列的方法。
- 但是原来的Scheduler已经无法使用,所以使用Scrapy-redis的scheduler组件。
Duplication Filter
-
Scrapy中用集合实现这个request去重功能,Scrapy中把已经发送的request指纹放入到一个集合中,把下一个request的指纹拿到集合中比对,如果该指纹存在于集合中,说明这个request发送过了,如果没有则继续操作。
-
在scrapy-redis中去重是由Duplication Filter组件来实现的,它通过redis的set 不重复的特性,巧妙的实现了Duplication Filter去重。
- scrapy-redis调度器从引擎接受request,将request的指纹存⼊redis的set检查是否重复,并将不重复的request push写⼊redis的 request queue。
-
引擎请求request(Spider发出的)时,调度器从redis的request queue队列⾥里根据优先级pop 出⼀个request 返回给引擎,引擎将此request发给spider处理。
Item Pipeline
- 引擎将(Spider返回的)爬取到的Item给Item Pipeline
- scrapy-redis 的Item Pipeline将爬取到的 Item 存⼊redis的 items queue。
- 修改过Item Pipeline可以很方便的根据 key 从 items queue 提取item
- 从⽽实现items processes集群。
Base Spider
- 不再使用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。
- 当我们生成一个Spider继承RedisSpider时,调用setup_redis函数,这个函数会去连接redis数据库,然后会设置signals(信号):
- RedisMixin是用来从redis读取url的类。
(2)Scrapy-Redis分布式策略
- 假设有四台电脑:Windows 10、Mac OS X、Ubuntu 16.04、CentOS 7.2,
- 任意一台电脑都可以作为 Master端 或 Slaver端,比如:
- Master端(核心服务器) :使用 Windows 10,搭建一个Redis数据库,不负责爬取,只负责url指纹判重、Request的分配,以及数据的存储。
- Slaver端(爬虫程序执行端) :使用 Mac OS X 、Ubuntu 16.04、CentOS 7.2,负责执行爬虫程序,运行过程中提交新的Request给Master。
- 首先Slaver端从Master端拿任务(Request、url)进行数据抓取,Slaver抓取数据的同时,产生新任务的Request便提交给 Master 处理;
- Master端只有一个Redis数据库,负责将未处理的Request去重和任务分配,
- 将处理后的Request加入待爬队列,并且存储爬取的数据。
- Scrapy-Redis默认使用的就是这种策略,我们实现起来很简单,
- 因为任务调度等工作Scrapy-Redis都已经帮我们做好了,
- 我们只需要继承RedisSpider、指定redis_key就行了。
- 缺点是,Scrapy-Redis调度的任务是Request对象,里面信息量比较大(不仅包含url,还有callback函数、headers等信息),
- 可能导致的结果就是会降低爬虫速度、而且会占用Redis大量的存储空间,
- 所以如果要保证效率,那么就需要一定硬件水平。
- from scrapy.linkextractors import LinkExtractor
- from scrapy.spiders import CrawlSpider, Rule
- from fbs2.items import DaImgItem
-
- from scrapy_redis.spiders import RedisCrawlSpider
-
-
- class DaimgSpider(RedisCrawlSpider): # 注意这里的继承类
- name = "daimg"
-
- # start_urls = ['http://www.daimg.com/pic/%E7%BE%8E%E5%A5%B3%E5%9B%BE%E7%89%87-0-0-0-0-0_1.html']
- redis_key = 'daImgQueue'
-
- link = LinkExtractor(allow=r'pic/%E7%BE%8E%E5%A5%B3%E5%9B%BE%E7%89%87')
- rules = (
- Rule(link, callback="parse_item", follow=False, ),
- )
-
- def parse_item(self, response):
- li_list = response.xpath('/html/body/div[5]/ul/li')
- for li in li_list:
- title = li.xpath('./a/img/@title').extract_first()
- src = li.xpath('./a/img/@src').extract_first()
- item = DaImgItem()
- item['img_name'] = title
- item['img_url'] = src
- print("item:::", item)
- yield item
注意:如果不是全站的深度爬虫,仅需要继承RedisSpider,来自
from scrapy_redis.spiders import RedisSpider
settings.py
- # 增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化
- DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
- # 使用scrapy-redis组件自己的调度器
- SCHEDULER = "scrapy_redis.scheduler.Scheduler"
- # 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。如果是True, 就表示要持久化存储, 就不清空数据, 否则清空数据
- SCHEDULER_PERSIST = True
-
- ITEM_PIPELINES = {
- 'scrapy_redis.pipelines.RedisPipeline': 400
- }
-
-
- REDIS_HOST = '127.0.0.1'
- REDIS_PORT = 6379
- # REDIS_ENCODING = ‘utf-8’
- # REDIS_PARAMS = {"password":"123456"}
- 在 Scrapy-Redis 分布式爬虫中
- Redis 起到了以下作用:
- 任务调度和分配:Scrapy-Redis 使用 Redis 数据库实现了多台机器之间的任务调度和分配,通过 Redis 中的队列来存储待爬取的 URL、请求和爬取状态等信息,从而实现多个爬虫进程或者机器之间的任务分配和调度。
- URL 管理:Scrapy-Redis 使用 Redis 数据库来存储待爬取的 URL 队列和已经爬取过的 URL 集合,从而避免了爬虫任务的重复执行和冲突问题。
- 数据存储:Scrapy-Redis 可以将爬取到的数据存储到 Redis 数据库中,从而实现分布式数据存储。这样可以避免单机存储造成的数据量过大和存储速度慢的问题。
- 状态共享:Scrapy-Redis 使用 Redis 数据库作为状态存储,从而实现多台机器之间的状态共享。当一个爬虫进程爬取了一个 URL 后,它可以将爬虫状态保存到 Redis 数据库中,其他进程就可以直接从 Redis 数据库中获取该状态。
- 开启多个slaver端:
scrapy crawl daming
- 启动Master端:
- # redis-cli
- # flushall
- # lpush daImgQueue http://www.daimg.com/pic/%E7%BE%8E%E5%A5%B3%E5%9B%BE%E7%89%87-0-0-0-0-0_1.html
- - 编码流程:
- 1.创建工程
- 2.cd proName
- 3.创建crawlspider的爬虫文件
- 4.修改一下爬虫类:
- - 导包:from scrapy_redis.spiders import RedisCrawlSpider
- - 修改当前爬虫类的父类:RedisCrawlSpider
- - allowed_domains和start_urls删除
- - 添加一个新属性:redis_key = 'xxxx'可以被共享的调度器队列的名称
- 5.修改配置settings.py
- - 指定管道
- ITEM_PIPELINES = {
- 'scrapy_redis.pipelines.RedisPipeline': 400
- }
- - 指定调度器
- # 增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化
- DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
- # 使用scrapy-redis组件自己的调度器
- SCHEDULER = "scrapy_redis.scheduler.Scheduler"
- # 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。
- # 如果是True, 就表示要持久化存储, 就不清空数据, 否则清空数据
- SCHEDULER_PERSIST = True
- - 指定redis数据库
- REDIS_HOST = 'redis服务的ip地址'
- REDIS_PORT = 6379
- 6.配置redis数据库(redis.windows.conf)
- - 关闭默认绑定
- - 56Line:#bind 127.0.0.1
- - 关闭保护模式
- - 75line:protected-mode no
- 7.启动redis服务(携带配置文件)和客户端
- - redis-server.exe redis.windows.conf
- - redis-cli
- 8.执行工程
- - scrapy runspider spider.py
- 9.将起始的url仍入到可以被共享的调度器的队列(sun)中
- - 在redis-cli中操作:lpush sun www.xxx.com
- 10.redis:
- - xxx:items:存储的就是爬取到的数据