Playwright Python捕获异常并优雅退出的问题咨询
解决Playwright脚本在CICD中无法干净退出的问题
问题场景
在CICD流程中通过subprocess()调用Playwright Python脚本时,触发KeyboardInterrupt(如按<ctrl+c>)或出现异常时,脚本无法干净退出,会出现两类报错:
关闭浏览器时报错
Traceback (most recent call last): File "C:\dist\testing\exit-test.py", line 59, in <module> browser.close() File "C:\dist\venvs\testing\Lib\site-packages\playwright\sync_api\_generated.py", line 13927, in close return mapping.from_maybe_impl(self._sync(self._impl_obj.close(reason=reason))) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\dist\venvs\testing\Lib\site-packages\playwright\_impl\_sync_base.py", line 115, in _sync return task.result() ^^^^^^^^^^^^^ File "C:\dist\venvs\testing\Lib\site-packages\playwright\_impl\_browser.py", line 192, in close raise e File "C:\dist\venvs\testing\Lib\site-packages\playwright\_impl\_browser.py", line 189, in close await self._channel.send("close", {"reason": reason}) File "C:\dist\venvs\testing\Lib\site-packages\playwright\_impl\_connection.py", line 59, in send return await self._connection.wrap_api_call( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\dist\venvs\testing\Lib\site-packages\playwright\_impl\_connection.py", line 514, in wrap_api_call raise rewrite_error(error, f"{parsed_st['apiName']}: {error}") from None Exception: Browser.close: Connection closed while reading from the driver
直接退出时的异步残留报错
Exception ignored in: <function BaseSubprocessTransport.__del__ at 0x00000242299C2A20> Traceback (most recent call last): File "C:\dist\python312\Lib\asyncio\base_subprocess.py", line 126, in __del__ File "C:\dist\python312\Lib\asyncio\base_subprocess.py", line 104, in close File "C:\dist\python312\Lib\asyncio\proactor_events.py", line 109, in close File "C:\dist\python312\Lib\asyncio\base_events.py", line 795, in call_soon File "C:\dist\python312\Lib\asyncio\base_events.py", line 541, in _check_closed RuntimeError: Event loop is closed Exception ignored in: <function _ProactorBasePipeTransport.__del__ at 0x00000242299E8220> Traceback (most recent call last): File "C:\dist\python312\Lib\asyncio\proactor_events.py", line 116, in __del__ File "C:\dist\python312\Lib\asyncio\proactor_events.py", line 80, in __repr__ File "C:\dist\python312\Lib\asyncio\windows_utils.py", line 102, in fileno ValueError: I/O operation on closed pipe Task was destroyed but it is pending! task: <Task pending name='Task-12' coro=<Locator.click() running at C:\dist\venvs\testing\Lib\site-packages\playwright\_impl\_locator.py:156> wait_for=<Future pending cb=[Task.task_wakeup()]> cb=[SyncBase._sync.<locals>.<lambda>() at C:\dist\venvs\testing\Lib\site-packages\playwright\_impl\_sync_base.py:111, ProtocolCallback.__init__.<locals>.cb() at C:\dist\venvs\testing\Lib\site-packages\playwright\_impl\_connection.py:191]>
解决方法
核心是确保Playwright资源的生命周期被正确管理,同时处理异步任务的残留问题:
- 使用Playwright的上下文管理器(
with语句)自动管理playwright实例、浏览器、页面,避免手动关闭时的连接问题 - 全局捕获
KeyboardInterrupt和所有异常,确保资源清理完成后再退出 - 避免在循环内直接调用
sys.exit(),改为标记退出状态,统一完成清理后退出
修改后的完整代码
#!/usr/bin/env python3 """ pip install playwright playwright install playwright install-deps """ import argparse, json, platform, sys, time from playwright.sync_api import sync_playwright url = "https://www.google.no" parser = argparse.ArgumentParser(description='playwright example') parser.add_argument('--browser', help='browser type', choices=['chrome','firefox','msedge','webkit'], default='chrome') parser.add_argument('--headed', help='set to set headed with browser', action='store_true') args, unknown_args = parser.parse_known_args() if unknown_args: print(f"unknown parameters: {unknown_args}") sys.exit(-1) args.headless = not args.headed if platform.system() != 'Darwin' and args.browser == 'webkit': print("webkit only supported on apple") sys.exit(-1) exit_code = 0 try: # 使用上下文管理器自动管理Playwright实例 with sync_playwright() as p: t0 = time.time() if args.browser == 'firefox': browser = p.firefox.launch(headless=args.headless) elif args.browser == 'chrome': browser = p.chromium.launch(channel='chrome', headless=args.headless) elif args.browser == 'msedge': browser = p.chromium.launch(channel='msedge', headless=args.headless) elif args.browser == 'webkit': browser = p.webkit.launch(headless=args.headless) # 使用上下文管理器自动管理浏览器和页面 with browser, browser.new_page() as page: searches = ["python", "playwright", "selenium", "requests", "beautifulsoup"] for search in searches: t1 = time.time() page.goto(url) try: page.get_by_role("button", name="Godta alle").click(timeout=1000) except Exception: pass page.get_by_label("Søk", exact=True).type(search) page.get_by_label("Søk", exact=True).press("Enter") page.locator("#tsf").get_by_role("button", name="Søk", exact=True) time.sleep(1) page.screenshot(path=f"pics/google-{search}.png") except KeyboardInterrupt: print("Received interrupt, exiting gracefully...") exit_code = 1 except Exception as e: print(f"Error occurred: {str(e)}") exit_code = -1 sys.exit(exit_code)
关键修改点说明
上下文管理器替代手动启动/关闭:
with sync_playwright() as p:自动启动和停止Playwright服务,避免手动调用p.start()和p.stop()的遗漏with browser, browser.new_page() as page:自动关闭浏览器和页面,无需手动调用browser.close(),确保资源被正确释放
全局异常捕获:
- 顶层
try-except块捕获KeyboardInterrupt和所有异常,确保无论触发何种中断,都能完成资源清理后再退出 - 移除循环内的
sys.exit(),改用exit_code标记退出状态,最后统一退出
- 顶层
简化逻辑:
- 将
args.headless = True改为args.headless = not args.headed,简化判断逻辑 - 移除循环内的异常捕获,统一到顶层处理,避免嵌套异常导致的清理失败
- 将
内容的提问来源于stack exchange,提问作者MortenB
相关产品推荐
相关产品推荐

