如何自己实现一个scrapy框架——框架升级(七)

本文介绍增量爬虫的设计原理与实现方法,包括修改Request对象以支持去重,调整调度器逻辑,以及实现无限请求。同时,探讨断点续爬原理,演示如何在Scrapy框架中开启和关闭此功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、增量爬虫设计原理及其实现

1、增量爬虫设计原理

增量抓取,意即针对某个站点的数据抓取,当网站的新增数据或者该站点的数据发生了变化后,自动地抓取它新增的或者变化后的数据
设计原理:
这里写图片描述
2、实现关闭请求去重
2.1 为Request对象增加属性filter

# scrapy/http/reqeust.py
class Request():
    """
    框架内封装的request对象
    """
    def __init__(self, url, method='GET', headers=None, params=None,
                 data=None, parse='parse', meta={}, filter=True): # 此处修改
        ......
        # 此处新增
        self.filter = filter  # 是否进行去重,默认是True 表示去重!

2.2 修改调度器,进行判断

# scrapy_plus/core/scheduler.py
class Scheduler(object):

    ......

    # 此处修改
    def add_request(self,request):
        '''
        实现添加request到队列中
        :param request: 请求对象
        :return: None
        '''
        #判断请求是否需要进行去重,如果不需要,直接添加到队列
        if not request.filter:#不需要去重
            request.fp = self._gen_fp(request)
            self.queue.put(request)
            logger.info("添加不去重的请求<{} {}>".format(request.method,request.url))
            return  #必须return

        if self._filter_request(request):
            self.queue.put(request)

    ......

2.3 修改项目中代码

# project/spiders/baidu.py
......
class BaiduSpider(Spider):

    name = 'baidu'
    start_urls = ['http://www.baidu.com']

    total = 0

    def parse(self, response):
        self.total += 1
        if self.total > 10:
            return
        yield Request(self.start_urls[0], filter=False, parse='parse')
# project/settings.py
......
# 启用的爬虫类
SPIDERS = [
    'spiders.baidu.BaiduSpider',
    # 'spiders.douban.DoubanSpider'
]
......

##3 实现无限发起请求
3.1 修改scrapy_plus/core/spider.py
添加一个timed_task属性

# scrapy_plus/core/spider.py
......
class Spider():
    """
    构建请求信息(初始的),也就是生成请求对象(Request)
    解析响应对象,返回数据对象(Item)或者新的请求对象(Request)
    """
    name = ''
    start_urls = [] # 默认初始请求地址

    # 定时爬虫标识
    # 默认为false,表示不是定时爬虫
    timed_task = False
    ......

3.2 修改引擎代码
修改处理初始urls的_start_request函数

但由于框架调用start_requests方法是同步的,如果设置为死循环后,那么启动后其他的爬虫的start_requests方法就不会被调用,因此需要在调用每个爬虫的start_reqeusts时设置为异步的
修改引擎的逻辑函数_start_engine的退出逻辑

把所有爬虫的timed_task组成一个列表并sum()求和,等于0就表示都是False,没有定时任务
如果不等于0就表示有定时任务,此时引擎一直执行

scrapy_plus/core/engine.py
    ......
    # 此处修改
    def _start_request(self):
        """单独处理爬虫模块中start_requests()产生的request对象"""
        def _func(spider_name, spider):
            for start_request in spider.start_requests():
                #1. 对start_request进过爬虫中间件进行处理
                for spider_mid in self.spider_mids:
                    start_request = spider_mid.process_request(start_request)
                # 为请求对象绑定它所属的爬虫的名称
                start_request.spider_name = spider_name
                #2. 调用调度器的add_request方法,添加request对象到调度器中
                self.scheduler.add_request(start_request)
                self.collector.incr(self.collector.request_nums_key)
        for spider_name, spider in self.spiders.items():
            # 把执行每个爬虫的start_requests方法,设置为异步的
            self.pool.apply_async(_func, args=(spider_name, spider), error_callback=self._error_callback) 
    ......
    def _start_engine(self):
        '''
        具体的实现引擎的细节
        :return:
        '''
        self.is_running = True  # 启动引擎,设置状态为True
        # 处理strat_urls产生的request
        self.pool.apply_async(self._start_request, error_callback=self._error_callback)  # 使用异步线程池中的线程执行指定的函数

        # 不断的处理解析过程中产生的request
        for i in range(MAX_ASYNC_THREAD_NUMBER): # 控制最大并发数
            self.pool.apply_async(self._execute_request_response_item, callback=self._call_back, error_callback=self._error_callback)

        # 此处修改
        timed_task_sum = sum([spider.timed_task for spider in self.spiders.values()])  # 对[False,True]求和
        start_url_nums = sum([len(spider.start_urls) for spider in self.spiders.values()])
        # 控制判断程序何时中止
        while True:
            time.sleep(1) # 避免cpu空转,避免性能消耗
            # 有定时爬虫时,一直不退出程序; 没有定时增量爬虫,才判断是否退出!
            if self.collector.request_nums+self.collector.repeat_request_nums >= start_urls_nums and timed_task_sum == 0:
                if self.collector.response_nums+self.collector.repeat_request_nums >= self.collector.request_nums:
                    self.is_running = False
                    break

3.3 项目代码中新增一个baidu2.py爬虫,在start_reqeusts中改成无限循环,并设置对应请求为非去重模式。(注意filter参数)

