bigywater.tech
Scrapy · 8

调度器:内存队列、磁盘队列与优先级

请求在调度器里排成什么队、先出哪个?这篇把内存队列、磁盘队列和优先级队列一层层拆开:priority 怎么排序、同优先级怎么出、JOBDIR 里装不下的请求去哪了,以及 2.19 默认换成的 DownloaderAwarePriorityQueue 对优先级的影响。

21 → 0
设了 JOBDIR 之后 21 个请求中进内存队列的个数(全部进磁盘)
B2 A2 B1 A1…
默认优先级队列在 A(0)、B(100) 两个域名间的出队顺序(轮流)
0 条
不可序列化的请求在默认日志里的提示(SCHEDULER_DEBUG=1 才有)

默认配置

实测 · 本机执行输出
  SCHEDULER                      = scrapy.core.scheduler.Scheduler
  SCHEDULER_PRIORITY_QUEUE       = scrapy.pqueues.DownloaderAwarePriorityQueue
  SCHEDULER_MEMORY_QUEUE         = scrapy.squeues.LifoMemoryQueue
  SCHEDULER_DISK_QUEUE           = scrapy.squeues.PickleLifoDiskQueue
  SCHEDULER_START_MEMORY_QUEUE   = scrapy.squeues.FifoMemoryQueue
  SCHEDULER_START_DISK_QUEUE     = scrapy.squeues.PickleFifoDiskQueue
  DUPEFILTER_CLASS               = scrapy.dupefilters.RFPDupeFilter
  不设 JOBDIR:dequeued=21, dequeued/memory=21, enqueued=21, enqueued/memory=21
  JOBDIR=<目录>:dequeued=21, dequeued/disk=21, enqueued=21, enqueued/disk=21

调度器由三层组成:优先级队列(2.19 默认 DownloaderAwarePriorityQueue)管优先级和域名,内存/磁盘队列(默认后进先出)管同优先级内的顺序,起始请求另用先进先出的队列。

priority

实测 · 本机执行输出
  产生顺序(序号-优先级):0:0 1:5 2:-3 3:10 4:0 5:5 6:-3 7:10 8:1 9:1 10:0 11:0
  服务器收到的顺序:     7:10 3:10 5:5 1:5 9:1 8:1 11:0 10:0 4:0 0:0 6:-3 2:-3
  → 优先级 10 的两个(3、7)最先且后产生的 7 在前;同优先级内后产生的先出(LIFO)

Request.priority 越大越先出队,同优先级内由内存队列的类型决定顺序。

改变同优先级顺序

实测 · 本机执行输出
  服务器收到的顺序:     3:10 7:10 1:5 5:5 8:1 9:1 0:0 4:0 10:0 11:0 2:-3 6:-3

把 SCHEDULER_MEMORY_QUEUE(和磁盘队列)换成 FIFO 版本即可。

JOBDIR 与不可序列化请求

实测 · 本机执行输出
  服务器收到的顺序:mem-p-1 disk-p9 disk-p5
  默认(SCHEDULER_DEBUG=False)时日志里没有任何警告;SCHEDULER_DEBUG=1 时:WARNING: Unable to serialize request: <GET http://127.0.0.1:9960/mem-p-1> - reason: Function <function S.parse.<locals>.<lambda> at 0x…> is not an instance method in: <S 's' at 0x…>
  stats:enqueued/disk=3, enqueued/memory=1, unserializable=1

启用 JOBDIR 后请求先尝试进磁盘队列,失败(比如回调是 lambda)才退回内存队列;出队时先内存后磁盘,两者之间不比较优先级。

按域名分队

实测 · 本机执行输出
  DownloaderAwarePriorityQueue(默认):服务器收到的顺序 B2 A2 B1 A1 B0 A0  (A=127.0.0.1 优先级 0,B=localhost 优先级 100)
  ScrapyPriorityQueue:服务器收到的顺序 B2 B1 B0 A2 A1 A0  (A=127.0.0.1 优先级 0,B=localhost 优先级 100)

DownloaderAwarePriorityQueue 每个域名一个子队列,选下载中请求最少的域名出队;优先级只在域名内部起作用。

与按 IP 限制冲突

实测 · 本机执行输出
  -s CONCURRENT_REQUESTS_PER_IP=2:退出码 1;服务器收到 0 个请求
  ValueError: "<class 'scrapy.pqueues.DownloaderAwarePriorityQueue'>" does not support CONCURRENT_REQUESTS_PER_IP

它不支持 CONCURRENT_REQUESTS_PER_IP,设了就在启动时抛 ValueError。

分步演示

调度队列
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8 / Scrapy 2.19.0 / Twisted 26.4.0 / lxml 6.1.3 / parsel 1.12.1(临时虚拟环境);所有请求只发给本机自己起的测试服务器(127.0.0.1,端口 9960 以上,不访问任何真实网站);服务器可以按实验需要制造延迟、重定向、压缩、断连等行为,并记录收到的原始请求;每个实验在临时目录里新建 Scrapy 项目或单文件 spider,命令输出来自真实的 scrapy 进程;计时用 time.perf_counter,数字每次运行会有小幅抖动。并发 1 时,由一个种子页的回调一次性产生所有请求,保证它们同时在队列里再按调度器规则出队;服务器记录收到请求的顺序;第五节用 127.0.0.1 与 localhost 当作两个域名
    • 没有验证:磁盘队列的文件格式与大小(见 JOBDIR 那篇);真实多域名爬取下 DownloaderAwarePriorityQueue 对吞吐的影响(本机一个服务器,看不出差别);优先级队列对 start 请求的细节(只在上一篇用顺序验证过);自定义 Scheduler 子类;MarshalFifoDiskQueue 等其它磁盘队列类的差别
    京ICP备2026067502号-1京公网安备11010502063487号