Wang's blog Wang's blog
首页
  • 前端文章

    • HTML教程
    • CSS
    • JavaScript
  • 前端框架

    • Vue
    • React
    • VuePress
    • Electron
  • 后端技术

    • Npm
    • Node
    • TypeScript
  • 编程规范

    • 规范
  • 我的笔记
  • Git
  • GitHub
  • VSCode
  • Mac工具
  • 数据库
  • Google
  • 服务器
  • Python爬虫
  • 前端教程
更多
收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Wang Mings

跟随大神,成为大神!
首页
  • 前端文章

    • HTML教程
    • CSS
    • JavaScript
  • 前端框架

    • Vue
    • React
    • VuePress
    • Electron
  • 后端技术

    • Npm
    • Node
    • TypeScript
  • 编程规范

    • 规范
  • 我的笔记
  • Git
  • GitHub
  • VSCode
  • Mac工具
  • 数据库
  • Google
  • 服务器
  • Python爬虫
  • 前端教程
更多
收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Python爬虫

    • 爬虫的基础知识

    • 请求的发送方法

    • 数据提取方法

    • 高性能爬虫

    • selenium

    • 反爬以及解决方案

    • MONGODB数据库

    • scrapy框架

    • scrapy_redis

    • 爬虫的部署

    • 爬虫框架开发分析

    • 框架雏形实现

    • 框架功能完善

    • 框架功能升级

      • 分布式爬虫设计原理及其实现
      • 增量爬虫设计原理及其实现
      • 断点续爬设计原理及其实现
        • 框架升级 -- 断点续爬设计原理及其实现
          • 学习目标
          • 1 断点续爬的原理
          • 2 实现关闭断点续爬的功能
          • 2.1 修改默认配置文件
          • 2.2 修改utils下stats_collector.py中的clear\(\)函数
          • 2.3 修改项目的settings.py文件
          • 3\. 请求丢失的情况
          • 3.1 产生请求丢失的场景
          • 3.2 不光我们的scrapy_plus会出现这个情况;scrapy框架同样如此
          • 3.3 解决请求丢失的方案(了解)
          • 小结
          • 本小结涉及修改的完整代码
    • 项目实战

    • pywin32介绍

  • 前端教程

  • 教程
  • Python爬虫
  • 框架功能升级
wangmings
2022-07-19
目录

断点续爬设计原理及其实现

# 框架升级 -- 断点续爬设计原理及其实现

# 学习目标
  1. 了解断点续爬的内涵
  2. 了解分布式爬虫中请求丢失的情况
  3. 了解使用备份队列保留请求的过程

# 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
...... 
1
2
3
4
5
6
7
8
# 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)
...... 
1
2
3
4
5
6
7
8
9
10
11
# 2.3 修改项目的settings.py文件
......
# 启用的爬虫类
SPIDERS = [
    'spiders.baidu.BaiduSpider',
    # 'spiders.baidu2.Baidu2Spider',
    # 'spiders.douban.DoubanSpider',
]
......

FP_PERSIST = False 
1
2
3
4
5
6
7
8
9
10

# 3. 请求丢失的情况

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

    • 添加一个请求备份容器 现有断点续爬方案的问题解决方案分析一

    • 给request对象设置一个重试次数的属性 现有断点续爬方案的问题解决方案分析二


# 小结

  1. 了解断点续爬的内涵
  2. 了解分布式爬虫中请求丢失的情况
  3. 了解使用备份队列保留请求的过程
  4. 完成代码重构

# 本小结涉及修改的完整代码

scrapy_plus/default_settings.py

import logging

# 默认的日志配置
DEFAULT_LOG_LEVEL = logging.INFO    # 默认等级
DEFAULT_LOG_FMT = '%(asctime)s %(filename)s[line:%(lineno)d] \
                  %(levelname)s: %(message)s'   # 默认日志格式
DEFUALT_LOG_DATEFMT = '%Y-%m-%d %H:%M:%S'  # 默认时间格式
DEFAULT_LOG_FILENAME = 'log.log'    # 默认日志文件名称

# 启用的默认管道类
PIPELINES = []

# 启用的默认爬虫中间件类
SPIDER_MIDDLEWARES = []

# 启用的默认下载器中间件类
DOWNLOADER_MIDDLEWARES = []

# 默认异步线程最大并发数,此参数可以在项目的settings.py中重新设置自动覆盖
MAX_ASYNC_THREAD_NUMBER = 5

# 异步并发的方式 thread or coroutine 线程 或 协程
# 可以在项目的settings.py中重新设置该值,自动覆盖
ASYNC_TYPE = 'thread' # 默认为线程的方式

