Playwright 全方位教程(理论、架构、执行原理与实战)
Playwright 全方位教程(理论、架构、执行原理与实战)
适用于 1.42+ 版本,示例以 Python 与 Node.js 为主,聚焦核心概念与最佳实践。
1. Playwright 是什么?
- 多浏览器统一控制层:一次 API 调用同时支持 Chromium / Firefox / WebKit。
- 自动化 + 端到端测试:内置测试框架(@playwright/test)、录制、截图/视频、追踪。
- 反检测更友好:默认隐藏
navigator.webdriver、提供伪装器、支持持久化上下文。 - 强一致性:同一 API 在不同浏览器呈现一致语义,减少跨浏览器差异。
2. 核心概念与对象模型
BrowserType→Browser→BrowserContext→Page→Frame→ElementHandle。- BrowserContext:独立的 Cookie/Storage 作用域,类似无痕窗口;推荐“一测一上下文”。
- Page:标签页;所有交互(点击、输入、评估脚本)都在 Page 层执行。
- Selectors:内置 CSS / XPath / Text / Role / ID / 自定义 Locator;
locator()懒求值,天然重试。 - Network 控制:拦截/修改请求、模拟响应、注入 headers/cookies、路由 mock。
- StorageState:导出/导入会话,复用登录态。
3. 内部架构与运行原理
- 多进程模型:
- Playwright Client(用户代码) → Node/Python/… binding → Playwright driver → 浏览器进程。
- 通过 WebSocket/CDP/自定义协议驱动浏览器(Chromium: CDP;Firefox/WebKit: PW 自协议)。
- 自动等待 (auto-wait):
- Action(click/fill/press)会等待元素可见、可操作、无遮挡;断言/expect 会轮询直至满足或超时。
- 事件循环与隔离:
- Playwright 在单独的执行器中维护与浏览器的连接,避免用户脚本阻塞协议层。
- 定位与重试机制:
locator在执行时解析,失败自动重试;page.wait_for_*精准等待特定事件/状态。
- 录制与追踪:
- Trace 记录网络、控制台、DOM 变化、截图;可用
trace viewer回放。
- Trace 记录网络、控制台、DOM 变化、截图;可用
4. 典型执行流程
- 启动:
chromium.launch()或launch_persistent_context()(持久化用户数据)。 - 创建上下文:配置 UA、视口、代理、时区、地理位置、权限、存储状态。
- 新建 Page:
context.new_page()。 - 导航与等待:
page.goto(url, wait_until="networkidle")+ 显式等待目标元素。 - 交互:点击、输入、上传、拖拽、滚动、执行 JS。
- 断言/提取:
locator.text_content()、expect(locator).to_have_text()。 - 资源:截图、PDF、下载、网络拦截、trace 采集。
- 关闭:
context.close()、browser.close()。
5. 配置要点(常用参数速查)
- launch:
headless、args(附加 flags)、proxy、slow_mo、timeout。 - context:
viewport、user_agent、device_scale_factor、locale/timezone/geolocation、permissions、storage_state、record_video/screenshot、bypass_csp。 - page.goto:
wait_until(load/domcontentloaded/networkidle/commit)、timeout、referer。 - locator:
locator(selector, has_text=..., has=...),组合筛选;nth()选取序号。 - 网络:
page.route拦截、context.set_extra_http_headers、page.wait_for_request/response。
6. 反爬与稳定性实践
- 指纹:自定义 UA、时区、语言、视口;必要时用持久化上下文携带真实 Profile。
- 资源节流:禁用无关资源(图片/字体)、
route拦截第三方域名,降低干扰。 - 等待策略:优先语义等待(
locator.wait_for、expect),少用固定sleep。 - 重试与超时:设置全局
set_default_timeout,在关键操作链外层加重试。 - 代理与并发:为 IP 敏感站点设置住宅代理,控制并发与访问节奏。
7. Python 实战示例
import asyncio from playwright.async_api import async_playwright asyncdefmain():asyncwith async_playwright()as p: browser =await p.chromium.launch(headless=True) context =await browser.new_context( viewport={"width":1366,"height":768}, user_agent="MyBot/1.0", timezone_id="America/Los_Angeles",) page =await context.new_page()await page.goto("https://example.com", wait_until="networkidle") title =await page.title()await page.screenshot(path="example.png", full_page=True)print("title=", title)await browser.close() asyncio.run(main())8. Node.js 实战示例(带测试断言)
import{ test, expect, chromium }from'@playwright/test';test('example.com title',async()=>{const browser =await chromium.launch({ headless:true});const context =await browser.newContext({ viewport:{ width:1280, height:720}});const page =await context.newPage();await page.goto('https://example.com',{ waitUntil:'networkidle'});awaitexpect(page).toHaveTitle(/Example Domain/);await page.screenshot({ path:'example.png', fullPage:true});await browser.close();});9. 进阶能力
- 录制脚本:
npx playwright codegen https://example.com自动生成用例。 - Trace / Video / Screenshot:
context.tracing.start()→stop({ path });record_video生成 MP4。 - 网络 Mock:
page.route('**/api/**', route => route.fulfill({ status: 200, body: 'ok' }))。 - 下载与上传:监听
page.on("download"),上传用locator.set_input_files()。 - 多上下文/多用户:一个 Browser 内同时创建多个 Context,模拟不同登录态并行执行。
10. 常见问题排查
- 超时:提升
page.gototimeout,或改用networkidle/精确等待元素。 - 元素不可点击:检查遮挡、滚动、
force参数慎用;优先用 Locator + expect。 - 验证码/登录:导入
storage_state或使用持久化上下文;必要时人工打码/第三方服务。 - 证书/HTTPS:
ignore_https_errors=True(Context 级);或信任自签证书。 - 字体/渲染差异:在容器中安装所需字体,固定 locale 和 deviceScaleFactor。
11. 深挖关键技术点(高频问答版)
- Driver 做了什么:Playwright Driver 作为中间层进程,接收语言 SDK 调用→序列化协议→通过 WebSocket/CDP 发送给浏览器;同时接收浏览器事件→反序列化→回调用户代码,并管理会话、超时、自动等待、selector 解析、事件订阅等。
- Driver 与浏览器内核的通信机制:Chromium 走 CDP,Firefox/WebKit 走 PW 自协议;双向异步消息通道,包含指令、事件与结果,Driver 维护消息队列和回调映射。
- Browser / Context / Page 的真实含义:Browser=进程;Context=独立无痕窗口(隔离 Cookie/Storage/权限);Page=标签页,具体交互对象。设计目的是并行多用户/多会话且互不污染。
- 自动等待(Auto-wait)真相:交互 API 内置等待“存在+可见+可操作+无遮挡+稳定”,失败自动重试到超时;locator/expect 也轮询,减少显式 sleep。
- 为什么更反爬友好:默认隐藏 webdriver 痕迹,提供 stealth/undetected 适配;易配指纹(UA/时区/语言/viewport/持久化上下文);精细网络控制与脚本注入;可切换多内核(Chromium/Firefox/WebKit)。
- 核心设计哲学:一致性(跨浏览器同语义)、隔离性(Context)、可观测性(trace/video/screenshot/log)、默认可靠(auto-wait/locator)、安全可控(权限/超时/拦截/持久化)。
- 面试常问(示例回答):
网络拦截怎么做:基于 CDP/PW 协议的 Fetch/Route,在请求发出前拦截,可 continue/fulfill/abort/修改;响应也可拦截,常用于 mock、限流、去噪。 continue_:改写请求 fulfill:自定义响应 abort:强制终止示例:等待特定 URL 响应并解析 JSON(Python async)
import asyncio from playwright.async_api import async_playwright TARGET ="https://api.example.com/data"asyncdefmain():asyncwith async_playwright()as p: browser =await p.chromium.launch(headless=True) page =await browser.new_page()# 方式一:用 wait_for_response(等待并解析指定响应) wait_resp = page.wait_for_response(lambda r: r.url.startswith(TARGET))await page.goto("https://example.com") resp =await wait_resp data =await resp.json()print("resp status", resp.status,"data keys",list(data.keys()))# 方式二:route 拦截——查看/改写请求后放行asyncdefhandle_route(route): req = route.request if req.url.startswith(TARGET):print("hit target", req.method, req.url)await route.continue_( headers={**req.headers,"X-Debug":"1"})else:await route.continue_()await page.route(TARGET, handle_route)# 方式三:route 拦截 + fulfill 自定义响应(mock 接口)asyncdefmock_route(route): req = route.request if req.url.startswith("https://api.example.com/mock"):await route.fulfill( status=200, content_type="application/json", body='{"ok":true,"data":[1,2,3]}')else:await route.continue_()await page.route("**/mock", mock_route)# 方式四:拦截并强制终止(用于屏蔽第三方资源)await page.route("**/*.png",lambda route: route.abort())await page.goto("https://example.com")# 再等一遍真正的目标响应以便解析 resp =await page.wait_for_response(lambda r: r.url.startswith(TARGET))print("status:", resp.status,"body:",await resp.text())await browser.close() asyncio.run(main())Selector 引擎:内置 CSS/XPath/Text/Role/ID/nth/has/has-text;locator() 懒求值+重试;支持自定义 selector 引擎。常用 selector 示例(Python)
page.locator('css=div.article h1')# CSS page.locator('xpath=//button[@type="submit"]') page.locator('text=登录')# 文本匹配(精确) page.locator('text=/Log.?n/i')# 文本正则 page.get_by_role('button', name='Submit')# ARIA Role + 可见名 page.get_by_test_id('submit-btn')# data-testid 或 test id 属性 page.locator('#search').nth(0)# ID + nth page.locator('div.list >> nth=3')# nth 语法 page.locator('section:has(div.card)')# :has 子选择 page.locator('div.item', has_text='已支付')# has_text 过滤 page.locator('div.list >> text=详情')# 链式 text 过滤自定义 selector(Node.js,示意)
// 注册自定义选择器 engine(示例:按 data-qa 查找)const{ selectors, chromium }=require('playwright'); selectors.register('dataqa',{create(root, target){return root.querySelector(`[data-qa="${target}"]`);}});(async()=>{const browser =await chromium.launch();const page =await(await browser.newContext()).newPage();await page.goto('https://example.com');await page.locator('dataqa=my-button').click();await browser.close();})();为什么并发强:单 Browser 多 Context 多 Page;通信异步非阻塞;自动等待减少 sleep;可禁用多余资源降低成本,并可水平扩多个 Browser 进程。示例:单 Browser + 多 Context + 多 Page 并发抓取(Python async)
import asyncio from playwright.async_api import async_playwright URLS =[f"https://example.com/?q={i}"for i inrange(30)] BROWSER_HEADLESS =True CONTEXTS =3# 并行上下文数 PAGES_PER_CTX =5# 每个上下文的并发页数(总并发=CONTEXTS*PAGES_PER_CTX)asyncdeffetch_page(page, url):await page.goto(url, wait_until="networkidle") title =await page.title()return url, title asyncdefworker(ctx, urls): results =[] sem = asyncio.Semaphore(PAGES_PER_CTX)asyncdefrun(url):asyncwith sem: page =await ctx.new_page()try: results.append(await fetch_page(page, url))finally:await page.close()await asyncio.gather(*(run(u)for u in urls))return results asyncdefmain():asyncwith async_playwright()as p: browser =await p.chromium.launch(headless=BROWSER_HEADLESS) contexts =[await browser.new_context()for _ inrange(CONTEXTS)]# 均分 URL 到多个上下文 chunks =[URLS[i::CONTEXTS]for i inrange(CONTEXTS)] tasks =[asyncio.create_task(worker(ctx, chunk))for ctx, chunk inzip(contexts, chunks)] all_results =[]for t in tasks: all_results.extend(await t)for ctx in contexts:await ctx.close()await browser.close()for u, title in all_results:print(u,"->", title) asyncio.run(main())- Playwright vs Selenium:Playwright 提供多浏览器一致 API、内建自动等待、Context 隔离、多核支持(Chromium/Firefox/WebKit)、更易反检测;Selenium 兼容性广但等待/指纹需手工处理,跨浏览器差异更大。
- Context 的作用:独立的无痕窗口/会话容器,隔离 Cookie/Storage/权限;可并行多用户场景,避免跨用例污染,支持 storage_state 导入导出。
- Auto-wait 机制:交互动作会等待元素存在、可见、可操作、无遮挡并稳定;Locator/Expect 轮询直至超时,减少显式 sleep,降低 flaky。
- 网络 mock 实现:
page.route/context.route拦截请求,在回调中fulfill/continue/abort;可改 headers/body/status 或返回自定义响应;配合wait_for_request/response做验证。 - 什么是 CDP 协议:Chrome DevTools Protocol,Chromium 暴露的远程调试与控制接口(WebSocket JSON-RPC);支持 DOM 操作、网络拦截、性能剖析、截图等。Playwright 在 Chromium 下通过 CDP 驱动浏览器(Firefox/WebKit 用自协议)。
- 反爬与指纹伪装:设置 UA/语言/时区/视口/地理位置,使用持久化 Context 或 storage_state 复用真实会话,开启 stealth/undetected 适配,合理代理与并发节流,拦截第三方资源减少噪音。
- 排查 flaky:开启 trace/video/screenshot,收集 console/network 日志;改用语义等待(locator/expect),提升/分级超时;隔离上下文、禁用不稳定资源,必要时重试。
- 并发模型与优化:一个 Browser 多 Context 多 Page;控制
max_concurrency、禁图/字体、开启 headless、分片运行;必要时多 Browser 进程分布式执行。 - 登录态复用:使用
storage_state导出/导入,或launch_persistent_context复用用户数据目录;上下文级别设置 cookies/localStorage 后再打开页面。
11. 参考与工具
- 官方文档:https://playwright.dev
- API 速查:
npx playwright open --help/python -m playwright codegen --help - Trace Viewer:
npx playwright show-trace trace.zip - GitHub 示例:https://github.com/microsoft/playwright
掌握以上内容后,可快速从“写脚本”进阶到“稳定可维护的自动化/爬取/测试”实践:
- 用 Context 隔离状态,Locator 做可靠等待。
- 通过拦截与指纹配置应对反爬。
- 结合 Trace/Video/Screenshot 复现并定位问题。