You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

按下Ctrl+C后Pyppeteer脚本崩溃并陷入无限循环的问题

问题分析:Pyppeteer触发Ctrl+C后无限报错的原因及修复方案

先贴出你的代码方便参考:

import asyncio
import pyppeteer

async def hi():
    ret = await pyppeteer.launch()
    await asyncio.sleep(10)
    return ret

browser = asyncio.get_event_loop().run_until_complete(hi())

问题现象

运行这段代码后,如果在10秒内按下Ctrl+C,会疯狂输出一堆报错,核心错误是RuntimeError: This event loop is already running,还有一堆WebSocket连接关闭的异常,甚至会陷入类似无限循环的报错状态。

问题根源

你说的没错,Pyppeteer确实注册了atexit处理程序来清理Chrome进程,但这个处理程序的实现有个致命问题:

当你按下Ctrl+C时,Python会抛出KeyboardInterrupt,此时asyncio.get_event_loop().run_until_complete(hi())还没执行完,事件循环仍然处于运行状态。而Pyppeteer的atexit回调(也就是launcher.py里的_close_process方法)直接调用了:

asyncio.get_event_loop().run_until_complete(self.killChrome())

在Python 3.6及以上版本中,事件循环是不允许嵌套调用run_until_complete的,这就直接抛出了RuntimeError。

更糟的是,这个异常会打断正常的资源清理流程,导致Chrome进程的WebSocket连接异常关闭,未处理的任务异常不断被抛出,最终形成你看到的无限报错场景。

修复方案

这里给你几个可行的解决办法,按推荐程度排序:

1. 使用Python 3.7+的asyncio.run(最简洁)

asyncio.run会自动管理事件循环的生命周期,包括异常时的优雅清理,完全避免嵌套运行的问题。同时我们加上finally块确保浏览器一定会被关闭:

import asyncio
import pyppeteer

async def hi():
    browser = await pyppeteer.launch()
    try:
        await asyncio.sleep(10)
        return browser
    finally:
        # 不管有没有异常,都关闭浏览器
        await browser.close()

if __name__ == "__main__":
    asyncio.run(hi())

2. 手动处理SIGINT信号(兼容Python 3.6)

如果你还在使用Python 3.6,可以自己注册信号处理函数,在收到Ctrl+C时先优雅关闭浏览器,再终止事件循环:

import asyncio
import pyppeteer
import signal

browser_instance = None

def handle_ctrl_c(signum, frame):
    # 提交关闭任务到事件循环
    asyncio.create_task(shutdown())

async def shutdown():
    global browser_instance
    if browser_instance:
        await browser_instance.close()
    # 停止事件循环
    asyncio.get_event_loop().stop()

async def hi():
    global browser_instance
    browser_instance = await pyppeteer.launch()
    await asyncio.sleep(10)
    return browser_instance

if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    # 注册SIGINT信号处理
    signal.signal(signal.SIGINT, handle_ctrl_c)
    try:
        loop.run_until_complete(hi())
    finally:
        loop.close()

3. 修改Pyppeteer源码(不推荐)

如果你不想改自己的代码,可以尝试修改Pyppeteer的launcher.py文件,把_close_process方法里的run_until_complete改成create_task:
把原来的:

asyncio.get_event_loop().run_until_complete(self.killChrome())

替换成:

asyncio.get_event_loop().create_task(self.killChrome())

不过这种方式需要修改第三方库的源码,后续升级Pyppeteer时会覆盖你的修改,所以只作为临时方案。

总结

核心问题就是Pyppeteer的atexit处理程序没有考虑到事件循环仍在运行的情况,错误地嵌套调用了run_until_complete,引发了连锁错误。通过上面的方案都能解决这个问题,个人推荐优先用asyncio.run的方案,最省心。

内容的提问来源于stack exchange,提问作者d33tah

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:32:40