程序员scholar 程序员scholar
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Python 基础

    • Python基础
  • Python 进阶

    • 装饰器与生成器
    • 异常处理
    • 标准库精讲
    • 模块与包
    • pip包管理工具
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
前端 (opens new window)
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Python 基础

    • Python基础
  • Python 进阶

    • 装饰器与生成器
    • 异常处理
    • 标准库精讲
    • 模块与包
    • pip包管理工具
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
前端 (opens new window)
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
npm

(进入注册为作者充电)

  • Python 基础

  • Python 进阶

  • Python爬虫

  • Scrapy 爬虫框架

    • Scrapy 框架核心:深入理解其工作流程
    • Scrapy 入门实战:从零到一构建你的第一个爬虫
    • Scrapy核心对象:Response超详细指南
    • Scrapy 核心配置与调试技巧
    • Scrapy Items与Pipeline数据管道
    • Scrapy 图片与文件下载
    • Scrapy 模拟登录与Cookie处理
    • Scrapy CrawlSpider全站爬取
    • Scrapy 中间件:请求与响应的强大控制器
    • Scrapy-Redis:从单机到分布式集群
      • 一、为什么需要分布式爬虫?
      • 二、Scrapy-Redis 核心原理
        • 2.1 组件改造
        • 2.2 分布式流程图
      • 三、分布式项目搭建完整教程
        • 3.1 环境准备
        • 3.2 创建项目与爬虫
        • 3.3 关键配置 (settings.py)
        • 3.4 爬虫改造 (fbs.py)
        • 3.5 部署与启动
        • 3.6 监控与调试
      • 四、消费 Redis 中的数据
      • 五、总结与常见问题
  • Python
  • Scrapy 爬虫框架
scholar
2025-07-28
目录

Scrapy-Redis:从单机到分布式集群

# Scrapy-Redis:从单机到分布式集群

当你的爬取目标数据量巨大、网站反爬策略严格(如 IP 封锁频繁)、或者单台机器的处理能力达到瓶颈时,单机的 Scrapy 爬虫便会显得力不从心。此时,将爬虫从单机扩展为分布式集群,协同作业,就成了必然选择。

scrapy-redis 正是为此而生的强大组件,它将 Scrapy 无缝地与 Redis 数据库结合,将一个普通的 Scrapy 项目改造为可以由多台机器共同执行的分布式爬虫系统。

# 一、为什么需要分布式爬虫?

原生 Scrapy 框架是一个高效的单机爬虫框架,但它的核心组件都是基于内存的,这导致了其天然的局限性:

  1. 调度器 (Scheduler):请求队列存在于单台机器的内存中,无法被其他机器访问。
  2. 去重过滤器 (DupeFilter):URL 指纹集合也存在于内存中,每台机器都有自己的去重集合,无法做到全局去重。
  3. 数据管道 (Pipeline):Item 数据被直接处理或存储在本地,无法集中管理。

这些限制意味着,如果你在多台机器上运行同一个 Scrapy 项目,它们将是完全独立、互不相干的,会产生大量的重复抓取,无法协同完成一个大的爬取任务。

# 二、Scrapy-Redis 核心原理

scrapy-redis 的核心思想非常巧妙:它替换掉了 Scrapy 原生的核心组件,将它们的功能从基于内存实现转为基于 Redis 实现。Redis 是一个高性能的内存数据库,可以被多台机器共享访问,从而完美解决了分布式的问题。

# 2.1 组件改造

  • 调度器 Scheduler -> 基于 Redis list 或 zset 的调度器

    • scrapy-redis 将待抓取的 Request 对象序列化后存入 Redis 的一个 list 中(通常以爬虫的 redis_key 命名)。
    • 所有爬虫客户端都从这个共享的 list 中获取任务(lpop 或 brpop),实现了任务的统一分配。
  • 去重过滤器 DupeFilter -> 基于 Redis set 的去重过滤器

    • 所有 Request 的指纹(fingerprint)都被存入 Redis 的一个 set 中。
    • 在调度请求前,会先检查该请求的指纹是否存在于这个共享的 set 中(sismember),从而实现了全局去重。
  • 数据管道 RedisPipeline -> 将 Item 存入 Redis

    • scrapy-redis 提供了一个 RedisPipeline,它会自动将爬虫抓取到的 Item 序列化为 JSON 字符串,并存入 Redis 的一个 list 中(key 通常为 spider.name:items)。
    • 这样,数据的产生(爬虫)和数据的消费(数据处理程序)就实现了解耦。你可以编写独立的脚本从 Redis 中读取并处理数据。

# 2.2 分布式流程图