# 设置调度器的内容是否要持久化
# 量个值:True和False
# 如果是True,那么就是使用分布式,同时也是基于请求的增量式爬虫
# 如果是False, 不使用redis队列,会使用python的set存储指纹和请求
SCHEDULER_PERSIST = False

# redis默认配置,默认为本机的redis
REDIS_SET_NAME = 'scrapy_plus_fp_set' # fp集合
REDIS_QUEUE_NAME = 'scrapy_plus_request_queue' # request队列
REDIS_HOST = '127.0.0.1'
REDIS_PORT = 6379
REDIS_DB = 0

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

scrapy_plus/utils/stats_collector.py

import redis
from scrapy_plus.conf.settings import REDIS_HOST, REDIS_PORT, REDIS_DB, FP_PERSIST, REDIS_SET_NAME

class NormalStatsCollector(object):
    def __init__(self, spider_names=[]):
        #存储请求数量的键
        self.request_nums_key = "_".join(spider_names) + "_request_nums"
        #存储响应数量的键
        self.response_nums_key = "_".join(spider_names) + "_response_nums"
        #存储重复请求的键
        self.repeat_request_nums_key = "_".join(spider_names) + "_repeat_request_nums"
        #存储start_request数量的键
        self.start_request_nums_key = "_".join(spider_names) + "_start_request_nums"

        #初始化收集数据的字典
        self.dict_collector = {
            self.request_nums_key :0,
            self.response_nums_key:0,
            self.repeat_request_nums_key:0,
            self.start_request_nums_key:0
        }

    def incr(self, key):
        self.dict_collector[key] +=1

    def get(self, key):
        return self.dict_collector[key]

    def clear(self):
        del self.dict_collector

    @property
    def request_nums(self):
        '''获取请求数量'''
        return self.get(self.request_nums_key)

    @property
    def response_nums(self):
        '''获取响应数量'''
        return self.get(self.response_nums_key)

    @property
    def repeat_request_nums(self):
        '''获取重复请求数量'''
        return self.get(self.repeat_request_nums_key)

    @property
    def start_request_nums(self):
        '''获取start_request数量'''
        return self.get(self.start_request_nums_key)

class StatsCollector(object):
    """对各种数量的统计,包括总的请求数量,总的响应数量,总的重复数量"""
    def __init__(self, spider_names=[], host=REDIS_HOST,
                 port=REDIS_PORT, db=REDIS_DB, password=None):

        self.redis = redis.StrictRedis(host=host, port=port, db=db, password=password)
        #存储请求数量的键
        self.request_nums_key = "_".join(spider_names) + "_request_nums"
        #存储响应数量的键
        self.response_nums_key = "_".join(spider_names) + "_response_nums"
        #存储重复请求的键
        self.repeat_request_nums_key = "_".join(spider_names) + "_repeat_request_nums"
        #存储start_request数量的键
        self.start_request_nums_key = "_".join(spider_names) + "_start_request_nums"

    def incr(self, key):
        '''给键对应的值增加1,不存在会自动创建,并且值为1,'''
        self.redis.incr(key)

    def get(self, key):
        '''获取键对应的值,不存在是为0,存在则获取并转化为int类型'''
        ret = self.redis.get(key)
        if not ret:
            ret = 0
        else:
            ret = int(ret)
        return ret

    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)

    @property
    def request_nums(self):
        '''获取请求数量'''
        return self.get(self.request_nums_key)

    @property
    def response_nums(self):
        '''获取响应数量'''
        return self.get(self.response_nums_key)

    @property
    def repeat_request_nums(self):
        '''获取重复请求数量'''
        return self.get(self.repeat_request_nums_key)

    @property
    def start_request_nums(self):
        '''获取start_request数量'''
        return self.get(self.start_request_nums_key) 
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106

项目路径/settings.py

# 修改默认日志文件名称
DEFAULT_LOG_FILENAME = '日志.log'    # 默认日志文件名称

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

# 启用的管道类
PIPELINES = [
    'pipelines.BaiduPipeline',
    'pipelines.DoubanPipeline'
]

# 启用的爬虫中间件类
SPIDER_MIDDLEWARES = []

# 启用的下载器中间件类
DOWNLOADER_MIDDLEWARES = []

SCHEDULER_PERSIST = True

ASYNC_TYPE = 'coroutine' # 默认为线程的方式

FP_PERSIST = False 
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
编辑 (opens new window)
增量爬虫设计原理及其实现
scrapy_plus实现腾讯招聘爬虫

← 增量爬虫设计原理及其实现 scrapy_plus实现腾讯招聘爬虫→

最近更新
01
theme-vdoing-blog博客静态编译问题
09-16
02
搜索引擎
07-19
03
友情链接
07-19
更多文章>
Theme by Vdoing | Copyright © 2019-2022 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式