必须复用browser实例,不可在每个asyncio.run()中重复launch;正确做法是单次async with async_playwright()内启动browser,再用asyncio.gather()并发多个page任务。

asyncio.run() 不能直接套用 Playwright 的 async with browser
Playwright 的 async with playwright.chromium.launch() 必须在同一个事件循环中完成初始化和关闭,而 asyncio.run() 每次调用都会新建一个事件循环。如果你写成这样:
async def scrape(url):
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
await page.goto(url)
title = await page.title()
await browser.close()
return title
<h1>❌ 错误:每个 scrape 都启动新浏览器,开销极大</h1><p>results = [asyncio.run(scrape(u)) for u in urls]
</p>
不仅会报 RuntimeError: Event loop is closed,还会反复启停浏览器,完全失去并发意义。
必须复用 browser 实例,且用 asyncio.gather() 调度任务
Playwright 的 browser.new_page() 是轻量级的,但 playwright.chromium.launch() 是重操作。正确做法是:单次 launch → 多个 page 并发 → 统一 close。关键点:
- 把
browser作为参数传入协程,不要在每个任务里重新 launch - 所有
page.goto()、page.content()等操作都应加超时(如timeout=30000),否则一个页面卡住会拖垮整组任务 - 避免在 page 上调用同步阻塞方法(如
time.sleep()),会阻塞整个事件循环
示例结构:
async def fetch_page(browser, url):
page = await browser.new_page()
try:
await page.goto(url, timeout=30000)
return await page.title()
except Exception as e:
return f"error: {e}"
finally:
await page.close()
<p>async def main():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True) # ✅ 复用 browser
tasks = [fetch_page(browser, u) for u in urls]
results = await asyncio.gather(*tasks, return_exceptions=True)
await browser.close() # ✅ 统一关闭
return results
</p>
chromium 启动失败常见于缺少系统依赖或 headless 环境缺失
在 Linux 服务器或 Docker 中运行时,playwright.chromium.launch() 容易报错:
-
TimeoutError: Timeout 30000ms exceeded:大概率是缺字体、缺共享库,不是网络问题 -
OSError: Executable doesn't exist at ...:未执行playwright install chromium - headless 模式下渲染异常(白屏、JS 不执行):需加
args=["--no-sandbox", "--disable-setuid-sandbox"]
推荐启动参数(Linux 生产环境):
await p.chromium.launch(
headless=True,
args=[
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-gpu",
"--disable-dev-shm-usage",
]
)
并发数别盲目设高,page 数量受 browser 进程内存限制
Playwright 的 browser 对应一个 Chromium 进程,每个 page 是该进程内的 renderer。实测发现:
- 单个 browser 实例稳定支持 10–20 个 page(取决于页面复杂度和内存)
- 超过 30 个 page 容易触发 Chromium OOM 或响应延迟飙升
- 若需更高并发,应按 batch 切分(例如每批 15 个 url),复用 browser 执行多轮
不建议用 asyncio.Semaphore 控制 page 数量——它只限流协程调度,无法缓解 Chromium 内存压力;真正要控的是 browser.new_page() 的并发峰值。
实际跑起来最易被忽略的,是没检查 playwright install 是否成功,以及把 browser 错误地放在了循环内创建。这两点一旦出错,表现出来的现象往往是“看起来在并发,其实全串行”,或者直接 crash。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