graph TD
    subgraph "Master 节点 (可选, 可兼作 Slave)"
        A[推送初始 URL 到 Redis] --> B{Redis 数据库};
    end

    subgraph "Slave 节点 1 (爬虫客户端)"
        C1[爬虫 1] -->|获取 Request| B;
        B -->|URL 指纹| D1[全局去重 DupeFilter];
        D1 -->|判断是否存在| B;
        B -->|返回 Request| C1;
        C1 -->|下载页面| E1[互联网];
        E1 -->|Response| C1;
        C1 -->|Item| F1[RedisPipeline];
        F1 -->|存储 Item| B;
    end
    
    subgraph "Slave 节点 2 (爬虫客户端)"
        C2[爬虫 2] -->|获取 Request| B;
        B -->|URL 指纹| D2[全局去重 DupeFilter];
        D2 -->|判断是否存在| B;
        B -->|返回 Request| C2;
        C2 -->|下载页面| E2[互联网];
        E2 -->|Response| C2;
        C2 -->|Item| F2[RedisPipeline];
        F2 -->|存储 Item| B;
    end

    subgraph "数据消费端"
        B --> G[独立的数据处理程序];
        G --> H[数据库/文件/其他];
    end
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

# 三、分布式项目搭建完整教程

下面,我们将以抓取“阳光问政”网站为例,从零开始搭建一个完整的分布式爬虫项目。

# 3.1 环境准备

  1. 安装 scrapy-redis
    pip install scrapy-redis
    
    1
  2. 安装并启动 Redis
    • 确保你的 Redis 服务已经启动。
    • 为了让其他机器能够连接,需要修改 redis.conf 文件:
      • 注释掉 bind 127.0.0.1 这一行,允许所有 IP 连接。
      • 将 protected-mode 设置为 no。
      • 如果需要,设置密码 requirepass your_password。

# 3.2 创建项目与爬虫

# 创建一个标准的 Scrapy 项目
scrapy startproject fbsPro
cd fbsPro

# 使用 crawlspider 模板创建一个爬虫
scrapy genspider -t crawl fbs wz.sun0769.com
1
2
3
4
5
6

# 3.3 关键配置 (settings.py)

这是将普通 Scrapy 项目改造为 scrapy-redis 项目最核心的一步。

# settings.py

# 1. (必须) 指定使用 scrapy-redis 的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"

# 2. (必须) 指定使用 scrapy-redis 的去重过滤器
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

# 3. (必须) 指定 redis 的主机和端口
REDIS_HOST = "127.0.0.1"  # Redis 服务器的 IP 地址
REDIS_PORT = 6379         # Redis 服务的端口
# 如果有密码,需要配置
# REDIS_PARAMS = {'password': 'your_password'}

# 4. (必须) 配置 Item Pipeline
ITEM_PIPELINES = {
    # 将 RedisPipeline 的优先级设得低一些,以便其他 Pipeline 先处理
   'scrapy_redis.pipelines.RedisPipeline': 400,
   # 如果你有自己的数据清洗 Pipeline,可以放在 RedisPipeline 之前
   # 'fbsPro.pipelines.FbsProPipeline': 300,
}

# 5. (可选) 设置 SCHEDULER_PERSIST = True,爬虫关闭时,保留 Redis 中的请求队列和去重集合,方便暂停和恢复。
SCHEDULER_PERSIST = True

# 6. (可选) 设置请求队列的类型
# SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.PriorityQueue"  # 优先级队列
# SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.FifoQueue"      # 先进先出队列
# SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.LifoQueue"      # 后进先出队列(默认)

# 7. (可选) 设置将起始 URL 存储在 Redis 的 set 集合中,允许多个爬虫共享起始URL
# REDIS_START_URLS_AS_SET = 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

# 3.4 爬虫改造 (fbs.py)

将爬虫的父类从 CrawlSpider 修改为 RedisCrawlSpider,并定义 redis_key。

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import Rule
# 1. 导入 RedisCrawlSpider
from scrapy_redis.spiders import RedisCrawlSpider

# 2. 继承自 RedisCrawlSpider
class FbsSpider(RedisCrawlSpider):
    name = 'fbs'
    allowed_domains = ['wz.sun0769.com']
    
    # 3. 定义 redis_key,爬虫将从这个 key 中读取起始 URL
    # 这个 key 就是一个 Redis 的 list
    redis_key = 'fbs:start_urls'
    
    # 注意:start_urls 属性在 RedisSpider 中将不再需要,因为起始 URL 是从 Redis 中读取的。
    # start_urls = ['http://wz.sun0769.com/']

    rules = (
        # 提取列表页的链接
        Rule(LinkExtractor(allow=r'type=4&page=\d+')),
        # 提取详情页的链接,并交给 parse_item 处理
        Rule(LinkExtractor(allow=r'/political/politics/view\?id=\d+'), callback='parse_item'),
    )

    def parse_item(self, response):
        item = {}
        # 提取标题
        item['title'] = response.xpath('//div[@class="focus-details-title-container"]/h2/text()').get()
        # 提取编号
        item['number'] = response.xpath('//div[@class="focus-details-title-container"]/p/span[last()]/text()').re_first(r'编号:(\d+)')
        # 提取内容
        content = response.xpath('//div[@class="details-content"]//text()').getall()
        item['content'] = "".join(content).strip()
        item['url'] = response.url
        
        yield item
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

