doupoa
一个不甘落后的热血青年!
Ping通途说

Windows Playwright NotImplementedError问题深究

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
  • 为什么?
    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.总结一下

  1. 如果只是临时开发,禁用 --reload 最简单。
  2. 如果必须用 --reload,优先 Async Playwright + ProactorEventLoop
  3. 如果坚持用 Sync Playwright,用线程隔离
0
0
赞赏

doupoa

文章作者

诶嘿

发表回复

textsms
account_circle
email

Ping通途说

Windows Playwright NotImplementedError问题深究
0. 引言 今天来看一下这个困扰我很久的问题。是关于在FastAPI / NiceGUI 等基于Uvicorn环境下使用Async Playwright 提示NotImplementedError的问题。 本解决方案仅适用基于Uvicorn…
扫描二维码继续阅读
2025-06-18

Optimized by WPJAM Basic