Scrapy-Redis分布式架构 - 构建高性能分布式爬虫集群

📂 所属阶段:第五阶段 — 战力升级(分布式与进阶篇)
🔗 相关章节:DownloaderMiddleware · Spider中间件深度定制 · 分布式去重与调度


概述与架构

Scrapy-Redis是基于Redis的分布式扩展库,通过共享请求队列Redis指纹去重集合,让多台/多个Scrapy实例协同工作,突破单机硬件、带宽瓶颈。

核心架构流程

graph TB
    A[初始URL<br/>Redis键] --> B{Redis集群}
    subgraph B
        Q[共享请求队列<br/>支持优先级/FIFO/LIFO]
        DF[共享去重集合<br/>RFPDupeFilter]
    end
    Q --> C1[爬虫节点1]
    Q --> C2[爬虫节点2]
    Q --> C3[爬虫节点N]
    C1/C2/C3 --> DF
    C1/C2/C3 -->|新URL入队| Q

快速上手指南

1. 安装依赖

pip install scrapy-redis redis

2. 修改Scrapy配置

核心只需替换三个模块,其他按需调整:

# settings.py
import os

# 1. 启用Redis调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"

# 2. 启用Redis指纹去重
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

# 3. 配置Redis连接(支持哨兵/集群)
REDIS_URL = os.getenv('REDIS_URL', 'redis://localhost:6379/0')
# 如需单独配置密码/DB
# REDIS_HOST = 'localhost'
# REDIS_PORT = 6379
# REDIS_PASSWORD = 'your_pwd'
# REDIS_DB = 0

# 4. 可选但常用的持久化/队列配置
SCHEDULER_PERSIST = True  # 爬虫关闭后保留队列(支持断点续爬)
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue'  # 默认优先级队列

3. 改造Spider

使用 RedisSpiderRedisCrawlSpider 替代原基类,移除 start_urlsallowed_domains(可选通过环境变量或后续补充,但初始URL必须从Redis键推入):

# spiders/distributed_spider.py
from scrapy_redis.spiders import RedisSpider
from scrapy.http import Request
import time

class DemoDistributedSpider(RedisSpider):
    name = 'demo_distributed'
    redis_key = f'{name}:start_urls'  # 共享的初始URL队列键

    custom_settings = {
        'CONCURRENT_REQUESTS': 32,  # 单节点并发,按需调整
        'DOWNLOAD_DELAY': 1,
    }

    def parse(self, response):
        # 1. 提取数据
        yield {
            'url': response.url,
            'title': response.css('title::text').get(),
            'node_id': self._get_node_id(),
            'timestamp': time.time()
        }

        # 2. 提取新URL入队(自动去重)
        for link in response.css('a::attr(href)').getall()[:10]:  # 测试取10条
            absolute_url = response.urljoin(link)
            yield Request(url=absolute_url, callback=self.parse)

    def _get_node_id(self):
        import socket
        return socket.gethostname()

4. 启动集群

步骤1:启动Redis服务

确保所有爬虫节点能访问到Redis(如果是生产环境,建议用哨兵模式/集群模式避免单点故障)。

步骤2:向Redis推入初始URL

# 用命令行推入
redis-cli lpush demo_distributed:start_urls https://example.com

步骤3:启动多台/多个爬虫节点

每个节点执行:

scrapy crawl demo_distributed

核心机制极简解析

1. 共享请求队列

Scrapy-Redis支持三种队列,通过 SCHEDULER_QUEUE_CLASS 切换:

队列类型实现原理适用场景
PriorityQueue(默认)Redis ZSet(score=-优先级)需要优先处理重要URL(如首页、API接口)
FifoQueueRedis List(LPUSH+BRPOP)普通顺序爬取
LifoQueueRedis List(LPUSH+LPOP)快速遍历/深度优先爬取(注意不要无限递归)

2. 共享指纹去重

Scrapy-Redis默认用 RFPDupeFilter,基于Redis Set存储URL指纹,指纹生成逻辑如下(可自定义):

# 简化版默认指纹生成逻辑
import hashlib
def default_fingerprint(request):
    fp = hashlib.sha1()
    fp.update(request.method.encode('utf-8'))
    fp.update(request.url.encode('utf-8'))
    fp.update(request.body or b'')
    return fp.hexdigest()

3. 负载均衡

Scrapy-Redis的负载均衡是被动式的:

  • 所有节点从同一个Redis队列 BRPOP(阻塞式获取)请求
  • 请求优先被空闲/响应快的节点获取(自动实现负载)
  • 如需更精细的节点调度(如按域名分片),需自定义调度器或节点路由

部署与运维要点

1. Redis生产环境配置

# settings.py 中的REDIS_PARAMS
REDIS_PARAMS = {
    'socket_timeout': 30,
    'socket_connect_timeout': 30,
    'retry_on_timeout': True,
    'encoding': 'utf-8',
    'health_check_interval': 30,  # 定期健康检查避免死连接
    'max_connections': 20,  # 每个节点的Redis连接池大小
    # 哨兵模式配置示例
    # 'sentinel': [('sentinel1', 26379), ('sentinel2', 26379)],
    # 'sentinel_service_name': 'mymaster',
    # 'sentinel_password': 'sentinel_pwd',
}

2. 断点续爬与清空队列

  • 断点续爬:确保 SCHEDULER_PERSIST = True,爬虫关闭后重启即可从上次中断的地方继续
  • 清空队列:如果需要重新爬取,需手动清空Redis相关键:
    redis-cli
    > DEL demo_distributed:requests  # 清空请求队列
    > DEL demo_distributed:dupefilter  # 清空去重集合
    > DEL demo_distributed:start_urls  # (可选)清空初始URL

最佳实践与避坑

1. 最佳实践

  • 限制单节点并发:避免因过载被目标网站封禁
  • 启用Redis持久化:RDB+AOF混合模式,防止数据丢失
  • 按域名限制并发:Scrapy原生支持 CONCURRENT_REQUESTS_PER_DOMAIN/CONCURRENT_REQUESTS_PER_IP
  • 监控Redis内存:设置合理的 maxmemorymaxmemory-policy(建议 allkeys-lru

2. 常见避坑

  • 不要重复设置start_urls:RedisSpider会忽略start_urls,初始URL必须从Redis键推入
  • 不要在Spider中硬编码Redis键:用 name 变量构造,避免不同爬虫冲突
  • 不要忽略Redis连接错误:设置合理的重试机制和监控
  • 不要使用过大的请求队列:定期检查队列长度,避免Redis内存溢出

通过这些步骤,你可以快速搭建一个稳定、可扩展的分布式爬虫集群。如果需要更复杂的功能(如节点监控、自动扩缩容、自定义去重),可以在这个基础上进行扩展。