浅谈scrapy-redis分布式爬虫

学习目标

1.理解scrapy-redis概念

2.理清工作原理和主要组件

3.理清爬虫编写的思路

4.实战

简介

scrapy-redis是scrapy基于Redis数据库的组件。

scrapy本身不支持分布式,所以并不会共享调度队列。scrapy-redis是为了方便实现scrapy分布式爬取,提供了redis为基础的组件。

特点

  • 分布式爬取:可启动多个spider工程,相互间共享单个redis的request队列
  • 分布式数据处理:将爬到的item推送到redis队列中(意味着可以按照需求启动尽可能的多的处理程序来共享item的队列,进行item数据持久化处理)

scrapy 与scrapy-redis

scrapy:通用爬虫框架,不支持分布式

scrapy-redis:在原有的scrapy基础上修改了组件,使之能够支持分布式

注意

​ 分布式听起来好像蛮厉害的,但它只不过是提高爬虫运行效率的一个小环节。当爬取的要求是:短时间内爬取海量的信息,这时候适合使用分布式爬虫。不过难点不在于代码怎么设计,而是着重于反爬策略。速度过快,会给对方服务器造成必要的压力,自然而然会被反爬技术阻拦。

工作流程

  1. 爬虫(起始的url构造成request对象)→爬虫中间件引擎

  2. 引擎(从爬虫中获取到第一个要爬的url,封装成request)→调度器

  3. 调度器(访问Redis数据库,Redis对请求判重,不重则添加到Redis中)→Redis

  4. Redis→调度器(当调度条件满足时,调度器从Redis中取出request)→引擎下载中间件下载器

  5. 下载器(页面一旦下载完毕,下载器将生成该页面的response)→下载中间件引擎

  6. 引擎(接收response响应)→爬虫中间件爬虫

  7. 爬虫(处理response并将item和新的request)→引擎

  8. 引擎(将item)→item pipelineRedis

    同时引擎(将新的request)→调度器(重复2步骤)

主要组件

Scheduler(调度器)

​ scrapy改变了python原本collection.deque(双向队列),形成自己的scrapy.queue。但是scrapy里多个spider不能共享待爬的scrapy.queue队列,因此scrapy本身不支持分布式爬虫。

​ scrapy-redis把scrapy Queue换成了Redis数据库,由一个数据库统一存放要爬取请求,这样就能够实现分布式爬虫。

Dupilaction Filter (去重)

​ scrapy使用集合实现去重功能,这是它自带的模块。它会把已经发送出去的请求指纹(指纹可以理解未一个特殊值)放到一个集合。它会把下一个请求指纹和集合中做对比,存在则说明请求已经发送,不存在则继续操作。

​ scrapy-redis去重是由Dupilcation Filter组件实现的,调度器通过redis的set不重复特性,实现去重。Dupilcation Filter set存放爬取过的request,调度器将spider新生成的request放到redisd Dupilcation Filter set中检查是否重复,不重复则将request push到redis的request队列中。

Item Pipeline(管道)

​ 当爬虫取到的Item数据时,引擎将会把Item数据交给Item Pipeline,Item Pipeline再把Item数据保存发哦Redistribution的Item队列中。scrapy-redis对ItemPipeline进行修改,可以根据key从Item队列中取出Item,从而实现Item Process集群。

Base Spider(爬虫)

​ 不再使用原来的Spider类,而是使用RedisSpider类,这个类继承了Spider和RedisMixin这两个类。RedisMixin用来读取Redistribution上的url。

爬虫编写的思路

普通爬虫

  • 明确目标
  • 创建项目
  • 创建爬虫

将普通爬虫改为分布式爬虫

  • 在settings.py中设置相关redis配置

  • 导入scrapy-redis类

  • 继承这个类并把start_urlallowed-domains这两行代码删掉

  • 设置redis-key获取start-urls

实战

环境要求

  • 安装scrapy-redis库:pip install scrapy-redis
  • 安装Redis
  • 安装Redis可视化工具(可选)

创建项目

流程简单,不讲

配置scrapy-redis

详细的代码可以到这里看看,文章中就不赘述了。

这次的代码我是直接拿上一篇的实战改的,普通scrapy思路都一样,我这里就不讲了,只讲修改的部分

