Scrapy-Redis 分布式爬虫实战——从单机到集群

上一篇用 Scrapy 实现了单机爬虫,能爬几千页没问题。但当数据量达到几十万、上百万页时,单机就有瓶颈了——爬取速度不够、IP 容易被封、爬了一半挂了还要重新来。

用 Scrapy-Redis 做分布式,几台机器一起爬,速度翻倍,还自带断点续爬。

一、分布式爬虫的原理

单机 Scrapy 的请求队列在内存里,A 机器爬过的 B 机器不知道。分布式就是把请求队列放到 Redis 中,所有机器共享。

单机模式:
  爬虫A → 内存队列 → 下载 → 解析
  爬虫B → 内存队列 → 下载 → 解析
  (各爬各的,互不通信)

分布式模式:
  Redis 请求队列(所有爬虫共享)
   ↑↓         ↑↓         ↑↓
  爬虫A      爬虫B      爬虫C
  (请求被分配,不会重复爬)

核心优势:

  • 爬取速度 = 机器数 × 单机速度(近似)
  • 天然去重:Redis 集合保证同一个 URL 不会被爬两次
  • 断点续爬:Redis 里的队列不会丢失,重启爬虫接着爬

二、安装与配置

1. 安装

pip install scrapy-redis

确保 Redis 已经安装并启动。

2. 配置 settings.py

# 使用 Scrapy-Redis 的调度器(替代默认的内存调度器)
SCHEDULER = "scrapy_redis.scheduler.Scheduler"

# 使用 Redis 去重组件(替代默认的内存去重)
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

# Redis 连接配置
REDIS_HOST = "localhost"    # Redis 服务器地址
REDIS_PORT = 6379
REDIS_PARAMS = {
    "password": "",         # Redis 密码
    "db": 0,
}

# 持久化请求队列(爬虫停了以后不清空)
SCHEDULER_PERSIST = True

# 爬虫停止后是否清空去重集合(建议 True,避免内存堆积)
SCHEDULER_FLUSH_ON_START = False

# 可选:从 Redis 中读取起始 URL
REDIS_START_URLS_AS_SET = True

3. 修改爬虫代码

# spiders/quotes.py
from scrapy_redis.spiders import RedisSpider

# 原来继承 scrapy.Spider,现在继承 RedisSpider
class QuotesSpider(RedisSpider):
    name = "quotes"

    # 不再写 start_urls,而是从 Redis 读取
    redis_key = "quotes:start_urls"

    def parse(self, response):
        # 解析逻辑和单机时完全一样
        for quote in response.css("div.quote"):
            yield {
                "text": quote.css("span.text::text").get(),
                "author": quote.css("small.author::text").get(),
            }

        # 翻页
        next_page = response.css("li.next a::attr(href)").get()
        if next_page:
            yield response.follow(next_page, callback=self.parse)

关键区别: 继承从 scrapy.Spider 改为 scrapy_redis.spiders.RedisSpider,不再写 start_urls,改为 redis_key

三、启动分布式爬虫

第一步:启动爬虫(所有机器都执行)

# 两台机器上都执行,它们都会去连同一个 Redis
scrapy crawl quotes

此时爬虫启动后会等待——因为 Redis 里还没有起始 URL。

第二步:向 Redis 推送起始 URL

在任意一台机器上执行:

# 向 Redis 的 quotes:start_urls 集合中插入起始 URL
redis-cli lpush quotes:start_urls "https://quotes.toscrape.com"

推入后,所有等待中的爬虫会立刻开始爬取,自动分配请求,不会重复。

第三步:随时追加新任务

redis-cli lpush quotes:start_urls "https://quotes.toscrape.com/page/2/"

新 URL 会被所有爬虫共享,继续爬取。

四、Redis 中的数据查看

# 查看等待爬取的请求数量
redis-cli llen quotes:requests

# 查看已爬取的 URL 数量(去重集合)
redis-cli scard quotes:dupefilter

# 查看爬取结果(如果配置了 ITEM_PIPELINES)
redis-cli llen quotes:items

核心 Redis key 说明:

