Pyppeteer+Flask开发截图程序遇Event loop is closed错误求助
解决Flask异步视图中Pyppeteer报错“RuntimeError: Event loop is closed”的问题
我写了一个处理HTTP请求、对查询参数里的链接截图的程序,想保持浏览器打开,通过新建标签页节省内存。但第一次请求能正常返回截图,第二次请求失败,报错RuntimeError: Event loop is closed。
代码示例
main.py
from flask import Flask, Response, request from schedule_gen import capture_screenshot app = Flask(__name__) @app.route('/schedule', methods=['GET']) async def schedule(): # Processing query parameters. url = request.args.get('url') can_cache = request.args.get('canCache', False) if url is None: return "No url" screenshot = await capture_screenshot(url) return Response(screenshot, mimetype='image/png') if __name__ == '__main__': app.run(debug=True, port=8800, host='0.0.0.0')
schedule_gen.py
from pyppeteer import launch browser = None async def get_browser(): global browser if browser is None: browser = await launch( options={'args': ['--no-sandbox']}, handleSIGINT=False, handleSIGTERM=False, handleSIGHUP=False ) return browser async def capture_screenshot(url): b = await get_browser() page = await b.newPage() # Set the viewport height to match the entire page await page.setViewport({'width': 1920, 'height': 0}) await page.goto(url) screenshot = await page.screenshot({'fullPage': True}) await page.close() return screenshot
问题原因
Flask默认的Werkzeug开发服务器不支持异步视图函数。第一次请求时,Flask会临时创建一个事件循环处理异步逻辑,但请求结束后这个循环会被直接关闭。第二次请求时,Pyppeteer的浏览器实例还绑定在已关闭的旧循环上,就触发了Event loop is closed的错误。
解决方案
方案一:改用ASGI服务器(推荐)
Flask 2.0+支持异步视图,但需要配合ASGI服务器运行,比如Uvicorn。
- 安装Uvicorn:
pip install uvicorn
- 修改main.py的启动代码:
from flask import Flask, Response, request from schedule_gen import capture_screenshot app = Flask(__name__) @app.route('/schedule', methods=['GET']) async def schedule(): url = request.args.get('url') can_cache = request.args.get('canCache', False) if url is None: return "No url" screenshot = await capture_screenshot(url) return Response(screenshot, mimetype='image/png') if __name__ == '__main__': import uvicorn uvicorn.run(app, host='0.0.0.0', port=8800, reload=True)
这样运行后,Uvicorn会维护一个持久的事件循环,Pyppeteer的浏览器实例可以一直复用,不会出现循环关闭的问题。
方案二:手动复用事件循环
如果不想换服务器,可以手动创建一个持久化的事件循环,把异步函数包装成同步调用:
修改main.py:
from flask import Flask, Response, request from schedule_gen import capture_screenshot import asyncio app = Flask(__name__) # 创建并设置全局事件循环 loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) @app.route('/schedule', methods=['GET']) def schedule(): url = request.args.get('url') can_cache = request.args.get('canCache', False) if url is None: return "No url" # 用全局循环执行异步截图函数 screenshot = loop.run_until_complete(capture_screenshot(url)) return Response(screenshot, mimetype='image/png') if __name__ == '__main__': app.run(debug=True, port=8800, host='0.0.0.0')
额外注意事项
- 关闭Flask的debug自动重载(或者在reload时重新初始化浏览器),否则重载会导致浏览器实例失效。
- 可以给浏览器添加退出钩子,确保应用关闭时正常关闭浏览器,避免资源泄漏:
在schedule_gen.py末尾添加:
import atexit async def close_browser(): global browser if browser is not None: await browser.close() # 用全局事件循环执行关闭操作 def shutdown(): global loop loop.run_until_complete(close_browser()) atexit.register(shutdown)
内容的提问来源于stack exchange,提问作者mfkrg
相关产品推荐
相关产品推荐