# project/spiders/baidu2.py
import time
from scrapy_plus.core.spider import Spider
from scrapy_plus.htttp.request import Request  # 注意这里是htttp

class Baidu2Spider(Spider):
    name = 'baidu2_spider'
    start_urls = ['https://www.baidu.com']
    timed_task = True # 表示 这是一个定时爬虫

    def start_requests(self):
        while True:
            for url in self.start_urls:
                yield Request(url, parse='parse', filter=False) # 注意这个parse接收的是字符串
                time.sleep(6)  # 定时发起请求,此时程序不会停止!

    def parse(self, response):
        print(response.body)
        yield response.body  # 一定要写yield

3.4 并在项目的settings.py中添加baidu2爬虫

# project/settings.py
......
# 启用的爬虫类
SPIDERS = [
    'spiders.baidu.BaiduSpider',
    'spiders.baidu2.Baidu2Spider',
]
......

显示结果:

2018-08-06 09:30:27 scheduler.py[line:43]                   INFO: 添加不去重的请求<GET http://www.baidu.com>
2018-08-06 09:30:27 scheduler.py[line:43]                   INFO: 添加不去重的请求<GET http://www.baidu.com>
2018-08-06 09:30:27 scheduler.py[line:43]                   INFO: 添加不去重的请求<GET http://www.baidu.com>
2018-08-06 09:30:27 scheduler.py[line:43]                   INFO: 添加不去重的请求<GET http://www.baidu.com>
2018-08-06 09:30:27 scheduler.py[line:43]                   INFO: 添加不去重的请求<GET http://www.baidu.com>
2018-08-06 09:30:27 scheduler.py[line:43]                   INFO: 添加不去重的请求<GET http://www.baidu.com>

注意:每次运行之前需要先清空redis数据库,否则会因为redis的url去重而停止运行
清空指令FLUSHALL
#二、断点续爬设计原理及其实现
##1 断点续爬的原理
在《框架升级-分布式爬虫设计原理及其实现》这一节当中,我们通过redis指纹集合已经实现了断点续爬的功能:

即利用持久化的指纹集合对request进行去重。
通过配置SCHEDULER_PERSIST=True来使用redis的同时也开启了断点续爬的功能
问题:那如何关闭断点续爬的功能呢?

思路:新增一个FP_PERSIST=True默认配置项,当它等于False的时候,引擎运行结束时删除redis中的指纹集合,这样就达到关闭断点续爬功能的目的了
##2 实现关闭断点续爬的功能
2.1 修改默认配置文件

# scrapy_plus/conf/default_settings.py
......
# 指纹是否持久化
# 默认为True 即持久化请求对象的指纹,达到断点续爬的目的
# 如果为False,则在爬虫结束的时候清空指纹库
# 可以在项目的settings.py中重新赋值进行覆盖
FP_PERSIST = True
......

2.2 修改utils下stats_collector.py中的clear()函数

# scrapy_plus/utils/stats_collector.py
from scrapy_plus.conf.settings import REDIS_HOST, REDIS_PORT, REDIS_DB, FP_PERSIST, REDIS_SET_NAME
......
    def clear(self):
        '''程序结束后清空所有计数的值'''
        self.redis.delete(self.request_nums_key, self.response_nums_key,
                          self.repeat_request_nums_key, self.start_request_nums_key)
        # 判断是否清空指纹集合
        if not FP_PERSIST: # not True 不持久化,就清空指纹集合
            self.redis.delete(REDIS_SET_NAME)
......

2.3 修改项目的settings.py文件

......
# 启用的爬虫类
SPIDERS = [
    'spiders.baidu.BaiduSpider',
    # 'spiders.baidu2.Baidu2Spider',
    # 'spiders.douban.DoubanSpider',
]
......

FP_PERSIST = True

重装后不清空redis二次运行结果:

2018-08-06 09:59:36 scheduler.py[line:70]                   INFO: 发现重复的请求:<GET http://www.baidu.com>
2018-08-06 09:59:37 engine.py[line:91]                   INFO: 爬虫结束:2018-08-06 09:59:37.932641
2018-08-06 09:59:37 engine.py[line:92]                   INFO: 耗时:1.00631
2018-08-06 09:59:37 engine.py[line:96]                   INFO: 一共获取了请求:1个
2018-08-06 09:59:37 engine.py[line:97]                   INFO: 重复的请求:1个
2018-08-06 09:59:37 engine.py[line:98]                   INFO: 成功的请求:0个

#3. 请求丢失的情况
3.1 产生请求丢失的场景
当项目运行后,Ctrl+C主动终止进程,或爬虫代码异常等程序非正常结束的情况下,某个request对象已经从队列中取出,但最终获取的数据的过程没有完成。
此时fp指纹集合中已经存在了该指纹,再启动项目运行,因该请求设置了去重,那么再也无法发出该请求。
这个情况就是请求丢失。
3.2 不光我们的scrapy_plus会出现这个情况;scrapy框架同样如此
解决请求丢失情况的需求,本身就是个伪需求,没有必要,多次一举。
如果真的要解决请求丢失也是可以的
3.3 解决请求丢失的方案(了解)
方案一
持久化fp指纹集合的同时保存fp相应的request对象,并在获取的数据中添加fp字段,保存获取该数据的request指纹
全部数据抓取完成后,删除数据中相同的fp指纹,最后指纹集合中剩下的fp对应的request对象,就是丢失的请求
方案二
添加一个请求备份容器
这里写图片描述

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值