bigywater.tech
Scrapy · 38

JOBDIR:暂停和恢复时磁盘上存了什么

给爬虫加 JOBDIR 就能暂停和恢复,可是磁盘上到底存了什么?续跑会不会重复或遗漏?kill -9 呢?起始请求会不会重发?请求里有不能序列化的东西又怎样?这篇把每种中断方式都做一遍,数清楚丢了多少、重了多少。

0 个
Ctrl-C 后续跑,两次运行重叠的页面数(40 个页面一个不重复)
0 字节
kill -9 后 requests.seen 的大小(指纹还在缓冲区)
24 个
meta 里放不能 pickle 的对象,中断后永久丢失的请求数(40 个里只下载了 16 个)

JOBDIR 里有什么

实测 · 本机执行输出
  finish_reason=shutdown;这次下载了 18 个页面(+ 起始页);scheduler/enqueued=41、dequeued=19
    requests.queue/127.0.0.1-f528764d624db129b32c21fbca0cb8d6/0/info.json     73 字节
    requests.queue/127.0.0.1-f528764d624db129b32c21fbca0cb8d6/0/q00000  10510 字节
    requests.queue/active.json             18 字节
    requests.seen                         902 字节
    spider.state                          135 字节

requests.queue/ 存磁盘队列,active.json 记录队列状态,requests.seen 是已见请求的指纹(每个 22 字节),spider.state 是 self.state 的 pickle。

续跑

实测 · 本机执行输出
  第 2 次:finish_reason=finished;下载了 22 个页面,起始页重发了 0 次(自己写的 start() 没有 dont_filter,被 requests.seen 过滤);dupefilter/filtered=1;scheduler/dequeued=22
  两次合计:页面 40 次请求,不同页面 40 个(应为 40),两次重叠的页面 0 个
    requests.queue/active.json              2 字节
    requests.seen                         902 字节
    spider.state                          267 字节

优雅停止(一次 Ctrl-C)后用同一个 JOBDIR 再运行,只处理磁盘队列里剩下的请求,不重复已完成的页面,self.state 的内容继续累加。

state

实测 · 本机执行输出
  spider.state 里的内容:count=40、urls 的个数=40;两次运行各自的 page 回调次数 18 和 22(合计 40)

self.state 里的值必须能 pickle,适合存计数、游标等。

起始请求

实测 · 本机执行输出
  第 3 次(自己写的 start()):服务器收到 0 个请求 [];dupefilter/filtered=1;finish_reason=finished
  第 4 次(默认 start_urls):服务器收到 1 个请求 ['/'];dupefilter/filtered=40;finish_reason=finished(起始页重发了,子链接全被过滤)

自己写 start() 且不加 dont_filter 时,续跑里起始请求被 requests.seen 过滤;默认 start_urls 自带 dont_filter=True,会重发。

强制杀死

实测 · 本机执行输出
    requests.queue/127.0.0.1-f528764d624db129b32c21fbca0cb8d6/0/q00000  10510 字节
    requests.seen                           0 字节
  kill -9 前下载了 22 个页面;恢复后又下载了 40 个;不同页面共 40 个(完整应为 40),缺 0 个;重叠 22 个
  恢复那次:finish_reason=finished;scheduler/dequeued=41

kill -9 不会写 requests.seen 和 spider.state,恢复等于重头开始,重复抓取但不会丢页面。

不能序列化的请求

实测 · 本机执行输出
  meta 里放 threading.Lock():scheduler/unserializable=40;enqueued/memory=40;enqueued/disk=1
  第 1 次下载 16 个页面;恢复后又下载 0 个;不同页面共 16 个(完整应为 40)——内存队列里没落盘的请求在中断时丢了

请求(含 meta、回调)不能 pickle 时只进内存队列,中断即丢,指纹却已入库,这些页面不会再被抓。

分步演示

暂停与恢复
本步的对象

    本文用到的工具与边界

    • 本机 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,数字每次运行会有小幅抖动。站点 40 个页面各 0.1 s,并发 2,先进先出的内存与磁盘队列;用 subprocess.Popen 启动 scrapy runspider,1.5 秒后发 SIGINT 或 SIGKILL;文件大小来自 JOBDIR 目录列表。每次运行的页面数有几个的抖动(18 或 22 取决于发信号的时机)
    • 没有验证:JOBDIR 与 DownloaderAwarePriorityQueue 的多槽子目录(这里只有一个槽);Windows 上的信号;二次 Ctrl-C 强制退出时的状态;requests.seen 的 flush 时机(只看到 kill -9 时是 0 字节,没有读缓冲策略的源码);JOBDIR 里的 spider.state 与多进程同时使用同一个 JOBDIR 的冲突;大规模(百万请求)磁盘队列的体积
    京ICP备2026067502号-1京公网安备11010502063487号