1. 简介

scrapy流程

img

scrapy-redis

scrapy是一个基于redis的scrapy组件,用于快速实现scrapy项目的分布式部署和数据爬取。

img

组件
  • Scrapy Engine(引擎):负责Spider、ItemPipeline、Downloader、Scheduler中间的通讯,信号、数据传递等。
  • Scheduler(调度器):它负责接受引擎发送过来的Request请求,并按照一定的方式进行整理排列,入队,当引擎需要时,交还给引擎。
  • Downloader(下载器):负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spider来处理。
  • Spider(爬虫):它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器)。
  • Item Pipeline(管道):它负责处理Spider中获取到的Item,并进行进行后期处理(详细分析、过滤、存储等)的地方。
  • Downloader Middlewares(下载中间件):自定义扩展下载功能的组件。
  • Spider Middlewares(Spider中间件):自定扩展和操作引擎和Spider中间通信的功能组件。
流程

(1) 引擎(Scrapy Engine)向爬虫(Spiders)请求第一个要爬取的URL。

(2) 引擎从爬虫中获取到第一个要爬取的URL,封装成请求(Request)并交给调度器(Scheduler)。

(3) 调度器访问Redis数据库对请求进行判重,如果不重复,就把这个请求添加到Redis中。

(4) 当调度条件满足时,调度器会从Redis中取出Request,交给引擎,引擎将这个Request通过下载中间件转发给下载器。

(5) 一旦页面下载完毕,下载器(Downloader)生成一个该页面的响应(Response),并将其通过下载中间件发送给引擎。

(6) 引擎从下载器中接收到响应,并通过爬虫中间件(Spider Middlewares)发送给爬虫处理。

(7) Spider处理Response,并返回爬取到的Item及新的Request给引擎。

(8) 引擎将爬取到的Item通过Item Pipeline给Redis数据库,将Request给调度器。

从(2) 开始重复,直到调度器中没有更多的Request为止。

2. scrapy-redis对比scrapy

scrapy
  • 调度器、管道不可以被分布式集群共享

  • scrapy使用改造后的collection.deque(双端队列)存放待爬取的request

  • scrapy中跟待爬取队列直接相关的是Scheduler(调度器),scheduler负责对新的requests进行入列操作,把待爬取的队列安装优先级建立字典,根据request中的priority属性做优先级(越小的优先级越高)进行出队列操作(不能共享)。

  • scrapy把已发送的request指纹放到集合中,下一个request指纹拿到集合中对比,存在说明已经爬取过不继续执行操作。

    def request_seen(self, request: Request) -> bool:
    	fp = self.request_fingerprint(request)
    	if fp in self.fingerprints:		# self.fingerprints指纹集合
    		return True
    	self.fingerprints.add(fp)
    	if self.file:
    		self.file.write(fp + '\n')
    	return False
    

scrapy-redis
  • scrapy-redis使用redis list存放待爬取的request

  • scrapy-redis在setting配置SCHEDULER = "scrapy_redis.scheduler.Scheduler"替换原本的待爬取队列。使用redis进行任务分发与调度,把所有待爬取的请求都放入redis,所有爬虫都去redis读取请求。

  • Scrapy-Redis中的去重是由Duplication Filter组件实现的,该组件利用Redis中set集合不重复的特性,巧妙地实现了这个功能。首先Scrapy-Redis调度器接收引擎递过来的请求,然后将这个请求指纹存入set集合中检查是否重复,并把不重复的请求加入到Redis的请求队列中。

        def request_seen(self, request):
            fp = self.request_fingerprint(request)
            # This returns the number of values added, zero if already exists.
            added = self.server.sadd(self.key, fp)
            return added == 0
    

  • scrapy-redis不再使用原有的Spider类,重写RedisSpider继承Spider和RedisMixin类。当我们生成一个Spider继承RedisSpider时,调用setup_redis函数,这个函数会去连接redis数据库,然后会设置signals(信号):一个是当spider空闲时候的signal,会调用spider_idle函数,这个函数调用schedule_next_request函数,保证spider是一直活着的状态,并且抛出DontCloseSpider异常。一个是当抓到一个item时的signal,会调用item_scraped函数,这个函数会调用schedule_next_request函数,获取下一个request。

3. 简述

命令
# 新建项目
$ scrapy startproject project_name

# 新建爬虫
$ scrapy genspider -t basic spider_name www.baidu.com
basic 基础
crawl 自动爬虫
csvfeed 用来处理csv文件
xmlfeed 用来处理xml文件

# 运行爬虫
$ scrapy crawl spider_name

# 自动爬虫(做增量爬虫)
$ scrapy genspider -t crawl xxx www.xxx.com
$ scrapy runspider xxx.py


