Playwright Python SDK 默认不支持 asyncio,需使用 async_api 模块并全程 await;networkidle 易失效,应优先等待具体元素;防反爬需禁用 automation 特征、模拟用户行为。

Playwright 能不能直接用 asyncio 运行
不能,Playwright 的 Python SDK 默认是同步 API,底层用的是 Chromium 的 DevTools 协议,但它的 playwright.sync_api 模块不支持 await;强行在 async 函数里调 page.goto() 会卡住事件循环,甚至抛出 RuntimeError: This event loop is already running。
正确做法是用 Playwright 提供的异步入口:必须从 playwright.async_api 导入,且整个流程(launch、context、page)都得走 async/await 链路。
- 别写
from playwright.sync_api import sync_playwright,改用from playwright.async_api import async_playwright -
async_playwright().start()必须用async with包裹,否则浏览器进程不会自动关闭 - 所有 page 方法如
page.goto()、page.wait_for_selector()都是协程,必须 await
asyncio.create_task() 和 asyncio.gather() 怎么选
两者都能并发跑多个页面,但行为差异明显:前者适合任务间有依赖或需单独错误处理,后者更简洁但任一失败就全崩。
比如抓 10 个动态商品页,若某页加载超时或 JS 报错,用 asyncio.gather() 会让其他 9 个也中断;而用 asyncio.create_task() 可配合 try/except 分离捕获。
立即学习“Python免费学习笔记(深入)”;
- 批量无依赖请求 → 用
asyncio.gather(*tasks, return_exceptions=True),加return_exceptions=True防止一个失败拖垮全部 - 需要按顺序等某页完成再启下一页 → 用
await asyncio.create_task(...)显式控制流 - 想限制并发数(防被封)→ 别直接
gather全部,改用asyncio.Semaphore(3)包住每个 task
为什么 page.wait_for_load_state('networkidle') 经常失效
这个状态判断基于“网络请求数连续 500ms ≤ 2”,但现代 SPA(如 React/Vue)常靠 WebSocket、fetch 轮询或 IntersectionObserver 触发懒加载,根本不会触发 networkidle。结果就是代码卡在等待,或误判为加载完成却没渲染出目标元素。
- 优先换
page.wait_for_selector("text=立即购买", state="visible"),等具体元素出现比等网络更可靠 - 若目标元素由 JS 异步插入,加 timeout(默认 30s 太长),比如
timeout=8000 - 避免用
page.wait_for_timeout()硬等,它不检测状态,纯阻塞,会拖慢整体吞吐 - 部分网站用
requestIdleCallback延迟执行,可搭配page.evaluate("() => window.requestIdleCallback(() => {}, { timeout: 5000 })")主动触发
如何避免 Playwright 被识别为自动化工具
Playwright 默认 User-Agent 和行为特征太明显,Cloudflare、极验等风控系统一眼识别。光改 UA 不够,得模拟真实用户交互节奏和环境。
- 启动浏览器时加参数:
args=["--disable-blink-features=AutomationControlled"],并用page.add_init_script()删除navigator.webdriver - 不要一打开就猛点或快速滚动,加
await page.wait_for_timeout(800)在 goto 后停顿,再用page.mouse.move()模拟随机位移 - 禁用图片节省带宽和时间:
context = await browser.new_context(**{"bypass_csp": True, "ignore_https_errors": True}),再设await context.route("**/*.{png,jpg,gif}", lambda route: route.abort())
真正难的不是启动几个协程,而是每个页面里 JS 执行时机、资源加载顺序、反爬钩子触发条件——这些没法靠通用模式覆盖,得针对目标站点 inspect 后调整等待点和注入逻辑。


















