bigywater.tech
Scrapy · 36

扩展:CloseSpider 与 MemoryUsage

想让爬虫到点自己停下来,有 CLOSESPIDER_* 一组设置和 MemoryUsage 扩展。它们精确吗?超出多少?哪些错误算数?内存限制是怎么量的?这篇用能计数的服务器和故意吃内存的回调逐项实测。

24 vs 10
CLOSESPIDER_PAGECOUNT = 10 实际收到的响应数(并发 16)
60 s
MemoryUsage 默认检查周期(不调小的话短爬虫里 max 永远等于 startup)
memusage_exceeded
内存超过 MEMUSAGE_LIMIT_MB 时的 finish_reason

PAGECOUNT

实测 · 本机执行输出
  PAGECOUNT=10:finish_reason=closespider_pagecount;服务器收到 24 个请求(含起始页);response_received_count=24;item_scraped_count=23;scheduler/enqueued=201;dequeued=24;耗时 0.15 s
  PAGECOUNT=50:finish_reason=closespider_pagecount;服务器收到 65 个请求(含起始页);response_received_count=65;item_scraped_count=64;scheduler/enqueued=201;dequeued=65;耗时 0.28 s
  不限制:finish_reason=finished;服务器收到 201 个请求(含起始页);response_received_count=201;item_scraped_count=200;scheduler/enqueued=201;dequeued=201;耗时 0.80 s

CLOSESPIDER_PAGECOUNT 到数就异步关闭爬虫,关闭那一刻在途的请求还会完成,所以实际响应数比设置值大;调度器里排队的请求被丢弃。

ITEMCOUNT

实测 · 本机执行输出
  ITEMCOUNT=10:finish_reason=closespider_itemcount;服务器收到 33 个请求(含起始页);response_received_count=33;item_scraped_count=32;scheduler/enqueued=201;dequeued=33;耗时 0.15 s
  ITEMCOUNT=50:finish_reason=closespider_itemcount;服务器收到 72 个请求(含起始页);response_received_count=72;item_scraped_count=71;scheduler/enqueued=201;dequeued=72;耗时 0.33 s

CLOSESPIDER_ITEMCOUNT 同理;item 比响应更晚计数,超出得更多。

错误计数

实测 · 本机执行输出
  回调抛异常,ERRORCOUNT=5:finish_reason=closespider_errorcount;服务器收到 33 个请求(含起始页);response_received_count=33;item_scraped_count=0;scheduler/enqueued=201;dequeued=33;耗时 0.16 s;spider_exceptions/count=32
  服务器全返回 404,ERRORCOUNT=5:finish_reason=finished;服务器收到 61 个请求(含起始页);response_received_count=61;item_scraped_count=0;scheduler/enqueued=61;dequeued=61;耗时 0.26 s(404 不算 spider_error)

CLOSESPIDER_ERRORCOUNT 只数 spider_error 信号(回调里的异常),HTTP 404、下载失败不算。

时间类

实测 · 本机执行输出
  TIMEOUT=1 s(并发 2,每页 0.1 s):finish_reason=closespider_timeout;服务器收到 19 个请求(含起始页);response_received_count=19;item_scraped_count=18;scheduler/enqueued=201;dequeued=19;耗时 1.01 s
  TIMEOUT_NO_ITEM=2 s(页面不产出 item,并发 2,每页 0.1 s):finish_reason=closespider_timeout_no_item;服务器收到 39 个请求(含起始页);response_received_count=39;item_scraped_count=0;scheduler/enqueued=201;dequeued=39;耗时 2.10 s
  PAGECOUNT_NO_ITEM=20(只有 p0~p2 产出 item,并发 1):finish_reason=closespider_pagecount_no_item;服务器收到 20 个请求(含起始页);response_received_count=20;item_scraped_count=0;scheduler/enqueued=201;dequeued=20;耗时 0.03 s

CLOSESPIDER_TIMEOUT 从 spider 打开开始计时;TIMEOUT_NO_ITEM 每隔一个周期检查是否有新 item;PAGECOUNT_NO_ITEM 数连续没有 item 的页面。

MemoryUsage

实测 · 本机执行输出
  不设限:finish_reason=finished;item_scraped_count=40;memusage/startup=86 MiB;max=86 MiB;limit_reached=None;warning_reached=None;[]
  MEMUSAGE_WARNING_MB=110:finish_reason=finished;item_scraped_count=40;memusage/startup=87 MiB;max=469 MiB;limit_reached=None;warning_reached=1;['WARNING: Memory usage reached 110MiB']
  MEMUSAGE_LIMIT_MB=130(每个页面分配 10 MiB 并一直保留):finish_reason=memusage_exceeded;item_scraped_count=7;memusage/startup=87 MiB;max=148 MiB;limit_reached=1;warning_reached=None;['ERROR: Memory usage exceeded 130MiB. Shutting down Scrapy...']

读 ru_maxrss(进程峰值 RSS),每 MEMUSAGE_CHECK_INTERVAL_SECONDS(默认 60)检查一次;超过 MEMUSAGE_LIMIT_MB 关闭爬虫,超过 MEMUSAGE_WARNING_MB 只告警并发信号。

分步演示

自动停止
本步的对象

    本文用到的工具与边界

    • 本机 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,数字每次运行会有小幅抖动。站点 200 个页面(服务器端 time.sleep 制造延迟),并发 16 或更小;内存实验里回调每次分配 10 MiB 并保留;检查间隔调成 0.2 s 才能看到超限
    • 没有验证:Linux 上 ru_maxrss 的单位(源码按平台换算,macOS 上是字节);MEMUSAGE_NOTIFY_MAIL(源码标注弃用并发警告);关闭后 pipeline 里排队的 item 是否处理完(item_scraped_count 与 CLOSESPIDER_ITEMCOUNT 的差距提示会处理);CLOSESPIDER_TIMEOUT 与 DontCloseSpider 的组合;多个 crawler 在同一进程里 MemoryUsage 读到的是整个进程的内存
    京ICP备2026067502号-1京公网安备11010502063487号