# 交互式终端
进入终端:scrapy shell ‘www.baidu.com’
查看状态: response
显示网页html:response.text
xpath匹配数据:response.xpath(‘//div[@class=”head_wrapper”])
退出终端:exit()

# 帮助
$ scrapy -h/--help
目录文件说明
scrapy.cfg :项目的配置文件

Spider/ :项目的Python模块,将会从这里引用代码

Spider/items.py :项目的目标文件

Spider/pipelines.py :项目的管道文件

Spider/middlewares.py : 爬虫、下载中间件

Spider/settings.py :项目的设置文件

Spider/spiders/ :存储爬虫代码目录
代码修改
# setting.py
# 1.启用调度将请求存储进redis
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 2.确保所有spider通过redis共享相同的重复过滤。
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 3.指定连接到Redis时要使用的主机和端口。
REDIS_HOST = '47.97.102.116'
REDIS_PORT = 6379

# 默认的scrapy-redis请求队列形式(按优先级)
SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderPriorityQueue"
# 队列形式,请求先进先出
#SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderQueue"
# 栈形式,请求先进后出
#SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderStack"

# 设置为True不清理redis队列,允许暂停/恢复抓取。多个爬虫读取url会造成重复抓取
SCHEDULER_PERSIST = True

# spider修改
from scrapy_redis.spiders import RedisSpider
class Spider(RedisSpider):  
    name = 'spider_name'
    # allowed_domains = ['movie.douban.com']  # 爬取边界
    redis_key = 'db:start_urls'   # 开启爬虫钥匙

# redis
$ lpush db:start_urls www.baidu.com
调度算法

爬虫请求调度算法有三种:

  1. 队列(默认)

    SCHEDULER_QUEUE_CLASS=‘scrapy_redis.queue.SpiderQueue’

    先进先出队列,先放进Redis的请求会优先爬取。

  2. SCHEDULER_QUEUE_CLASS=‘scrapy_redis.queue.SpiderStack’

    后进先出,后放入redis的请求会优先爬取。

  3. 优先级队列

    SCHEDULER_QUEUE_CLASS=‘scrapy_redis.queue.SpiderPriorityQueue’

    根据优先级算法计算出请求爬取先后。

Redis存放内容
  • spidername:items(不建议用,爬取内容多时会很占用内存,一般把数据保存到mongodb)

    list类型,保存爬虫获取到的数据item内容是json字符串。

  • spidername:dupefilter

    set类型,用于爬虫访问的URL去重内容,是40个字符的url的hash字符串

  • spidername:start_urls

    list类型,用于接收redis spider启动时的第一个url

  • spidername:requests

    zset类型,用于存放requests等待调度。内容是requests对象的序列化字符串

4. 分布式策略

img

​ Slaver获取Master待爬取Request进行数据爬取,在爬取过程中处理生成新的任务抛给Master。Master只有一个Redis数据库复制对Slave任务进行去重、加入待爬取队列。

注意:Master和Slaver交互的信息并不单单是url而是包含许多信息的Request

{'url': 'https://book.qidian.com/info/1010868264/', 'callback': 'parse_detail', 'errback': None, 'headers': {b'Referer': [b'https://www.qidian.com/all/page2/'], b'Accept': [b'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'], b'Accept-Language': [b'en'], b'User-Agent': [b'Scrapy/2.6.1 (+https://scrapy.org)'], b'Accept-Encoding': [b'gzip, deflate'], b'Cookie': [b'_csrfToken=Y4KP9vSv2X6XuWvDyVeke5o0jlyUazCqrrosBGrJ; newstatisticUUID=1652946272_1559582696; fu=762226548']}, 'method': 'GET', 'body': b'', 'cookies': {}, 'meta': {'item': {'detail_url': 'https://book.qidian.com/info/1010868264/',
 'img_url': 'https://bookcover.yuewen.com/qdbimg/349573/1010868264/150',
 'name': '诡秘之主'}, 'depth': 2, 'download_timeout': 180.0, 'download_slot': 'book.qidian.com', 'download_latency': 0.5939218997955322, 'retry_times': 1}, 'encoding': 'utf-8', 'priority': -1, 'dont_filter': True, 'flags': [], 'cb_kwargs': {}}

5.进阶

中间件使用

下载中间件(Downloader Middleware)核心方法有3个:

  • process_request(request, spider)

    设置headers,proxy

  • process_response(request, response, spider)

    设置response编码等信息

  • process_exception(request, exception, spider)

    异常报错(曾它做过重试,但后来发现直接在setting设置更方便)

crawl 爬虫

基于CrawlSpider可以很方便地进行全站数据爬取

class QidianCrawlSpider(CrawlSpider):	# 继承CrawlSpider类
    name = 'qidian_crawl'
    allowed_domains = ['www.qidian.com', 'book.qidian.com']
    start_urls = ['http://www.qidian.com/']

    rules = (
        Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
    )
# rules规则解析器内包含链接提取器LinkExtractor(allow=r'Items/'),callback指定解析方法,follow指定爬取页面内可见部分还是全部(True把链接提取器继续作用到链接提取器对应的页面,False爬取页面内可见部分页面)。

使用CrawlSpider生成爬虫文件时,在规则解析器rules里面添加正则表达式进而发起请求,如果要一个请求内需要再次发起请求,就需要在rules中添加链接请求并指定对应的解析方法

6. 优缺点

优点:广泛多域爬取大量url,节约时间,去重简单,可以启动尽可能爬虫去进行数据爬取。

内包含链接提取器LinkExtractor(allow=r’Items/'),callback指定解析方法,follow指定爬取页面内可见部分还是全部(True把链接提取器继续作用到链接提取器对应的页面,False爬取页面内可见部分页面)。


使用`CrawlSpider`生成爬虫文件时,在规则解析器`rules`里面添加正则表达式进而发起请求,如果要一个请求内需要再次发起请求,就需要在`rules`中添加链接请求并指定对应的解析方法




### 6. 优缺点

优点:广泛多域爬取大量url,节约时间,去重简单,可以启动尽可能爬虫去进行数据爬取。

缺点:相对于单个爬虫不便于管理。Request对象里面信息量较大,降低爬虫速度、占用Redis存储空间。
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