原文鏈接: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,用線程隔離。