Scrapy-Redis Scheduler 队列模式详解

学习日期:2026-02-24
关键词:scrapy-redis、Scheduler、队列、优先级、BFS、DFS

一、三种队列模式概览

scrapy-redis 的 Scheduler 支持三种队列模式,底层分别对应不同的 Redis 数据结构:

队列类型Redis 结构类路径默认?
优先级队列Sorted Set (ZSET)scrapy_redis.queue.PriorityQueue✅ 是
先进先出(FIFO)List(LPUSH/RPOP)scrapy_redis.queue.FifoQueue
先进后出(LIFO/栈)List(LPUSH/LPOP)scrapy_redis.queue.LifoQueue

二、各队列特点与使用场景

1. 优先级队列(PriorityQueue)— 默认

特点: 基于 Redis ZADD/ZPOPMIN,以 Request 的 priority 字段作为排序依据,priority 越大越先处理。

适用场景:

  • 需要对不同类型 URL 区分优先级,例如详情页优先于列表页
  • 电商爬虫中促销页面优先于普通分类页
  • 已发现大量 URL,但希望优先处理某类重要内容

注意: ZADD 时间复杂度为 O(log N),数据量极大时性能略低于纯 List 操作。


2. 先进先出(FifoQueue)— BFS 广度优先

特点: 按照 URL 发现顺序依次处理,类似广度优先搜索(BFS)。

适用场景:

  • 按层级结构抓取网站,确保同一深度的页面先被处理
  • 爬取新闻网站,希望先处理最早发现的链接(时序公平)
  • 需要均匀覆盖整个站点,而不是扎进某个子树

3. 先进后出(LifoQueue)— DFS 深度优先

特点: 最新入队的请求先被处理,类似深度优先搜索(DFS)。

适用场景:

  • 需要快速抓取完整的单条内容链路,例如一个帖子的所有楼层/评论
  • 希望集中处理某个子域,减少跨站跳跃,对 session/cookie 更友好
  • 先把一条路径走完再处理下一条

三、如何配置队列模式

settings.py 中指定:

SCHEDULER = 'scrapy_redis.scheduler.Scheduler'
DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'

# 三选一,默认为 PriorityQueue
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue'
# SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.FifoQueue'
# SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.LifoQueue'

REDIS_URL = 'redis://localhost:6379'

四、优先级队列的原理与使用

4.1 底层实现原理

yield Request(priority=10)
        ↓
Scheduler.enqueue_request()
        ↓
PriorityQueue.push()
        ↓
Redis: ZADD key  score=-10  value=序列化的Request
(存入时对 priority 取负数作为 score)
        ↓
取出时: ZPOPMIN → 取 score 最小值 = priority 最大的请求
关键点: scrapy-redis 在存入 ZSET 时自动将 priority 取负数作为 score,因此你只需记住:priority 正数越大 = 越先被处理,框架自动处理转换。

4.2 设置 Request 优先级

在 Spider 中,通过 scrapy.Requestpriority 参数指定:

# 高优先级
yield scrapy.Request(url, callback=self.parse_detail, priority=10)

# 中优先级
yield scrapy.Request(url, callback=self.parse_category, priority=5)

# 低优先级
yield scrapy.Request(url, callback=self.parse, priority=1)

4.3 完整示例:电商爬虫按优先级抓取

# spiders/shop_spider.py
import scrapy
from scrapy_redis.spiders import RedisSpider

class ShopSpider(RedisSpider):
    name = 'shop'
    redis_key = 'shop:start_urls'

    def parse(self, response):
        # 商品详情页 → 最高优先级(最想要的数据)
        for product_url in response.css('a.product-link::attr(href)').getall():
            yield scrapy.Request(
                url=response.urljoin(product_url),
                callback=self.parse_product,
                priority=10
            )

        # 分类列表页 → 中等优先级
        for category_url in response.css('a.category::attr(href)').getall():
            yield scrapy.Request(
                url=response.urljoin(category_url),
                callback=self.parse_category,
                priority=5
            )

        # 翻页链接 → 低优先级
        next_page = response.css('a.next-page::attr(href)').get()
        if next_page:
            yield scrapy.Request(
                url=response.urljoin(next_page),
                callback=self.parse,
                priority=1
            )

    def parse_product(self, response):
        yield {
            'title': response.css('h1::text').get(),
            'price': response.css('.price::text').get(),
        }

4.4 在 Redis 中验证优先级

# 查看队列中所有请求及其 score(score 越小 = 优先级越高)
ZRANGE shop:requests 0 -1 WITHSCORES

# 预期结果示例:
# 序列化的Request(详情页)   score: -10
# 序列化的Request(分类页)   score: -5
# 序列化的Request(翻页)     score: -1

五、选择建议

需要控制不同类型 URL 的抓取顺序   →  PriorityQueue(默认,推荐)
追求广度覆盖 / 按层级均匀抓取     →  FifoQueue
追求深度 / 快速完成某条内容链路   →  LifoQueue

大多数项目用默认的 PriorityQueue 配合 priority 参数即可满足需求,只有在明确需要 BFS/DFS 语义时才切换其他两种模式。

标签: none

添加新评论