Scrapy框架04【scrapy_redis分布式爬虫,概述,及实例】
scrapy_redis概述
分布式爬虫源码git https://github.com/rmax/scrapy-redis
scrapy是一个通用的爬虫框架,但是不支持分布式,Scrapy-redis是为了更方便的实现scrapy分布式爬取
Ubuntu下载包
安装Redis:http://redis.io/download
pip install scrapy-redis
scrapy_redis执行命令:
slave端: scrapy runspider sina.py # 爬虫文件名 Master端: redis-cli> lpush sinaspider:start_urls http://news.sina.com.cn/guide/ # 向redis的数据库当中添加起始的url
scrapy_redis的四个组件
Scheduler调度器-->将数据放到redis数据库队列当中
Duplication Filter- 重复过滤器-->在redis数据库当中去重
Item Pipeline- 将数据保存到redis队列当中
Base Spider- 从redis当中获取url


Duplication Filter
Scrapy中用集合实现这个request去重功能,Scrapy中把已经发送的request指纹放入到一个集合中,把下一个request的指纹拿到集合中比对,如果该指纹存在于集合中,说明这个request发送过了,如果没有则继续操作。这个核心的判重功能是这样实现的:
def request_seen(self, request):
# 把请求转化为指纹
fp = self.request_fingerprint(request)
# 这就是判重的核心操作 ,self.fingerprints就是指纹集合
if fp in self.fingerprints:
return True #直接返回
self.fingerprints.add(fp) #如果不在,就添加进去指纹集合
if self.file:
self.file.write(fp + os.linesep)
在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处理。
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配置文件的说明
# -*- coding: utf-8 -*- # 指定使用scrapy-redis的调度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 指定使用scrapy-redis的去重 这个时候,原来的过滤器将会被停用 DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter' # 指定排序爬取地址时使用的队列, # 默认的 按优先级排序(Scrapy默认),由sorted set实现的一种非FIFO、LIFO方式。 SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderPriorityQueue' # 可选的 按先进先出排序(FIFO) # SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderQueue' # 可选的 按后进先出排序(LIFO) # SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderStack' # 在redis中保持scrapy-redis用到的各个队列,从而允许暂停和暂停后恢复,也就是不清理redis queues 是否能从从断点(断掉的地方)继续爬取 SCHEDULER_PERSIST = True # 只在使用SpiderQueue或者SpiderStack是有效的参数,指定爬虫关闭的最大间隔时间 # SCHEDULER_IDLE_BEFORE_CLOSE = 10 # 通过配置RedisPipeline将item写入key为 spider.name : items 的redis的list中,供后面的分布式处理item # 这个已经由 scrapy-redis 实现,不需要我们写代码 ITEM_PIPELINES = { 'example.pipelines.ExamplePipeline': 300, 'scrapy_redis.pipelines.RedisPipeline': 400 } # 指定redis数据库的连接参数 # REDIS_PASS是我自己加上的redis连接密码(默认不做) REDIS_HOST = '127.0.0.1' REDIS_PORT = 6379 #REDIS_PASS = 'redisP@ssw0rd' # LOG等级 LOG_LEVEL = 'DEBUG' #默认情况下,RFPDupeFilter只记录第一个重复请求。将DUPEFILTER_DEBUG设置为True会记录所有重复的请求。 DUPEFILTER_DEBUG =True # 覆盖默认请求头,可以自己编写Downloader Middlewares设置代理和UserAgent DEFAULT_REQUEST_HEADERS = { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.8', 'Connection': 'keep-alive', 'Accept-Encoding': 'gzip, deflate, sdch' }
将一个普通的爬虫修改为分布式爬虫的方法
1.修改爬虫文件
- )导入分布式爬虫类
- 修改爬虫的继承
- 注销allowed_domains和start_urls
- )重写__init__()方法(可选)
- 添加redis_key
# -*- coding: utf-8 -*- import scrapy import time from Tianqi.items import TianqiItem # ----------导入redisSpider类 from scrapy_redis.spiders import RedisSpider # ------------将集成的类改变 class TianqiSpider(RedisSpider): name = 'tianqi' # -----------——注销掉允许的域和其实的url列表 # allowed_domains = ['tianqi.com'] # start_urls = ['http://lishi.tianqi.com/'] # -----------编写__init__ 动态获取允许的域 def __init__(self, *args, **kwargs): domain = kwargs.pop('domain', '') self.allowed_domains = filter(None, domain.split(',')) super(TianqiSpider, self).__init__(*args, **kwargs) # -------编写reids_key redis_key = "tianqi:start_urls" def __str__(self): pass def parse(self, response): # 明确目标建模 # 编写爬虫 # 获取地区对应的名称及url area_name_list = response.xpath("//div[@id='tool_site']/div[2]/ul/li/a/text()").extract() area_url_list = response.xpath("//div[@id='tool_site']/div[2]/ul/li/a/@href").extract() for area_name, area_url in list(zip(area_name_list, area_url_list))[0:10]: # 遍历每个地区发送请求, 并传递meta参数 if area_url != "#": yield scrapy.Request(url=area_url, callback=self.parse_area, meta={"area_name": area_name}) def parse_area(self, response): # 接受传来的meta参数 area_name = response.meta["area_name"] # 获取当地的所有月份的天气 li_list = response.xpath("//div[@class='tqtongji1']/ul/li/a/@href").extract() # 遍历发送请求 for li in li_list: yield scrapy.Request(url=li, callback=self.parse_date, meta={"area_name": area_name}) def parse_date(self, response): area_name = response.meta["area_name"] # print(area_name, response.url, "*"*50) data_ul_list = response.xpath("//div[@class='tqtongji2']/ul") for ul in data_ul_list[1:]: item = TianqiItem() # print('-'*88) # print(ul) # print('-'*88) item['area'] = area_name item['url'] = response.url item['timestamp'] = time.time() item['date'] = ul.xpath('./li[1]/a/text()').extract_first() if item['date'] is None: item['date'] = ul.xpath('./li[1]/text()').extract_first() item['max_t'] = ul.xpath("./li[2]/text()").extract_first() item['min_t'] = ul.xpath('./li[3]/text()').extract_first() item['weather'] = ul.xpath('./li[4]/text()').extract_first() item['wind_direction'] = ul.xpath('./li[5]/text()').extract_first() item['wind_power'] = ul.xpath('./li[6]/text()').extract_first() yield item
2.修改配置文件
- 使用官方案例的配置文件进行修改
- 配置redis数据库地址
数据持久化
概念:所谓数据持久化就是将redis中存储的item数据存储到其他数据库或介质中
为什么要做数据持久化处理
- 1)redis是内存型数据库,容量有限
- 2)内存在断电时会丢失所有数据,不安全
- 3)数据的使用一般不使用redis

浙公网安备 33010602011771号