Celery+Playwright默认失败,因Celery fork子进程导致Playwright事件循环丢失或浏览器被终止;异步模式不被Celery原生支持,推荐用subprocess隔离运行Playwright脚本。

Playwright 不能直接在 Celery worker 进程中启动浏览器实例,除非显式配置事件循环和进程模型——否则会报 RuntimeError: There is no current event loop in thread 或 BrowserType.launch: Browser closed。
为什么 Celery + Playwright 默认会失败
Playwright 的 sync_playwright() 和 playwright.sync_api.sync_playwright 依赖主线程的事件循环,而 Celery worker(尤其是 prefork 模式)会 fork 子进程,导致事件循环丢失或浏览器进程被意外终止。异步版本(async_playwright())更麻烦:Celery 当前(v5.3+)仍不原生支持 async task,强行 await 会阻塞 worker。
- Celery 默认用
fork启动子进程,Playwright 浏览器二进制无法继承图形上下文 -
sync_playwright()内部调用asyncio.get_event_loop(),但在 fork 后的子进程中 loop 未创建 - 即使加
asyncio.set_event_loop_policy(),也难保证 browser 实例生命周期与 task 对齐
推荐方案:用 subprocess 调用独立 Playwright 脚本
绕过 Celery worker 的 Python 环境限制,把 Playwright 逻辑隔离到独立进程里运行。这是最稳定、可调试、易部署的方式。
- 写一个纯命令行脚本
spider_runner.py,接收 URL、超时等参数,用sync_playwright()执行,输出 JSON 到 stdout - Celery task 中用
subprocess.run()调用它,设置timeout和capture_output=True - 确保该脚本所在环境已安装
playwright并执行过playwright install chromium - 避免使用
shell=True,防止注入;参数全部走args列表
import subprocess
import json
def run_playwright_spider(url: str) -> dict:
result = subprocess.run(
["python", "spider_runner.py", "--url", url],
capture_output=True,
text=True,
timeout=60
)
if result.returncode != 0:
raise RuntimeError(f"Spider failed: {result.stderr}")
return json.loads(result.stdout)
如果坚持在 Celery 中用 sync_playwright:必须用 threadsafe 模式
仅限单 worker、单并发、非 fork 场景(如 --pool=threads),且每次 task 必须完整管理 playwright 上下文生命周期。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
立即学习“Python免费学习笔记(深入)”;
- 不要全局复用
playwright或browser实例;每个 task 内调用with sync_playwright() as p: - 显式指定浏览器路径(尤其 Docker 环境):
p.chromium.launch(executable_path="/usr/bin/chromium-browser") - 禁用 sandbox(Linux):
headless=True, args=["--no-sandbox", "--disable-setuid-sandbox"] - 设置
slow_mo或timeout防止页面卡死拖垮整个 worker
Docker 部署时的常见坑
Playwright 在容器里跑不起来,90% 是缺系统依赖或权限问题,和 Celery 无关,但叠加后更难排查。
- 基础镜像别用
python:slim,改用python:3.10-bullseye或显式安装缺失库:libnss3 libatk1.0-0 libatk-bridge2.0-0 libgtk-3-0 libgbm1 - Chromium 启动失败时检查
stderr输出,常见错误是Failed to move to new namespace→ 加--cap-add=SYS_ADMIN - Celery worker 启动命令里加
--concurrency=1,避免多 browser 实例争抢 /dev/shm - Playwright 日志开启:
PLAYWRIGHT_DEBUG=1,但别长期开,会吃光磁盘
真正麻烦的不是怎么写,而是 browser 实例的销毁时机和异常中断后的残留清理——比如网络超时后没 close page,下次 task 就可能因端口占用或共享内存满而失败。建议所有 Playwright 调用都包在 try/finally 里,显式 page.close()、context.close()、browser.close()。

















