爬虫要停的时候,在途的请求怎么办?直接按 Ctrl-C,已经发出的请求会被取消,结果丢了;容器发 SIGTERM,默认行为更直接。这篇让真实的爬虫进程收到不同的信号,看每种情况下在途请求、队列里剩余的 URL 和退出时间。
退出码 0(脚本里捕获了 KeyboardInterrupt,所以是 0),从发信号到退出 19 ms
进程最后的输出:{'done': 0, 'started': 8, 'left_in_queue': 192, 'log': ['取消:0', '取消:1', '取消:2'], 'nlog': 8}
进程最后的输出:{'keyboardinterrupt': True}
服务器共收到 8 个请求;asyncio.run 在收到 SIGINT 时取消了主任务,在途的请求被取消(连接被关闭),队列里剩下的 URL 没有被处理asyncio.run 收到 SIGINT 会取消主任务,在途的请求一起被取消,进程以 KeyboardInterrupt 收尾。8 个已发出的请求全部白费,已经开始的工作没有保存。
退出码 0,发信号后 313 ms 退出(服务器每个请求 0.3 秒);完成 8 个,开始过 8 个,队列里还剩 192 个没开始;被取消的请求 0 个 服务器共收到 8 个请求;完成数 = 开始数 → 没有请求被半路丢掉;剩下的 192 个 URL 还留在队列里,可以保存下来下次再抓
在事件循环里用 loop.add_signal_handler(signal.SIGINT, stop.set) 设一个标志,worker 在取下一个 URL 之前检查它;在途的请求自己做完(8 个全完成、0 个取消),队列里剩下的 192 个 URL 没有被丢弃,可以写进状态文件下次再抓(上一篇的做法)。
不处理信号时收到 SIGTERM:退出码 -15(负数表示被该信号杀死),2 ms 退出;进程有没有机会输出收尾信息:False 不处理信号时收到 SIGKILL:退出码 -9(负数表示被该信号杀死),5 ms 退出;进程有没有机会输出收尾信息:False 只给 SIGINT 装了处理器时收到 SIGTERM:退出码 -15,2 ms 退出;进程有没有机会输出收尾信息:False
容器、systemd、进程管理器通常先发 SIGTERM 再发 SIGKILL。没有为 SIGTERM 装处理器,进程直接以 -15 退出;SIGKILL 任何代码都拦不住。所以优雅停止的处理器要同时装给 SIGINT 和 SIGTERM。
服务器每个请求 0.3 秒:发信号后 322 ms 退出;完成 8 个,队列里剩 192 个 服务器每个请求 1.0 秒:发信号后 1023 ms 退出;完成 8 个,队列里剩 192 个 服务器每个请求 2.0 秒:发信号后 2025 ms 退出;完成 8 个,队列里剩 192 个
优雅停止的时间等于在途请求剩下的时间:0.3 秒的请求 322 ms,2 秒的请求 2025 ms。生产里要配一个超时(比如超过 N 秒就取消剩下的),避免一个卡住的请求让进程永远停不下来。