修改settings.py

​ 在该文件下添加以下代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 设置重复过滤器模块
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

# 设置调取器,scrap_redis中的调度器具备与数据库交互的功能
SCHEDULER = "scrapy_redis.scheduler.Scheduler"

# 设置爬虫结束时是否保持redis数据库中的去重集合与任务队列,程序结束后不清空redis数据库
SCHEDULER_PERSIST = True

ITEM_PIPELINES = {
    #这一行更具项目名不同而不同,本身就有,不用管也不用抄
    'suningBook.pipelines.SuningbookPipeline': 300,
    # 开启管道,该管道将会把数据存到Redis数据库中,也可以自己设置数据库
    'scrapy_redis.pipelines.RedisPipeline': 400,
}
# 设置redis数据库
REDIS_URL = "redis://127.0.0.1:6379"

# 请求间隔时长
DOWNLOAD_DELAY = 1

pipeline.py

增加下面的代码,可以查看item获取的时间,同时知道这些数据是哪一个虫子爬取的

1
2
3
4
5
6
7
8
9
from datetime import datetime

class SuningbookPipeline(object):
    def process_item(self, item, spider):
        # 获取UTC时间
        item["crawled"] = datetime.utcnow()
        # 爬虫名称
        item["spider"] = spider.name
        return item

spider/bookPro.py

这里是写爬虫的地方,只需要修改部分即可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# -*- coding=utf-8 -*-
from scrapy_redis.spiders import RedisSpider

class BookproSpider(RedisSpider):
    name = 'bookPro'
    redis_key = "bookprospider:sun_urls"
    
    def parse(self, response):
        # 一级分类(L1表示Lecel 1)
        L1_list = response.xpath('//div[@class="menu-list"]/div/dl')
        # 获取大分类
        for L1 in L1_list:
            item = {}
            item['L1_cate'] = L1.xpath('./dt/h3/a/text()').extract_first()
            # 获取二级分类
            L2_list = L1.xpath('./dd/a')

            for L2 in L2_list:
                # 二级分类url
                item['L2_href'] = L2.xpath('./@href').extract_first()
                # 二级分类名称
                item['L2_name'] = L2.xpath('./text()').extract_first()
                # return item

                yield scrapy.Request(
                    url=item['L2_href'],
                    callback=self.parse_book_list,
                    meta={'item': item}
                )

    def parse_book_list(self, response):
        item = response.meta['item']
    #     #获取每一本书的信息
        books_list = response.xpath('//ul[@class="clearfix"]/li')
        for book in books_list:
            #书籍详情URL
            item['book_href'] = 'https:' + book.xpath('//div[@class="res-img"]/div/a/@href').extract_first()
            # 书籍名字
            item['book_name'] = book.xpath('//div[@class="res-img"]/div/a/img/@alt').extract_first()
            # 书籍图片
            item['src'] = 'https:' + book.xpath('//div[@class="res-img"]/div/a/img/@src2').extract_first()

            yield scrapy.Request(
                url=item['book_href'],
                callback=self.parse_book_detail,
                meta={'item': item}
            )

    def parse_book_detail(self, response):
        item = response.meta['item']
        # 作者
        item['author'] = response.xpath('//ul[@class="bk-publish clearfix"]/li[1]/text()').extract_first()
        # 出版社
        item['publish'] = response.xpath('//ul[@class="bk-publish clearfix"]/li[2]/text()').extract_first()
        yield item

​ 简单解释一下下:这个爬虫继承了RedisSpider,支持分布式爬取。这里注意的是,不再需要写start_urls和了,被redis_key取代。必须指定redis_key,这个是启动爬虫的格式。

执行

  • 打开Redis的redis-server.exe
  • 打开Redis的redis-cli.exe
  • 在项目里面执行运行代码:
1
2
3
# 注意,要进入spiders文件目录下执行这条命令
# 格式: scrapy runspider [要执行的爬虫文件名]
scrapy runspider bookPro.py
  • 在Master端的redis-cli输入lpush命令:
1
2
# 命令格式: lpush [redis_key] [网站URL]
127.0.0.1:6379> lpush bookprospider:sun_urls http://book.suning.com/

ok,这样就可以了

再看一眼Redis可视化工具