scrapy-redis 中 RedisSpider 的正确使用方式

核心结论: 继承 RedisSpider 后,不能重写 start_requests(),要重写 make_requests_from_url()

scrapy-redis 的使用场景

scrapy-redis 的核心使用方式是:

外部脚本持续发现新 URL → push 到 Redis Key
            ↓
爬虫从 Redis 里取 URL 去爬(多台机器同时消费)

起始 URL 不是爬虫自己生成的,而是外部动态投递进来的。


两种爬虫的本质区别

常规 Scrapyscrapy-redis
任务来源代码里写死外部动态 push 到 Redis
任务数量明确知道不确定,随时可能有新任务
工作模式批处理,像跑脚本,有头有尾常驻服务,像 worker 进程,随时待命
爬完之后进程退出继续等待新任务

为什么不能重写 start_requests()?

父类 RedisSpider 已经重写了 start_requests(),内部逻辑大致如下:

# 父类内部简化逻辑
def start_requests(self):
    while True:
        urls = self.next_requests()  # 从 Redis 里取 URL
        if urls:
            yield from urls
        else:
            time.sleep(self.waiting_time)  # 没数据就等待,继续监听

这是一个持续阻塞监听 Redis 的循环,保证爬虫进程一直活着。


如果你重写了 start_requests()

# ❌ 错误做法
def start_requests(self):
    yield scrapy.Request("https://example.com/page1")
    yield scrapy.Request("https://example.com/page2")

后果:

  • ✅ 能发请求(所以看起来"没问题")
  • ❌ URL 写死在代码里,不从 Redis 取
  • ❌ 爬完这几个 URL 就退出,无法接收后续任务
  • ❌ 多台机器部署时,每台都爬同样的硬编码 URL,完全没有分工
  • ❌ 本质上把 scrapy-redis 退化成了普通 Scrapy

正确做法:重写 make_requests_from_url()

完整数据流

外部脚本 lpush URL 到 Redis Key(如 my_spider:start_urls)
        ↓
父类 start_requests() 持续监听
调用 next_requests() 用 rpop 从列表取出 URL
        ↓
把 URL 传入 make_requests_from_url(url)  ← 你重写这里,负责构造请求
        ↓
打包成 Request 对象(可定制 headers、callback、meta 等)
        ↓
Request 对象交给 Redis Scheduler
        ↓
dont_filter=False → 走指纹去重(dupefilter),指纹写入 Redis Set
        ↓
去重通过 → 放入 Redis 队列等待下载
        ↓
下载 → parse() → 提取新 URL → 继续入队
        ↓
队列空了也不退出,继续监听 Redis Key 等待新任务

注意: make_requests_from_url() 只负责构造请求,取 URL 的动作由父类的 next_requests() 完成,两者职责不同。

make_requests_from_url() 是父类监听逻辑内部的钩子,重写它只是定制"拿到 URL 后怎么构造请求",不破坏整个监听机制。

# ✅ 正确做法
from scrapy_redis.spiders import RedisSpider
import scrapy

class MySpider(RedisSpider):
    name = "my_spider"
    redis_key = "my_spider:start_urls"  # 监听的 Redis Key

    def make_requests_from_url(self, url):
        return scrapy.Request(
            url,
            headers={"User-Agent": "Mozilla/5.0"},
            callback=self.parse,
            dont_filter=False  # 开启去重
        )

    def parse(self, response):
        ...

监听的工作流程

Redis Key: my_spider:start_urls
[url1, url2, url3]  ← 初始有3条

爬虫A 取走 url1
爬虫B 取走 url2
爬虫C 取走 url3

队列空了... 父类继续等待,进程不退出

10分钟后,外部脚本又 push 了 url4、url5
爬虫继续取任务,继续爬

Redis 中的三个核心 Key

scrapy-redis 运行时,Redis 里至少存在以下三个 Key:

Key数据结构作用写入时机读取时机
my_spider:start_urlsList存储外部投递的起始 URL外部脚本 lpush爬虫 rpop 取任务
my_spider:dupefilterSet存储请求指纹,用于去重每次有新请求时写入指纹判断请求是否已爬过
my_spider:requestsZSet存储序列化后的 Request 对象去重通过后入队Scheduler 取出交给下载器

三个 Key 的职责分工:

外部 lpush → start_urls(List)
                    ↓
              rpop 取出 URL,构造 Request
                    ↓
              写指纹 → dupefilter(Set)判断是否重复
                    ↓
              去重通过 → requests(ZSet)等待调度
                    ↓
              Scheduler 取出 → 下载器

总结

不清楚有多少 URL 要爬,也不清楚什么时候新的 URL 会进来,所以要一直监听。
常规爬虫明确知道有多少 URL,爬完就结束,不需要监听。
  • 重写 start_requests() → 能跑,但丢失了"外部投递任务 + 持续监听 + 多机分工"三个核心能力
  • 重写 make_requests_from_url() → 在保留完整机制的前提下,定制请求构造逻辑

标签: none

添加新评论