原文链接:Windows Playwright NotImplementedError问题深究 < Ping通途说
0. 引言
今天来看一下这个困扰我很久的问题。是关于在FastAPI / NiceGUI 等基于Uvicorn环境下使用Async Playwright 提示NotImplementedError
的问题。
本解决方案仅适用基于Uvicorn的异步环境,若需解决在Jupyter中无法使用Async Playwright的问题,请参阅:Running Playwright in JupyterLab Notebook Problem - Not implemented Error - JupyterLab - Jupyter Community Forum
1.reload来背锅吧
根本原因分析
1. Async Playwright 在 --reload
下的 NotImplementedError
- 触发条件:
- Uvicorn 使用
--reload
时,会启动一个 文件监视子进程(通过asyncio.create_subprocess_exec
)。 - Windows 上,
asyncio
子进程管理依赖ProactorEventLoop
。 - Async Playwright 启动浏览器时,内部也会尝试创建子进程(浏览器进程),但 Windows 的事件循环策略冲突导致
NotImplementedError
。
- Uvicorn 使用
- 为什么?
Windows 的SelectorEventLoop
不支持子进程操作,而ProactorEventLoop
需要显式设置。Uvicorn 的重载机制和 Playwright 的子进程创建可能使用了不同的事件循环策略,导致冲突。
2. Sync Playwright 在 --reload
下的 it looks like you are using playwright sync api inside the asyncio loop
- 触发条件:
- Uvicorn 运行在异步事件循环中(ASGI 服务器必须是异步的)。
- 如果在 FastAPI 路由或生命周期事件(如
@app.on_event("startup")
)中直接调用 Sync Playwright,会阻塞事件循环。 - Playwright 检测到你在异步环境中使用同步 API,抛出此错误。
- 为什么?
Sync Playwright 会尝试在同步上下文中运行,但 Uvicorn 的--reload
模式已经运行在asyncio
事件循环中,二者无法兼容。
2.要怎么解决
方案 1:禁用 --reload
(最简单)
- 适用场景:开发/生产环境均可,但失去代码热更新功能。
- 启动方式:bash复制下载uvicorn main:app --host 0.0.0.0 --port 8000 # 去掉 --reload(或者在入口中使用
Uvicorn.run("main:app",host="0.0.0.0",port=8000,reload=False)
) - 优点:无需修改代码,直接解决问题。
- 缺点:开发时需手动重启服务。
方案 2:显式设置 WindowsProactorEventLoopPolicy
(仅限 Async Playwright)
- 适用场景:必须使用
--reload
+ Async Playwright。 - 修改入口文件(
main.py
):
import sys
import asyncio
from fastapi import FastAPI if sys.platform == "win32": asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) app = FastAPI()
@app.on_event("startup")
async def startup(): from playwright.async_api import async_playwright playwright = await async_playwright().start() # ... 其余初始化代码
- 优点:保留
--reload
功能。 - 缺点:仅适用于 Async Playwright,且需确保所有 Playwright 操作都是异步的。
方案 3:Sync Playwright + 线程隔离(推荐)
- 适用场景:必须使用 Sync Playwright +
--reload
。 - 实现方式:
from fastapi import FastAPI
import threading from playwright.sync_api
import sync_playwright app = FastAPI()
def run_sync_playwright(): with sync_playwright() as playwright: browser = playwright.chromium.launch() # ... Sync Playwright 操作 @app.on_event("startup")
async def startup(): # 在单独线程中运行 Sync Playwright,避免阻塞事件循环 thread = threading.Thread(target=run_sync_playwright) thread.start()
- 优点:
- 兼容
--reload
。 - 不依赖事件循环策略。
- 兼容
- 缺点:需要管理线程生命周期。
方案 4:换用 Linux 开发(终极方案)
- Windows 的
asyncio
子进程管理存在限制,而 Linux/Mac 无此问题。 - 适用场景:长期项目,可切换开发环境。
- 优点:一劳永逸,无需处理兼容性问题。
- 缺点:需要调整开发环境。
3.总结一下
- 如果只是临时开发,禁用
--reload
最简单。 - 如果必须用
--reload
,优先 Async Playwright +ProactorEventLoop
。 - 如果坚持用 Sync Playwright,用线程隔离。