# 3.5 部署与启动

现在,你的分布式爬虫已经准备就绪。

1. 启动爬虫客户端 (Slave 节点)

将配置好的 fbsPro 项目代码复制到所有你希望参与爬取的机器上(Slave 节点)。在每台机器上,进入项目目录并执行:

scrapy crawl fbs
1

此时,所有的爬虫客户端都会启动,但它们会阻塞住,因为 Redis 的 fbs:start_urls 队列中还没有任何任务。

2. 推送初始 URL (Master 节点)

选择任意一台安装了 Redis 客户端的机器(可以是爬虫节点,也可以是独立机器),连接到 Redis 服务器,并向 redis_key 指定的队列中推送一个或多个起始 URL。

# 使用 redis-cli
redis-cli

# 连接到 Redis (如果需要)
# auth your_password

# 推送起始 URL
lpush fbs:start_urls "https://wz.sun0769.com/political/index/politicsNewest?type=4&page=1"
1
2
3
4
5
6
7
8

一旦你推送了 URL,所有正在等待的爬虫客户端会立刻开始抢占任务,并开始分布式爬取。

# 3.6 监控与调试

在爬虫运行期间,你可以通过监控 Redis 中的关键 key 来了解爬虫的状态:

  • lrange fbs:start_urls 0 -1: 查看待抓取的起始 URL 队列。
  • llen fbs:start_urls: 查看队列中剩余的任务数。
  • scard fbs:dupefilter: 查看已经抓取过的 URL 指纹数量,可以判断去重是否正常。
  • llen fbs:items: 查看已经抓取到并存入 Redis 的 Item 数量。
  • lrange fbs:items 0 10: 查看最近抓取的 10 条数据,检查数据格式是否正确。

# 四、消费 Redis 中的数据

当爬虫将数据存入 Redis 后,你需要编写一个独立的 Python 脚本来消费这些数据。

# consume_data.py
import redis
import json

def main():
    # 连接到 Redis
    r = redis.Redis(host='127.0.0.1', port=6379)
    
    while True:
        # 从 'fbs:items' 队列中阻塞式地取出一个 item
        # 'brpop' 会在列表为空时阻塞,直到有新元素加入
        source, data = r.brpop(["fbs:items"])
        
        # 将取出的 JSON 字符串反序列化为 Python 字典
        item = json.loads(data)
        
        # 在这里进行你的数据处理,例如存入 MongoDB, MySQL 或写入文件
        print(f"消费到数据: {item['title']}")
        # 假设我们将其写入一个 JSON Lines 文件
        with open('wenzheng.jsonl', 'a', encoding='utf-8') as f:
            f.write(json.dumps(item, ensure_ascii=False) + '\n')

if __name__ == '__main__':
    main()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

你可以独立运行这个 consume_data.py 脚本,它会持续地从 Redis 中获取并处理数据。

# 五、总结与常见问题

  1. 核心改造:scrapy-redis 的核心是对 Scrapy 的调度器和去重过滤器进行了 Redis 化。
  2. 爬虫继承:分布式爬虫必须继承自 RedisSpider 或 RedisCrawlSpider。
  3. 启动方式:分布式爬虫不再依赖 start_urls,而是通过 redis-cli 向 redis_key 指定的队列推送 URL 来启动。
  4. 数据解耦:RedisPipeline 实现了爬虫(生产者)与数据处理(消费者)的解耦,增强了系统的健壮性和可扩展性。

FAQ:

  • 爬虫启动后没反应?
    • 检查 Redis 连接配置(IP, Port, Password)是否正确。
    • 检查是否已向正确的 redis_key 中推送了起始 URL。
    • 检查防火墙是否开放了 Redis 端口。
  • 出现大量重复抓取?
    • 确认 DUPEFILTER_CLASS 配置是否正确。
    • 检查所有爬虫客户端是否都连接到了同一个 Redis 实例和数据库。
  • 如何暂停和恢复爬虫?
    • 确保 SCHEDULER_PERSIST = True。
    • 暂停:直接用 Ctrl+C 停止所有爬虫进程。
    • 恢复:重新在所有节点上执行 scrapy crawl fbs 即可,它们会自动从上次中断的地方继续。
编辑此页 (opens new window)
上次更新: 2025/07/27, 04:30:11
Scrapy 中间件:请求与响应的强大控制器

← Scrapy 中间件:请求与响应的强大控制器

Theme by Vdoing | Copyright © 2019-2025 程序员scholar
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式