Key类型说明
爬虫名:requestsList/ZSet待爬取的请求队列
爬虫名:dupefilterSet已爬取的 URL(去重用)
爬虫名:itemsList爬取到的数据(如果启用了 RedisPipeline)
爬虫名:start_urlsList/Set起始 URL 入口

五、数据存储——分布式写入

数据量大了以后,每台机器各自存一份 JSON 显然不行。统一存到 MongoDB 或 MySQL。

# pipelines.py
import pymongo

class MongoPipeline:
    def open_spider(self, spider):
        # 所有爬虫都连同一个 MongoDB
        self.client = pymongo.MongoClient("localhost", 27017)
        self.db = self.client["spider_data"]

    def close_spider(self, spider):
        self.client.close()

    def process_item(self, item, spider):
        # 插入到同一张表
        self.db["quotes"].insert_one(dict(item))
        return item
# settings.py
ITEM_PIPELINES = {
    # 注释掉默认的 JsonPipeline,换成 MongoPipeline
    "myspider.pipelines.MongoPipeline": 300,
}

关键: 所有爬虫连接同一个数据库,数据自动汇总。

六、实战:分布式爬取豆瓣电影 Top250

from scrapy_redis.spiders import RedisSpider

class DoubanSpider(RedisSpider):
    name = "douban"
    redis_key = "douban:start_urls"

    def parse(self, response):
        # 提取电影列表
        for movie in response.css("div.item"):
            yield {
                "title": movie.css("span.title::text").get(),
                "rating": movie.css("span.rating_num::text").get(),
                "quote": movie.css("span.inq::text").get(),
                "url": movie.css("a::attr(href)").get(),
            }

        # 翻页
        next_page = response.css("span.next a::attr(href)").get()
        if next_page:
            yield response.follow(next_page, callback=self.parse)

启动:

# 机器A
scrapy crawl douban

# 机器B
scrapy crawl douban

# 推送起始 URL
redis-cli lpush douban:start_urls "https://movie.douban.com/top250"

两台机器同时爬,一台爬第 1 页、第 3 页、第 5 页……另一台爬第 2 页、第 4 页、第 6 页……谁也抢不到谁的任务。

七、分布式爬虫注意事项

1. IP 被封问题

多台机器用同一个 IP 访问同一个网站,被封的概率也翻倍了。必须配代理:

# middlewares.py
class ProxyMiddleware:
    def process_request(self, request, spider):
        # 从代理池获取一个代理
        request.meta["proxy"] = "http://代理IP:端口"

建议每台机器配不同的代理 IP 池。

2. 爬取速度控制

# settings.py
# 每台机器各自限速,总的爬取速度会累加
DOWNLOAD_DELAY = 1.0           # 单机间隔 1 秒
CONCURRENT_REQUESTS = 8        # 单机并发 8
# 3 台机器总速度 ≈ 3 × 8/1 = 24请求/秒

3. 去重机制

Scrapy-Redis 的去重是基于 URL 的,如果两个不同的 URL 返回相同的内容,不会被去重。如果要基于内容去重,需要自定义 DupeFilter。

4. 爬虫挂了怎么办

# settings.py
# 调度器持久化 = 爬虫停了队列还在 Redis 中
SCHEDULER_PERSIST = True

# 重新启动爬虫
scrapy crawl douban
# 它会继续消费 Redis 中剩余的请求,不会从头开始

八、什么时候需要分布式?

数据量推荐方案原因
< 1万页单机 Scrapy几十分钟的事,没必要分布式
1万~10万页单机 Scrapy + 优化配置加大并发、减小延迟就能搞定
10万~100万页分布式 Scrapy-Redis单机需要跑几天,扛不住
> 100万页分布式 + 消息队列需要更健壮的架构

总结

Scrapy-Redis 只是把请求队列从内存搬到 Redis,代码改动很小——改继承、删 start_urls、加三行配置,单机就变分布式了。

单机爬虫 → 加 scrapy-redis 配置 → 多台机器启动 → push 起始 URL → 分布式跑起来

建议先在一台机器上把爬虫调好,再部署到多台机器上。不要还没调通就上分布式,排查问题会多一倍的复杂度。


💡 觉得有用的话,点赞 + 关注【张老师技术栈】吧!每周更新 Java/Python/爬虫 实战干货,不让你白来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值