bigywater.tech
Scrapy · 37

signals:引擎在哪些时刻发出信号

Scrapy 的信号有哪些、什么时候发、带什么参数?处理函数抛异常、写成 async 会怎样?能不能用信号提前中止一次下载?这篇给全部 22 个信号接上记录器,在一次包含各种情形的抓取里量出来。

22 个
scrapy.signals 里的信号总数
9 个
接 async 处理函数时根本没有运行的信号数(16 个里 7 个会被 await)
65,536 字节
bytes_received 里 StopDownload 把 300,000 字节响应截断后的 body 大小

信号一览

实测 · 本机执行输出
  scrapy.signals 里有 22 个信号:bytes_received engine_started engine_stopped feed_exporter_closed feed_slot_closed headers_received item_dropped item_error item_scraped memusage_warning_reached request_dropped request_left_downloader request_reached_downloader request_scheduled response_downloaded response_received robots_parsed scheduler_empty spider_closed spider_error spider_idle spider_opened
  信号                            次数  参数(除 sender、signal)
  bytes_received                 6  ['data', 'request', 'spider']
  engine_started                 1  []
  engine_stopped                 1  []
  feed_exporter_closed           1  []
  feed_slot_closed               1  ['slot']
  headers_received               7  ['body_length', 'headers', 'request', 'spider']
  item_dropped                   1  ['exception', 'item', 'response', 'spider']
  item_error                     1  ['failure', 'item', 'response', 'spider']
  item_scraped                   2  ['item', 'response', 'spider']
  memusage_warning_reached       0  (没触发)
  request_dropped                1  ['request', 'spider']
  request_left_downloader        7  ['request', 'spider']
  request_reached_downloader     7  ['request', 'spider']
  request_scheduled              7  ['request', 'spider']
  response_downloaded            7  ['request', 'response', 'spider']
  response_received              6  ['request', 'response', 'spider']
  robots_parsed                  1  ['request', 'robotparser']
  scheduler_empty                3  []
  spider_closed                  1  ['reason', 'spider']
  spider_error                   1  ['failure', 'response', 'spider']
  spider_idle                    1  ['spider']
  spider_opened                  1  ['spider']

请求生命周期的四个信号(scheduled、reached_downloader、left_downloader、response_downloaded)每个下载的请求各触发一次;response_received 比它们少,因为被重定向这类中间件消费的响应不会走到引擎。

顺序

实测 · 本机执行输出
  首次出现的顺序:spider_opened → engine_started → request_scheduled → request_reached_downloader → headers_received → bytes_received → response_downloaded → request_left_downloader → response_received → robots_parsed → scheduler_empty → request_dropped → spider_error → item_scraped → item_dropped → item_error → spider_idle → spider_closed → feed_slot_closed → feed_exporter_closed → engine_stopped
  最后 6 个事件:scheduler_empty → spider_idle → spider_closed → feed_slot_closed → feed_exporter_closed → engine_stopped

每个请求的信号链是 scheduled → reached → headers_received → bytes_received → response_downloaded → left → response_received;结束时 spider_idle → spider_closed → feed_* → engine_stopped。

提前中止下载

实测 · 本机执行输出
  StopDownload(fail=False):[{'sig': 'bytes_received', 'n': 65536}, {'who': 'callback', 'body': 65536, 'status': 200}];stats downloader/response_bytes=65670
  StopDownload(fail=True):[{'sig': 'bytes_received', 'n': 65536}, {'who': 'errback', 'exc': 'StopDownload', 'resp': 65536}];stats downloader/response_bytes=None
  不中止:[{'who': 'callback', 'body': 300000, 'status': 200}];bytes_received 触发了 5 次;response_bytes=300134

headers_received 和 bytes_received 的处理函数可以抛 StopDownload,fail 参数决定把截断的响应交给回调还是 errback。

异常与 async

实测 · 本机执行输出
  信号                          async 处理函数      
  spider_opened               被 await 到结束     
  engine_started              被 await 到结束     
  request_scheduled           没有运行(协程没被调度)    
  request_reached_downloader  没有运行(协程没被调度)    
  headers_received            没有运行(协程没被调度)    
  bytes_received              没有运行(协程没被调度)    
  response_downloaded         没有运行(协程没被调度)    
  request_left_downloader     没有运行(协程没被调度)    
  response_received           没有运行(协程没被调度)    
  item_scraped                被 await 到结束     
  scheduler_empty             没有运行(协程没被调度)    
  spider_idle                 没有运行(协程没被调度)    
  spider_closed               被 await 到结束     
  feed_slot_closed            被 await 到结束     
  feed_exporter_closed        被 await 到结束     
  engine_stopped              被 await 到结束     
  日志:['[scrapy.utils.signal] ERROR: Error caught on signal handler: <function S.from_crawler.<locals>.bad a'];finish_reason=finished(处理函数抛异常没有影响)

处理函数异常只记日志。async 处理函数只在 Scrapy 用 send_catch_log_async 触发的信号上被 await,其它信号上等于不运行。

弱引用

实测 · 本机执行输出
  不保存引用的局部函数:处理函数被调用 0 次
  把函数挂到 spider 属性上:处理函数被调用 1 次

connect 默认只存弱引用,局部函数要自己保存引用。

分步演示

信号
本步的对象

    本文用到的工具与边界

    • 本机 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,数字每次运行会有小幅抖动。信号次数来自给每个信号接一个只记录名称与参数名的处理函数;async 实验在每个信号上接 await asyncio.sleep(0.3) 的处理函数,看是否出现 start 和 end 两个事件;并发 1
    • 没有验证:signals.connect(weak=False);其它 send_catch_log 来源(telnet、custom)的信号;spider_idle 里抛 DontCloseSpider 以外的行为(上一批文章里测过);多个 crawler 同进程时信号是否隔离;处理函数的执行顺序(同一信号上多个处理函数);feed_slot_closed 的 slot 参数内容
    京ICP备2026067502号-1京公网安备11010502063487号