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

处理过程

  1. 首先Slaver端从Master端拿任务(Request、url)进行数据抓取,Slaver抓取数据的同时,产生新任务的Request便提交给 Master 处理;

  2. 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'
}
View Code

将一个普通的爬虫修改为分布式爬虫的方法

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
配置分布式的Code

 

2.修改配置文件

  • 使用官方案例的配置文件进行修改
  • 配置redis数据库地址

数据持久化

概念:所谓数据持久化就是将redis中存储的item数据存储到其他数据库或介质中

为什么要做数据持久化处理

  • 1)redis是内存型数据库,容量有限
  • 2)内存在断电时会丢失所有数据,不安全
  • 3)数据的使用一般不使用redis

 

posted @ 2017-08-23 18:40  凯哥吧  阅读(219)  评论(0)    收藏  举报