Skip to content
阅读进度0%
知识库待复核

Playwright 深度解析:从浏览器自动化到动态爬虫、自动化测试与 AI Agent

CSDN 原文全文镜像:Playwright 是一个由微软开发的开源浏览器自动化框架,支持Chromium、Firefox和WebKit三大浏览器引擎,提供多语言接口。它不仅能用于Web自动化测试,还能处理动态网页采集、自动登录、iframe操作等复杂场景。相……

CSDN 原文镜像

本文为作者 CSDN 博客的全文镜像,原文发布于 2026-07-30。为适配本站结构,仅补充了站内元数据与来源说明,正文主体保持原文内容。

在这里插入图片描述

前言

在传统爬虫中,我们通常使用 requestshttpxcurl 向服务器发送 HTTP 请求,然后解析返回的 HTML。

这种方式简单、快速、资源消耗低,但它有一个明显的问题:

它只能获取服务器直接返回的内容,无法像真实用户一样操作浏览器。

现代网站大量使用 React、Vue、Angular 等前端框架。很多页面打开时,服务器只返回一个简单的 HTML 外壳,真正的数据需要浏览器执行 JavaScript、调用后端接口后才能渲染出来。

除此之外,很多业务系统还包含:

  • 登录状态与 Cookie;
  • 动态 Token;
  • iframe 嵌套页面;
  • 弹窗和新标签页;
  • 文件上传与下载;
  • 懒加载列表;
  • WebSocket 通信;
  • 验证码和风控检测;
  • 必须点击、滚动或输入后才加载的数据。

在这些场景下,仅使用 HTTP 请求库往往很难完成任务。

Playwright 正是为浏览器自动化而设计的工具。它能够通过统一的 API 控制 Chromium、Firefox 和 WebKit,并提供 TypeScript、JavaScript、Python、Java 和 .NET 等语言接口。目前 Playwright 不仅用于自动化测试,也广泛应用于浏览器脚本、动态网页采集和 AI Agent 浏览器操作。


一、Playwright 是什么

Playwright 是由 Microsoft 主导开发的开源浏览器自动化框架。

简单来说,Playwright 可以让程序像人一样操作浏览器,例如:

  • 打开网站;
  • 点击按钮;
  • 输入账号密码;
  • 登录系统;
  • 切换页面;
  • 操作 iframe;
  • 等待数据加载;
  • 监听接口请求;
  • 上传和下载文件;
  • 截图和录制视频;
  • 提取页面中的数据。

它支持以下三类核心浏览器引擎:

浏览器引擎常见浏览器
ChromiumChrome、Edge、Chromium
FirefoxMozilla Firefox
WebKitSafari 使用的浏览器引擎

Playwright 可以使用同一套 API 控制 Chromium、Firefox 和 WebKit,也可以运行 Chrome、Edge 等品牌浏览器,并支持模拟桌面端、平板和移动设备。

可以把 Playwright 理解为:

一套通过代码控制真实浏览器的工具。

它不是一个简单的 HTML 解析器,也不是普通的 HTTP 请求库,而是真正启动并控制浏览器进程。


二、Playwright 能做什么

2.1 Web 自动化测试

Playwright 最典型的用途是端到端测试,也就是 E2E 测试。

例如测试一个登录流程:

  1. 打开登录页面;
  2. 输入用户名;
  3. 输入密码;
  4. 点击登录;
  5. 验证是否进入首页;
  6. 验证用户名称是否正确显示。
python
<span class="token keyword">from</span> playwright<span class="token punctuation">.</span>sync_api <span class="token keyword">import</span> sync_playwright<span class="token punctuation">,</span> expect

<span class="token keyword">def</span> <span class="token function">test_login</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span>
<span class="token keyword">with</span> sync_playwright<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">as</span> p<span class="token punctuation">:</span>
browser <span class="token operator">=</span> p<span class="token punctuation">.</span>chromium<span class="token punctuation">.</span>launch<span class="token punctuation">(</span>headless<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">)</span>
page <span class="token operator">=</span> browser<span class="token punctuation">.</span>new_page<span class="token punctuation">(</span><span class="token punctuation">)</span>

page<span class="token punctuation">.</span>goto<span class="token punctuation">(</span><span class="token string">"https://example.com/login"</span><span class="token punctuation">)</span>

page<span class="token punctuation">.</span>get_by_label<span class="token punctuation">(</span><span class="token string">"用户名"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>fill<span class="token punctuation">(</span><span class="token string">"admin"</span><span class="token punctuation">)</span>
page<span class="token punctuation">.</span>get_by_label<span class="token punctuation">(</span><span class="token string">"密码"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>fill<span class="token punctuation">(</span><span class="token string">"123456"</span><span class="token punctuation">)</span>
page<span class="token punctuation">.</span>get_by_role<span class="token punctuation">(</span><span class="token string">"button"</span><span class="token punctuation">,</span> name<span class="token operator">=</span><span class="token string">"登录"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>click<span class="token punctuation">(</span><span class="token punctuation">)</span>

expect<span class="token punctuation">(</span>page<span class="token punctuation">)</span><span class="token punctuation">.</span>to_have_url<span class="token punctuation">(</span><span class="token string">"https://example.com/home"</span><span class="token punctuation">)</span>
expect<span class="token punctuation">(</span>page<span class="token punctuation">.</span>get_by_text<span class="token punctuation">(</span><span class="token string">"欢迎回来"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span>to_be_visible<span class="token punctuation">(</span><span class="token punctuation">)</span>

browser<span class="token punctuation">.</span>close<span class="token punctuation">(</span><span class="token punctuation">)</span>

这段代码不是直接调用登录接口,而是完整模拟用户操作浏览器。

因此,它能够验证的不仅是接口,还包括:

  • 页面是否正常渲染;
  • 前后端是否正确联调;
  • 按钮是否可点击;
  • 表单校验是否生效;
  • 页面跳转是否正确;
  • 登录状态是否保存;
  • 权限控制是否正确;
  • 不同浏览器中是否表现一致。

Playwright Test 本身还提供测试执行器、断言、自动等待、失败重试、并行执行和 Trace 调试等能力。


2.2 动态网页数据采集

Playwright 也经常被用于动态爬虫。

假设一个网页的初始 HTML 只有:

html
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>app<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/assets/index.js<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span>

真正的数据由 JavaScript 请求接口后渲染。

使用 requests 获取到的可能只是空壳页面,而 Playwright 会启动浏览器、加载 JavaScript、执行接口请求,最终拿到完整页面。

python
<span class="token keyword">from</span> playwright<span class="token punctuation">.</span>async_api <span class="token keyword">import</span> async_playwright

<span class="token keyword">async</span> <span class="token keyword">def</span> <span class="token function">fetch_page</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span>
<span class="token keyword">async</span> <span class="token keyword">with</span> async_playwright<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">as</span> p<span class="token punctuation">:</span>
browser <span class="token operator">=</span> <span class="token keyword">await</span> p<span class="token punctuation">.</span>chromium<span class="token punctuation">.</span>launch<span class="token punctuation">(</span>headless<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">)</span>

context <span class="token operator">=</span> <span class="token keyword">await</span> browser<span class="token punctuation">.</span>new_context<span class="token punctuation">(</span>
locale<span class="token operator">=</span><span class="token string">"zh-CN"</span><span class="token punctuation">,</span>
viewport<span class="token operator">=</span><span class="token punctuation">{<!-- --></span><span class="token string">"width"</span><span class="token punctuation">:</span> <span class="token number">1440</span><span class="token punctuation">,</span> <span class="token string">"height"</span><span class="token punctuation">:</span> <span class="token number">900</span><span class="token punctuation">}</span>
<span class="token punctuation">)</span>

page <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>new_page<span class="token punctuation">(</span><span class="token punctuation">)</span>

<span class="token keyword">await</span> page<span class="token punctuation">.</span>goto<span class="token punctuation">(</span>
<span class="token string">"https://example.com"</span><span class="token punctuation">,</span>
wait_until<span class="token operator">=</span><span class="token string">"domcontentloaded"</span>
<span class="token punctuation">)</span>

<span class="token keyword">await</span> page<span class="token punctuation">.</span>locator<span class="token punctuation">(</span><span class="token string">".result-list"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>wait_for<span class="token punctuation">(</span><span class="token punctuation">)</span>

items <span class="token operator">=</span> <span class="token keyword">await</span> page<span class="token punctuation">.</span>locator<span class="token punctuation">(</span><span class="token string">".result-item"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>all_inner_texts<span class="token punctuation">(</span><span class="token punctuation">)</span>

<span class="token keyword">print</span><span class="token punctuation">(</span>items<span class="token punctuation">)</span>

<span class="token keyword">await</span> context<span class="token punctuation">.</span>close<span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">await</span> browser<span class="token punctuation">.</span>close<span class="token punctuation">(</span><span class="token punctuation">)</span>

这类方式适合处理:

  • JavaScript 动态渲染页面;
  • 无限滚动列表;
  • 点击“查看更多”后加载的数据;
  • 登录后才能访问的页面;
  • 需要切换筛选条件的页面;
  • 数据位于 iframe 中的页面;
  • 通过接口异步加载内容的页面。

不过,Playwright 的资源消耗通常比 requests 更高。因此,更合理的采集架构往往是:

text
普通静态页面

requests / httpx 直接请求

动态页面或登录页面

Playwright 获取登录状态、接口地址或页面内容

发现稳定数据接口

尽可能切换回 HTTP 客户端批量请求

也就是说:

Playwright 不一定要承担全部采集工作,它也可以负责打开页面、完成登录和发现接口,再把后续批量请求交给更轻量的 HTTP 客户端。


2.3 自动登录和登录状态保存

很多系统需要登录后才能访问。

Playwright 可以操作登录页面,也可以保存 Cookie、LocalStorage 等浏览器状态。

python
context <span class="token operator">=</span> <span class="token keyword">await</span> browser<span class="token punctuation">.</span>new_context<span class="token punctuation">(</span><span class="token punctuation">)</span>

page <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>new_page<span class="token punctuation">(</span><span class="token punctuation">)</span>

<span class="token keyword">await</span> page<span class="token punctuation">.</span>goto<span class="token punctuation">(</span><span class="token string">"https://example.com/login"</span><span class="token punctuation">)</span>

<span class="token keyword">await</span> page<span class="token punctuation">.</span>get_by_label<span class="token punctuation">(</span><span class="token string">"账号"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>fill<span class="token punctuation">(</span><span class="token string">"admin"</span><span class="token punctuation">)</span>
<span class="token keyword">await</span> page<span class="token punctuation">.</span>get_by_label<span class="token punctuation">(</span><span class="token string">"密码"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>fill<span class="token punctuation">(</span><span class="token string">"123456"</span><span class="token punctuation">)</span>
<span class="token keyword">await</span> page<span class="token punctuation">.</span>get_by_role<span class="token punctuation">(</span><span class="token string">"button"</span><span class="token punctuation">,</span> name<span class="token operator">=</span><span class="token string">"登录"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>click<span class="token punctuation">(</span><span class="token punctuation">)</span>

<span class="token keyword">await</span> page<span class="token punctuation">.</span>wait_for_url<span class="token punctuation">(</span><span class="token string">"**/home"</span><span class="token punctuation">)</span>

<span class="token keyword">await</span> context<span class="token punctuation">.</span>storage_state<span class="token punctuation">(</span>path<span class="token operator">=</span><span class="token string">"auth.json"</span><span class="token punctuation">)</span>

下次执行时,可以直接加载登录状态:

python
context <span class="token operator">=</span> <span class="token keyword">await</span> browser<span class="token punctuation">.</span>new_context<span class="token punctuation">(</span>
storage_state<span class="token operator">=</span><span class="token string">"auth.json"</span>
<span class="token punctuation">)</span>

这样就不需要每次重新登录。

Playwright 的官方认证方案允许保存并复用已认证状态,从而减少重复登录并提高执行速度。需要注意的是,认证状态文件可能包含敏感 Cookie 和 Header,不应该提交到公开代码仓库。


2.4 操作 iframe

很多政务网站、企业系统和老旧平台都会使用 iframe。

页面结构可能是:

html
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>html</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>body</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>iframe</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>content-frame<span class="token punctuation">"</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/detail/content<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>iframe</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>body</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>html</span><span class="token punctuation">></span></span>

正文并不在主页面中,而在 iframe 里。

Playwright 可以通过 frame_locator 进入 iframe:

python
frame <span class="token operator">=</span> page<span class="token punctuation">.</span>frame_locator<span class="token punctuation">(</span><span class="token string">"#content-frame"</span><span class="token punctuation">)</span>

title <span class="token operator">=</span> <span class="token keyword">await</span> frame<span class="token punctuation">.</span>locator<span class="token punctuation">(</span><span class="token string">"h1"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>inner_text<span class="token punctuation">(</span><span class="token punctuation">)</span>
content <span class="token operator">=</span> <span class="token keyword">await</span> frame<span class="token punctuation">.</span>locator<span class="token punctuation">(</span><span class="token string">".article-content"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>inner_text<span class="token punctuation">(</span><span class="token punctuation">)</span>

Playwright 的 FrameLocator 专门用于定位 iframe,并继续查找 iframe 内部的元素。

这对动态采集非常重要。

例如有些网站的详情页实际上分为两层:

text
外层壳页面
└── iframe
└── 真正的正文页面

如果直接在外层页面执行:

python
<span class="token keyword">await</span> page<span class="token punctuation">.</span>locator<span class="token punctuation">(</span><span class="token string">"body"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>inner_text<span class="token punctuation">(</span><span class="token punctuation">)</span>

拿到的可能是导航栏、菜单、版权信息和 iframe 周边文本,而不是正文。

更合理的方式是:

  1. 判断页面中是否存在 iframe;
  2. 找到当前激活的正文 iframe;
  3. 只在该 iframe 内提取正文;
  4. iframe 不存在时,再回退到主页面提取。

2.5 监听和拦截网络请求

Playwright 不只能操作 DOM,还可以监听浏览器发送的网络请求。

python
page<span class="token punctuation">.</span>on<span class="token punctuation">(</span>
<span class="token string">"request"</span><span class="token punctuation">,</span>
<span class="token keyword">lambda</span> request<span class="token punctuation">:</span> <span class="token keyword">print</span><span class="token punctuation">(</span>
<span class="token string">"请求:"</span><span class="token punctuation">,</span>
request<span class="token punctuation">.</span>method<span class="token punctuation">,</span>
request<span class="token punctuation">.</span>url
<span class="token punctuation">)</span>
<span class="token punctuation">)</span>

page<span class="token punctuation">.</span>on<span class="token punctuation">(</span>
<span class="token string">"response"</span><span class="token punctuation">,</span>
<span class="token keyword">lambda</span> response<span class="token punctuation">:</span> <span class="token keyword">print</span><span class="token punctuation">(</span>
<span class="token string">"响应:"</span><span class="token punctuation">,</span>
response<span class="token punctuation">.</span>status<span class="token punctuation">,</span>
response<span class="token punctuation">.</span>url
<span class="token punctuation">)</span>
<span class="token punctuation">)</span>

这项能力在动态页面分析中非常实用。

例如页面上显示了一张列表,但 DOM 结构非常复杂。此时可以监听页面加载过程,找到真正返回 JSON 数据的接口:

python
<span class="token keyword">async</span> <span class="token keyword">with</span> page<span class="token punctuation">.</span>expect_response<span class="token punctuation">(</span>
<span class="token keyword">lambda</span> response<span class="token punctuation">:</span> <span class="token string">"/api/project/list"</span> <span class="token keyword">in</span> response<span class="token punctuation">.</span>url
<span class="token punctuation">)</span> <span class="token keyword">as</span> response_info<span class="token punctuation">:</span>
<span class="token keyword">await</span> page<span class="token punctuation">.</span>get_by_role<span class="token punctuation">(</span><span class="token string">"button"</span><span class="token punctuation">,</span> name<span class="token operator">=</span><span class="token string">"查询"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>click<span class="token punctuation">(</span><span class="token punctuation">)</span>

response <span class="token operator">=</span> <span class="token keyword">await</span> response_info<span class="token punctuation">.</span>value
data <span class="token operator">=</span> <span class="token keyword">await</span> response<span class="token punctuation">.</span>json<span class="token punctuation">(</span><span class="token punctuation">)</span>

相比直接从页面文本中提取,读取接口 JSON 通常更加稳定。

Playwright 还支持:

  • 修改请求 Header;
  • 阻止图片、字体和视频;
  • Mock API 返回值;
  • 修改接口响应;
  • 监听 WebSocket;
  • Mock WebSocket 通信;
  • 模拟接口异常和超时。

Playwright 官方网络能力覆盖 HTTP、HTTPS 和 WebSocket 的监听、修改与模拟。

例如,采集时可以阻止图片加载:

python
<span class="token keyword">async</span> <span class="token keyword">def</span> <span class="token function">handle_route</span><span class="token punctuation">(</span>route<span class="token punctuation">)</span><span class="token punctuation">:</span>
resource_type <span class="token operator">=</span> route<span class="token punctuation">.</span>request<span class="token punctuation">.</span>resource_type

<span class="token keyword">if</span> resource_type <span class="token keyword">in</span> <span class="token punctuation">{<!-- --></span><span class="token string">"image"</span><span class="token punctuation">,</span> <span class="token string">"font"</span><span class="token punctuation">,</span> <span class="token string">"media"</span><span class="token punctuation">}</span><span class="token punctuation">:</span>
<span class="token keyword">await</span> route<span class="token punctuation">.</span>abort<span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">else</span><span class="token punctuation">:</span>
<span class="token keyword">await</span> route<span class="token punctuation">.</span>continue_<span class="token punctuation">(</span><span class="token punctuation">)</span>

<span class="token keyword">await</span> page<span class="token punctuation">.</span>route<span class="token punctuation">(</span><span class="token string">"**/*"</span><span class="token punctuation">,</span> handle_route<span class="token punctuation">)</span>

这样可以减少网络流量,但需要谨慎使用。有些页面会通过图片、字体或其他资源的加载结果判断页面状态,过度拦截可能导致页面逻辑异常。


2.6 文件上传与下载

Playwright 可以自动操作文件上传组件:

python
<span class="token keyword">await</span> page<span class="token punctuation">.</span>get_by_label<span class="token punctuation">(</span><span class="token string">"上传文件"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>set_input_files<span class="token punctuation">(</span>
<span class="token string">"documents/report.pdf"</span>
<span class="token punctuation">)</span>

也可以监听文件下载:

python
<span class="token keyword">async</span> <span class="token keyword">with</span> page<span class="token punctuation">.</span>expect_download<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">as</span> download_info<span class="token punctuation">:</span>
<span class="token keyword">await</span> page<span class="token punctuation">.</span>get_by_role<span class="token punctuation">(</span><span class="token string">"button"</span><span class="token punctuation">,</span> name<span class="token operator">=</span><span class="token string">"导出"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>click<span class="token punctuation">(</span><span class="token punctuation">)</span>

download <span class="token operator">=</span> <span class="token keyword">await</span> download_info<span class="token punctuation">.</span>value

<span class="token keyword">await</span> download<span class="token punctuation">.</span>save_as<span class="token punctuation">(</span>
<span class="token string-interpolation"><span class="token string">f"downloads/</span><span class="token interpolation"><span class="token punctuation">{<!-- --></span>download<span class="token punctuation">.</span>suggested_filename<span class="token punctuation">}</span></span><span class="token string">"</span></span>
<span class="token punctuation">)</span>

Playwright 会为下载事件创建对应的 Download 对象,可以获取文件名称、下载地址并保存文件。下载文件默认与产生它的 BrowserContext 生命周期相关。

因此,它非常适合:

  • 自动下载报表;
  • 自动导出 Excel;
  • 上传标书或附件;
  • 批量上传文档;
  • 验证上传下载功能;
  • 自动化处理后台管理系统。

2.7 截图、视频和 Trace

Playwright 可以对页面进行截图:

python
<span class="token keyword">await</span> page<span class="token punctuation">.</span>screenshot<span class="token punctuation">(</span>
path<span class="token operator">=</span><span class="token string">"page.png"</span><span class="token punctuation">,</span>
full_page<span class="token operator">=</span><span class="token boolean">True</span>
<span class="token punctuation">)</span>

也可以只截取某个元素:

python
<span class="token keyword">await</span> page<span class="token punctuation">.</span>locator<span class="token punctuation">(</span><span class="token string">".article-content"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>screenshot<span class="token punctuation">(</span>
path<span class="token operator">=</span><span class="token string">"article.png"</span>
<span class="token punctuation">)</span>

官方截图 API 支持全页面截图、指定区域截图和将图片读取为内存 Buffer。

除了截图,Playwright 还支持录制浏览器视频和 Trace。

Trace 可以记录:

  • 每一步执行的操作;
  • 操作前后的页面状态;
  • DOM 快照;
  • 网络请求;
  • Console 日志;
  • 页面截图;
  • 操作耗时;
  • 使用的 Locator;
  • 错误发生的位置。
bash
npx playwright <span class="token builtin class-name">test</span> <span class="token parameter variable">--trace</span> on

运行完成后可以打开 Trace:

bash
npx playwright show-trace trace.zip

Trace Viewer 是 Playwright 提供的可视化分析工具,特别适合排查 CI 环境中偶发失败的问题。


2.8 模拟不同设备和网络环境

Playwright 可以模拟:

  • 手机和平板;
  • 不同屏幕尺寸;
  • 不同 User-Agent;
  • 不同语言;
  • 不同时区;
  • 地理位置;
  • 摄像头和麦克风权限;
  • 深色模式;
  • 离线状态;
  • 自定义 HTTP Header。
python
context <span class="token operator">=</span> <span class="token keyword">await</span> browser<span class="token punctuation">.</span>new_context<span class="token punctuation">(</span>
viewport<span class="token operator">=</span><span class="token punctuation">{<!-- --></span>
<span class="token string">"width"</span><span class="token punctuation">:</span> <span class="token number">390</span><span class="token punctuation">,</span>
<span class="token string">"height"</span><span class="token punctuation">:</span> <span class="token number">844</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
user_agent<span class="token operator">=</span><span class="token string">"Mozilla/5.0 ..."</span><span class="token punctuation">,</span>
locale<span class="token operator">=</span><span class="token string">"zh-CN"</span><span class="token punctuation">,</span>
timezone_id<span class="token operator">=</span><span class="token string">"Asia/Shanghai"</span><span class="token punctuation">,</span>
color_scheme<span class="token operator">=</span><span class="token string">"dark"</span>
<span class="token punctuation">)</span>

这使 Playwright 不仅能测试桌面网页,还能验证移动端适配、国际化、权限和网络异常场景。浏览器和 BrowserContext 级别均可以配置设备模拟、网络和录制能力。


2.9 作为 AI Agent 的浏览器执行工具

随着 AI Agent 的发展,Playwright 也逐渐成为浏览器 Agent 的基础执行工具。

一个浏览器 Agent 的工作流程可能是:

text
用户提出任务

大模型理解目标

读取当前页面结构

判断下一步操作

调用 Playwright 点击、输入或跳转

获取新的页面状态

继续推理和执行

例如用户说:

登录后台,找到昨天失败的任务,把错误日志整理出来。

Agent 可以通过 Playwright:

  1. 打开系统;
  2. 填写账号密码;
  3. 进入任务中心;
  4. 选择时间范围;
  5. 筛选失败任务;
  6. 打开任务详情;
  7. 读取错误信息;
  8. 返回总结。

Microsoft 目前提供了 Playwright MCP Server,使大模型能够通过 Model Context Protocol 调用浏览器自动化能力。官方实现主要使用结构化的可访问性快照帮助模型理解页面,而不是完全依赖截图和视觉识别。


三、Playwright 的核心对象模型

学习 Playwright 时,最重要的是理解以下几个核心对象:

text
Playwright
├── Chromium
├── Firefox
└── WebKit

Browser

BrowserContext

Page

Frame / Locator

3.1 Playwright

Playwright 是整个框架的入口。

python
<span class="token keyword">async</span> <span class="token keyword">with</span> async_playwright<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">as</span> p<span class="token punctuation">:</span>
browser <span class="token operator">=</span> <span class="token keyword">await</span> p<span class="token punctuation">.</span>chromium<span class="token punctuation">.</span>launch<span class="token punctuation">(</span><span class="token punctuation">)</span>

这里的 p 就是 Playwright 实例。

它提供三个主要的浏览器类型:

python
p<span class="token punctuation">.</span>chromium
p<span class="token punctuation">.</span>firefox
p<span class="token punctuation">.</span>webkit

3.2 Browser

Browser 表示一个浏览器进程。

python
browser <span class="token operator">=</span> <span class="token keyword">await</span> p<span class="token punctuation">.</span>chromium<span class="token punctuation">.</span>launch<span class="token punctuation">(</span>
headless<span class="token operator">=</span><span class="token boolean">True</span>
<span class="token punctuation">)</span>

常见配置包括:

python
browser <span class="token operator">=</span> <span class="token keyword">await</span> p<span class="token punctuation">.</span>chromium<span class="token punctuation">.</span>launch<span class="token punctuation">(</span>
headless<span class="token operator">=</span><span class="token boolean">False</span><span class="token punctuation">,</span>
slow_mo<span class="token operator">=</span><span class="token number">500</span>
<span class="token punctuation">)</span>

其中:

  • headless=True:无界面运行;
  • headless=False:显示浏览器界面;
  • slow_mo=500:每一步操作放慢 500 毫秒。

在开发和排查问题时,可以使用有界面模式;在服务器和生产环境中,通常使用无头模式。


3.3 BrowserContext

BrowserContext 是 Playwright 非常重要的设计。

可以把它理解为一个独立的无痕浏览器环境。

每个 BrowserContext 都有自己独立的:

  • Cookie;
  • LocalStorage;
  • SessionStorage;
  • 页面;
  • 权限;
  • User-Agent;
  • 网络规则;
  • 登录状态。
python
context_1 <span class="token operator">=</span> <span class="token keyword">await</span> browser<span class="token punctuation">.</span>new_context<span class="token punctuation">(</span><span class="token punctuation">)</span>
context_2 <span class="token operator">=</span> <span class="token keyword">await</span> browser<span class="token punctuation">.</span>new_context<span class="token punctuation">(</span><span class="token punctuation">)</span>

即使它们共用同一个 Browser 进程,也不会共享登录状态。

Playwright 使用 BrowserContext 实现测试隔离,每个测试可以拥有独立的浏览器上下文,从而降低状态污染和级联失败。

这意味着执行批量任务时,不一定要为每个任务都启动一个新的浏览器进程。

更常见的方式是:

text
一个 Browser
├── Context A:账号 A
├── Context B:账号 B
└── Context C:游客状态

这样既实现状态隔离,也能减少频繁启动浏览器的成本。


3.4 Page

Page 表示浏览器中的一个标签页或弹窗页面。

python
page <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>new_page<span class="token punctuation">(</span><span class="token punctuation">)</span>

一个 BrowserContext 可以包含多个 Page。

python
page_1 <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>new_page<span class="token punctuation">(</span><span class="token punctuation">)</span>
page_2 <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>new_page<span class="token punctuation">(</span><span class="token punctuation">)</span>

Playwright 中的 Page 可以:

  • 打开 URL;
  • 获取 HTML;
  • 执行 JavaScript;
  • 操作页面元素;
  • 监听请求;
  • 监听弹窗;
  • 监听下载;
  • 截图;
  • 获取 Console 日志。

官方将 Page 定义为 BrowserContext 中的一个标签页或弹出窗口。


3.5 Locator

Locator 是 Playwright 元素定位机制的核心。

python
login_button <span class="token operator">=</span> page<span class="token punctuation">.</span>get_by_role<span class="token punctuation">(</span>
<span class="token string">"button"</span><span class="token punctuation">,</span>
name<span class="token operator">=</span><span class="token string">"登录"</span>
<span class="token punctuation">)</span>

这里的 login_button 不是某一个固定 DOM 节点,而是一种“如何找到这个元素”的描述。

执行操作时,Playwright 会重新查找当前最新的 DOM 元素:

python
<span class="token keyword">await</span> login_button<span class="token punctuation">.</span>hover<span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">await</span> login_button<span class="token punctuation">.</span>click<span class="token punctuation">(</span><span class="token punctuation">)</span>

即使两次操作之间页面发生了重新渲染,Playwright 也会在每次操作前重新解析 Locator,而不是一直引用旧的 DOM 节点。

这对 React、Vue 等动态更新 DOM 的页面非常重要。


四、Playwright 的底层工作原理

4.1 整体调用链路

Playwright 的工作过程可以抽象为:

开发者编写:

python
<span class="token keyword">await</span> page<span class="token punctuation">.</span>get_by_role<span class="token punctuation">(</span>
<span class="token string">"button"</span><span class="token punctuation">,</span>
name<span class="token operator">=</span><span class="token string">"登录"</span>
<span class="token punctuation">)</span><span class="token punctuation">.</span>click<span class="token punctuation">(</span><span class="token punctuation">)</span>

背后会经历以下过程:

  1. Python 客户端接收调用;
  2. 将操作转换为 Playwright 协议消息;
  3. Playwright Driver 接收消息;
  4. Driver 将操作发送给对应浏览器;
  5. 浏览器查找元素并执行点击;
  6. 浏览器产生页面、网络和事件变化;
  7. Driver 将结果返回给 Python 客户端;
  8. Python 代码继续执行。

以 Playwright Python 为例,官方代码仓库说明,Python 客户端通过管道向内置的 Node.js Driver 发送 JSON 消息,通信协议由 Playwright 上游协议定义。

因此,Python 版本并不是完全用 Python 重新实现了一套浏览器控制核心。

更准确的理解是:

text
Python API

Playwright Driver

浏览器

Java 和 .NET 等语言版本也采用类似的语言绑定与 Driver 通信方式。


4.2 Playwright 并不只是 CDP 的简单封装

CDP 是 Chrome DevTools Protocol,也就是 Chromium 系浏览器使用的调试协议。

Playwright 确实支持通过 connect_over_cdp 连接现有 Chromium 浏览器:

python
browser <span class="token operator">=</span> <span class="token keyword">await</span> p<span class="token punctuation">.</span>chromium<span class="token punctuation">.</span>connect_over_cdp<span class="token punctuation">(</span>
<span class="token string">"http://localhost:9222"</span>
<span class="token punctuation">)</span>

但 CDP 连接仅适用于 Chromium 浏览器。Playwright 官方还明确指出,通过 CDP 建立的连接,相比 Playwright 自己的协议连接功能完整度更低;复杂功能更适合使用 Playwright 原生连接方式。

因此,不能简单地把 Playwright 理解为:

在 CDP 外面封装了一层 API。

更准确的说法是:

Playwright 提供统一的上层 API 和自己的通信协议,再针对 Chromium、Firefox 和 WebKit 实现浏览器控制能力。


4.3 事件驱动模型

浏览器中的很多行为不是同步发生的。

例如点击按钮后,可能产生:

  • 页面跳转;
  • 弹出新窗口;
  • 文件下载;
  • 接口请求;
  • iframe 加载;
  • WebSocket 消息;
  • DOM 更新。

因此 Playwright 大量使用事件驱动机制。

例如等待新标签页:

python
<span class="token keyword">async</span> <span class="token keyword">with</span> context<span class="token punctuation">.</span>expect_page<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">as</span> page_info<span class="token punctuation">:</span>
<span class="token keyword">await</span> page<span class="token punctuation">.</span>get_by_text<span class="token punctuation">(</span><span class="token string">"打开详情"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>click<span class="token punctuation">(</span><span class="token punctuation">)</span>

new_page <span class="token operator">=</span> <span class="token keyword">await</span> page_info<span class="token punctuation">.</span>value

等待下载:

python
<span class="token keyword">async</span> <span class="token keyword">with</span> page<span class="token punctuation">.</span>expect_download<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">as</span> download_info<span class="token punctuation">:</span>
<span class="token keyword">await</span> page<span class="token punctuation">.</span>get_by_text<span class="token punctuation">(</span><span class="token string">"下载"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>click<span class="token punctuation">(</span><span class="token punctuation">)</span>

download <span class="token operator">=</span> <span class="token keyword">await</span> download_info<span class="token punctuation">.</span>value

等待接口响应:

python
<span class="token keyword">async</span> <span class="token keyword">with</span> page<span class="token punctuation">.</span>expect_response<span class="token punctuation">(</span>
<span class="token keyword">lambda</span> response<span class="token punctuation">:</span> <span class="token string">"/api/detail"</span> <span class="token keyword">in</span> response<span class="token punctuation">.</span>url
<span class="token punctuation">)</span> <span class="token keyword">as</span> response_info<span class="token punctuation">:</span>
<span class="token keyword">await</span> page<span class="token punctuation">.</span>get_by_text<span class="token punctuation">(</span><span class="token string">"查看详情"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>click<span class="token punctuation">(</span><span class="token punctuation">)</span>

response <span class="token operator">=</span> <span class="token keyword">await</span> response_info<span class="token punctuation">.</span>value

这里有一个重要原则:

先注册等待事件,再触发页面操作。

错误写法:

python
<span class="token keyword">await</span> page<span class="token punctuation">.</span>get_by_text<span class="token punctuation">(</span><span class="token string">"下载"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>click<span class="token punctuation">(</span><span class="token punctuation">)</span>
download <span class="token operator">=</span> <span class="token keyword">await</span> page<span class="token punctuation">.</span>wait_for_event<span class="token punctuation">(</span><span class="token string">"download"</span><span class="token punctuation">)</span>

点击完成时,下载事件可能已经发生,后续等待就可能超时。

正确写法:

python
<span class="token keyword">async</span> <span class="token keyword">with</span> page<span class="token punctuation">.</span>expect_download<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">as</span> download_info<span class="token punctuation">:</span>
<span class="token keyword">await</span> page<span class="token punctuation">.</span>get_by_text<span class="token punctuation">(</span><span class="token string">"下载"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>click<span class="token punctuation">(</span><span class="token punctuation">)</span>

五、Playwright 为什么比传统自动化脚本更稳定

5.1 自动等待

传统浏览器脚本中经常出现大量固定等待:

python
<span class="token keyword">await</span> page<span class="token punctuation">.</span>click<span class="token punctuation">(</span><span class="token string">"#submit"</span><span class="token punctuation">)</span>
<span class="token keyword">await</span> asyncio<span class="token punctuation">.</span>sleep<span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span>

问题在于:

  • 网络快时,浪费三秒;
  • 网络慢时,三秒仍然不够;
  • CI 环境负载变化时容易偶发失败。

Playwright 在执行点击等操作之前,会自动检查元素状态。

以点击为例,Playwright 通常会检查:

  • 元素是否已经找到;
  • 元素是否可见;
  • 元素位置是否稳定;
  • 元素是否启用;
  • 元素是否能够接收点击事件;
  • 元素是否被其他弹窗或遮罩层挡住。

只有满足操作条件后,Playwright 才会执行点击。

python
<span class="token keyword">await</span> page<span class="token punctuation">.</span>get_by_role<span class="token punctuation">(</span>
<span class="token string">"button"</span><span class="token punctuation">,</span>
name<span class="token operator">=</span><span class="token string">"提交"</span>
<span class="token punctuation">)</span><span class="token punctuation">.</span>click<span class="token punctuation">(</span><span class="token punctuation">)</span>

这行代码背后实际上包含了查找、等待、状态检查、滚动和点击等过程。


5.2 自动重试断言

Playwright 的 Web First Assertions 也会自动重试。

python
expect<span class="token punctuation">(</span>page<span class="token punctuation">.</span>locator<span class="token punctuation">(</span><span class="token string">".status"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span>to_have_text<span class="token punctuation">(</span>
<span class="token string">"处理完成"</span>
<span class="token punctuation">)</span>

如果页面暂时显示的是:

text
处理中

Playwright 不会立即判定失败,而是会在超时时间内持续检查,直到:

text
处理完成

或者等待超时。

Playwright 官方断言支持对元素可见性、文本、属性、状态、URL 和响应等条件进行自动重试。


5.3 Locator 会重新解析元素

传统代码可能先找到 DOM 元素,然后长期持有这个引用。

但 React 或 Vue 重新渲染后,原来的元素可能已经被删除并替换。

Playwright 推荐使用 Locator:

python
button <span class="token operator">=</span> page<span class="token punctuation">.</span>get_by_role<span class="token punctuation">(</span>
<span class="token string">"button"</span><span class="token punctuation">,</span>
name<span class="token operator">=</span><span class="token string">"提交"</span>
<span class="token punctuation">)</span>

<span class="token keyword">await</span> button<span class="token punctuation">.</span>hover<span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">await</span> button<span class="token punctuation">.</span>click<span class="token punctuation">(</span><span class="token punctuation">)</span>

每次真正执行操作时,Locator 都会根据当前页面重新寻找元素,从而减少旧元素引用导致的问题。


六、Playwright 常见定位方式

Playwright 支持多种元素定位方式。

6.1 根据角色定位

python
page<span class="token punctuation">.</span>get_by_role<span class="token punctuation">(</span>
<span class="token string">"button"</span><span class="token punctuation">,</span>
name<span class="token operator">=</span><span class="token string">"登录"</span>
<span class="token punctuation">)</span>

适合按钮、链接、输入框、复选框等标准元素。


6.2 根据 Label 定位

python
page<span class="token punctuation">.</span>get_by_label<span class="token punctuation">(</span><span class="token string">"用户名"</span><span class="token punctuation">)</span>
page<span class="token punctuation">.</span>get_by_label<span class="token punctuation">(</span><span class="token string">"密码"</span><span class="token punctuation">)</span>

适合表单元素。


6.3 根据文本定位

python
page<span class="token punctuation">.</span>get_by_text<span class="token punctuation">(</span><span class="token string">"查看详情"</span><span class="token punctuation">)</span>

6.4 根据 Placeholder 定位

python
page<span class="token punctuation">.</span>get_by_placeholder<span class="token punctuation">(</span><span class="token string">"请输入关键词"</span><span class="token punctuation">)</span>

6.5 根据 Test ID 定位

python
page<span class="token punctuation">.</span>get_by_test_id<span class="token punctuation">(</span><span class="token string">"submit-button"</span><span class="token punctuation">)</span>

前端页面:

html
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name">data-testid</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>submit-button<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
提交
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span>

6.6 CSS 选择器

python
page<span class="token punctuation">.</span>locator<span class="token punctuation">(</span><span class="token string">".article-list .article-item"</span><span class="token punctuation">)</span>

6.7 XPath

python
page<span class="token punctuation">.</span>locator<span class="token punctuation">(</span>
<span class="token string">"xpath=//button[contains(text(),'提交')]"</span>
<span class="token punctuation">)</span>

虽然 XPath 能力很强,但通常不应该优先使用过长的绝对 XPath:

python
<span class="token operator">/</span>html<span class="token operator">/</span>body<span class="token operator">/</span>div<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token operator">/</span>div<span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">]</span><span class="token operator">/</span>div<span class="token operator">/</span>button

页面结构稍微变化,定位就可能失效。

Playwright 官方最佳实践建议优先使用用户可感知的属性和明确契约,例如 Role、Label、Text 和 Test ID,而不是依赖脆弱的 DOM 层级。


七、一个相对完整的 Python 示例

下面使用 Playwright 完成:

  1. 启动浏览器;
  2. 创建独立上下文;
  3. 打开页面;
  4. 监听接口;
  5. 点击查询;
  6. 提取列表;
  7. 保存截图;
  8. 捕获异常;
  9. 释放资源。
python
<span class="token keyword">import</span> asyncio
<span class="token keyword">from</span> pathlib <span class="token keyword">import</span> Path

<span class="token keyword">from</span> playwright<span class="token punctuation">.</span>async_api <span class="token keyword">import</span> <span class="token punctuation">(</span>
async_playwright<span class="token punctuation">,</span>
TimeoutError <span class="token keyword">as</span> PlaywrightTimeoutError<span class="token punctuation">,</span>
<span class="token punctuation">)</span>

<span class="token keyword">async</span> <span class="token keyword">def</span> <span class="token function">collect_data</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">list</span><span class="token punctuation">[</span><span class="token builtin">dict</span><span class="token punctuation">[</span><span class="token builtin">str</span><span class="token punctuation">,</span> <span class="token builtin">str</span><span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">:</span>
output_dir <span class="token operator">=</span> Path<span class="token punctuation">(</span><span class="token string">"artifacts"</span><span class="token punctuation">)</span>
output_dir<span class="token punctuation">.</span>mkdir<span class="token punctuation">(</span>parents<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">,</span> exist_ok<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">)</span>

<span class="token keyword">async</span> <span class="token keyword">with</span> async_playwright<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">as</span> playwright<span class="token punctuation">:</span>
browser <span class="token operator">=</span> <span class="token keyword">await</span> playwright<span class="token punctuation">.</span>chromium<span class="token punctuation">.</span>launch<span class="token punctuation">(</span>
headless<span class="token operator">=</span><span class="token boolean">True</span>
<span class="token punctuation">)</span>

context <span class="token operator">=</span> <span class="token keyword">await</span> browser<span class="token punctuation">.</span>new_context<span class="token punctuation">(</span>
locale<span class="token operator">=</span><span class="token string">"zh-CN"</span><span class="token punctuation">,</span>
viewport<span class="token operator">=</span><span class="token punctuation">{<!-- --></span>
<span class="token string">"width"</span><span class="token punctuation">:</span> <span class="token number">1440</span><span class="token punctuation">,</span>
<span class="token string">"height"</span><span class="token punctuation">:</span> <span class="token number">900</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>

page <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>new_page<span class="token punctuation">(</span><span class="token punctuation">)</span>

<span class="token keyword">try</span><span class="token punctuation">:</span>
<span class="token keyword">await</span> page<span class="token punctuation">.</span>goto<span class="token punctuation">(</span>
<span class="token string">"https://example.com/projects"</span><span class="token punctuation">,</span>
wait_until<span class="token operator">=</span><span class="token string">"domcontentloaded"</span><span class="token punctuation">,</span>
timeout<span class="token operator">=</span><span class="token number">30_000</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>

keyword_input <span class="token operator">=</span> page<span class="token punctuation">.</span>get_by_placeholder<span class="token punctuation">(</span>
<span class="token string">"请输入项目名称"</span>
<span class="token punctuation">)</span>

<span class="token keyword">await</span> keyword_input<span class="token punctuation">.</span>fill<span class="token punctuation">(</span><span class="token string">"人工智能"</span><span class="token punctuation">)</span>

<span class="token keyword">async</span> <span class="token keyword">with</span> page<span class="token punctuation">.</span>expect_response<span class="token punctuation">(</span>
<span class="token keyword">lambda</span> response<span class="token punctuation">:</span> <span class="token punctuation">(</span>
<span class="token string">"/api/project/list"</span> <span class="token keyword">in</span> response<span class="token punctuation">.</span>url
<span class="token keyword">and</span> response<span class="token punctuation">.</span>status <span class="token operator">==</span> <span class="token number">200</span>
<span class="token punctuation">)</span><span class="token punctuation">,</span>
timeout<span class="token operator">=</span><span class="token number">20_000</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span> <span class="token keyword">as</span> response_info<span class="token punctuation">:</span>
<span class="token keyword">await</span> page<span class="token punctuation">.</span>get_by_role<span class="token punctuation">(</span>
<span class="token string">"button"</span><span class="token punctuation">,</span>
name<span class="token operator">=</span><span class="token string">"查询"</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token punctuation">.</span>click<span class="token punctuation">(</span><span class="token punctuation">)</span>

response <span class="token operator">=</span> <span class="token keyword">await</span> response_info<span class="token punctuation">.</span>value
response_data <span class="token operator">=</span> <span class="token keyword">await</span> response<span class="token punctuation">.</span>json<span class="token punctuation">(</span><span class="token punctuation">)</span>

<span class="token keyword">await</span> page<span class="token punctuation">.</span>locator<span class="token punctuation">(</span>
<span class="token string">".project-list"</span>
<span class="token punctuation">)</span><span class="token punctuation">.</span>wait_for<span class="token punctuation">(</span>
state<span class="token operator">=</span><span class="token string">"visible"</span><span class="token punctuation">,</span>
timeout<span class="token operator">=</span><span class="token number">20_000</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>

rows <span class="token operator">=</span> page<span class="token punctuation">.</span>locator<span class="token punctuation">(</span><span class="token string">".project-item"</span><span class="token punctuation">)</span>
count <span class="token operator">=</span> <span class="token keyword">await</span> rows<span class="token punctuation">.</span>count<span class="token punctuation">(</span><span class="token punctuation">)</span>

results<span class="token punctuation">:</span> <span class="token builtin">list</span><span class="token punctuation">[</span><span class="token builtin">dict</span><span class="token punctuation">[</span><span class="token builtin">str</span><span class="token punctuation">,</span> <span class="token builtin">str</span><span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>

<span class="token keyword">for</span> index <span class="token keyword">in</span> <span class="token builtin">range</span><span class="token punctuation">(</span>count<span class="token punctuation">)</span><span class="token punctuation">:</span>
row <span class="token operator">=</span> rows<span class="token punctuation">.</span>nth<span class="token punctuation">(</span>index<span class="token punctuation">)</span>

title <span class="token operator">=</span> <span class="token keyword">await</span> row<span class="token punctuation">.</span>locator<span class="token punctuation">(</span>
<span class="token string">".project-title"</span>
<span class="token punctuation">)</span><span class="token punctuation">.</span>inner_text<span class="token punctuation">(</span><span class="token punctuation">)</span>

date <span class="token operator">=</span> <span class="token keyword">await</span> row<span class="token punctuation">.</span>locator<span class="token punctuation">(</span>
<span class="token string">".project-date"</span>
<span class="token punctuation">)</span><span class="token punctuation">.</span>inner_text<span class="token punctuation">(</span><span class="token punctuation">)</span>

results<span class="token punctuation">.</span>append<span class="token punctuation">(</span>
<span class="token punctuation">{<!-- --></span>
<span class="token string">"title"</span><span class="token punctuation">:</span> title<span class="token punctuation">.</span>strip<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token string">"date"</span><span class="token punctuation">:</span> date<span class="token punctuation">.</span>strip<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span>
<span class="token punctuation">)</span>

<span class="token keyword">await</span> page<span class="token punctuation">.</span>screenshot<span class="token punctuation">(</span>
path<span class="token operator">=</span>output_dir <span class="token operator">/</span> <span class="token string">"result.png"</span><span class="token punctuation">,</span>
full_page<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>

<span class="token keyword">print</span><span class="token punctuation">(</span>
<span class="token string">"接口返回数据量:"</span><span class="token punctuation">,</span>
<span class="token builtin">len</span><span class="token punctuation">(</span>response_data<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"data"</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>

<span class="token keyword">return</span> results

<span class="token keyword">except</span> PlaywrightTimeoutError<span class="token punctuation">:</span>
<span class="token keyword">await</span> page<span class="token punctuation">.</span>screenshot<span class="token punctuation">(</span>
path<span class="token operator">=</span>output_dir <span class="token operator">/</span> <span class="token string">"timeout.png"</span><span class="token punctuation">,</span>
full_page<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>
<span class="token keyword">raise</span> RuntimeError<span class="token punctuation">(</span><span class="token string">"页面操作超时"</span><span class="token punctuation">)</span>

<span class="token keyword">finally</span><span class="token punctuation">:</span>
<span class="token keyword">await</span> context<span class="token punctuation">.</span>close<span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">await</span> browser<span class="token punctuation">.</span>close<span class="token punctuation">(</span><span class="token punctuation">)</span>

<span class="token keyword">if</span> __name__ <span class="token operator">==</span> <span class="token string">"__main__"</span><span class="token punctuation">:</span>
data <span class="token operator">=</span> asyncio<span class="token punctuation">.</span>run<span class="token punctuation">(</span>collect_data<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>

<span class="token keyword">for</span> item <span class="token keyword">in</span> data<span class="token punctuation">:</span>
<span class="token keyword">print</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span>

八、Playwright 在生产环境中的架构设计

直接写一个 Playwright 脚本并不难,难的是将它建设成稳定的生产系统。

推荐将系统拆分为以下几层:

8.1 Browser 不要频繁启动

错误方式:

text
每来一个任务

启动浏览器

执行任务

关闭浏览器

浏览器启动属于相对昂贵的操作。

更合理的方式是:

text
Worker 启动

创建 Browser

任务到达

创建 BrowserContext

执行任务

关闭 BrowserContext

复用 Browser

但是 Browser 也不能永久运行,需要配置:

  • 最大任务数;
  • 最大存活时间;
  • 内存阈值;
  • 页面崩溃检测;
  • Browser 重启机制。

8.2 任务之间使用 BrowserContext 隔离

不同用户和任务不应该共用同一个 Context,否则可能产生:

  • Cookie 串用;
  • 登录状态污染;
  • LocalStorage 污染;
  • 页面残留;
  • 权限相互影响。

推荐:

text
Browser:进程级复用
BrowserContext:任务级或账号级隔离
Page:具体页面操作

8.3 设置分层超时

不要只有一个总超时。

建议分别设置:

text
任务总超时
├── 页面打开超时
├── 元素等待超时
├── 接口等待超时
├── 下载超时
└── 单步操作超时

例如:

python
context<span class="token punctuation">.</span>set_default_timeout<span class="token punctuation">(</span><span class="token number">10_000</span><span class="token punctuation">)</span>
context<span class="token punctuation">.</span>set_default_navigation_timeout<span class="token punctuation">(</span><span class="token number">30_000</span><span class="token punctuation">)</span>

任务调度层还应设置更高层的总超时,避免整个 Worker 被某个任务长期占用。


8.4 做好可观测性

生产环境中的 Playwright 任务失败时,只保存一条异常日志通常不够。

至少应该保存:

  • 当前 URL;
  • 任务 ID;
  • 页面标题;
  • 异常堆栈;
  • 页面截图;
  • 页面 HTML;
  • Console 日志;
  • 失败请求;
  • Trace;
  • 浏览器版本;
  • 脚本版本;
  • 账号或会话标识。

这样才能回答:

是页面改版了、接口失败了、登录失效了、元素被遮挡了,还是浏览器崩溃了?


8.5 设计任务状态机

一个 Playwright 任务通常不是简单的成功或失败。

可以设计为:

text
PENDING

RUNNING

LOGIN_REQUIRED

COLLECTING

PARSING

SUCCESS

异常状态包括:

text
TIMEOUT
AUTH_EXPIRED
PAGE_CHANGED
NETWORK_ERROR
BROWSER_CRASHED
CAPTCHA_REQUIRED
FAILED

通过明确的状态机,可以决定哪些错误能够自动重试,哪些错误需要人工介入。


九、Playwright 的局限性

Playwright 很强,但并不是万能的。

9.1 资源消耗较大

每个浏览器进程和页面都需要消耗:

  • CPU;
  • 内存;
  • 文件描述符;
  • 网络连接;
  • 临时磁盘空间。

如果同时启动几百个浏览器实例,服务器很容易出现资源耗尽。

因此,需要限制:

  • Worker 数量;
  • Browser 数量;
  • Context 数量;
  • Page 数量;
  • 单任务并发;
  • 单浏览器存活时间。

9.2 无法天然解决验证码

Playwright 可以操作验证码页面,但它并不能天然解决:

  • 图片验证码;
  • 滑块验证码;
  • 短信验证码;
  • 扫码登录;
  • 行为验证;
  • 人机验证。

遇到这些场景时,通常需要:

  • 人工介入;
  • 官方接口;
  • 合法授权的账号体系;
  • 验证码识别服务;
  • 会话状态复用。

9.3 不能天然绕过所有反爬系统

使用真实浏览器不等于完全无法被识别。

网站可能综合判断:

  • IP;
  • Cookie;
  • 浏览器指纹;
  • 请求频率;
  • 鼠标轨迹;
  • 页面停留时间;
  • 账号行为;
  • TLS 指纹;
  • Header;
  • 访问路径;
  • 浏览器自动化特征。

因此,Playwright 的定位应该是:

浏览器自动化工具,而不是万能的风控绕过工具。

使用时也应遵守目标网站的服务条款、数据授权范围和相关法律法规。


9.4 页面改版仍然会导致脚本失效

如果页面按钮名称、DOM 结构或业务流程发生变化,自动化脚本仍然可能失败。

稳定性需要依赖:

  • 更可靠的 Locator;
  • 页面版本监控;
  • 多种提取策略;
  • 失败截图;
  • Trace;
  • 结构变化检测;
  • 回退逻辑;
  • 自动化回归测试。

十、Playwright、HTTP 请求和人工浏览器如何选择

场景推荐方案
静态 HTML 页面requests、httpx
已知并且稳定的 JSON 接口直接请求接口
JavaScript 动态渲染页面Playwright
必须登录才能访问Playwright 或复用登录 Cookie
iframe 页面Playwright
需要点击、输入、滚动Playwright
高并发批量接口采集HTTP 客户端
自动化回归测试Playwright Test
自动下载后台报表Playwright
AI Agent 操作网页Playwright、Playwright MCP
复杂验证码或人工审批人机协同

一个成熟系统通常不会只使用一种方式。

更合理的组合是:

text
Playwright
负责登录、交互、页面分析和接口发现

HTTP 客户端
负责稳定接口的高并发数据请求

HTML 解析器
负责结构化提取

任务队列
负责并发、重试和调度

对象存储
负责保存截图、HTML、下载文件和 Trace

十一、最佳实践总结

在实际项目中使用 Playwright,可以重点遵循以下原则。

第一,优先使用 Locator,不要长期持有旧的 DOM ElementHandle。

第二,优先使用 Role、Label、Text 和 Test ID,尽量避免脆弱的绝对 XPath。

第三,减少固定 sleep,依赖 Locator 自动等待、接口事件和明确的页面状态。

第四,一个 Browser 可以复用,但不同任务尽量使用独立 BrowserContext。

第五,动态页面优先分析网络接口,不要所有数据都从页面文本中硬解析。

第六,出现 iframe 时,应明确进入目标 iframe,而不是直接扫描整个外层页面。

第七,等待下载、弹窗和接口时,要先注册事件,再触发操作。

第八,生产环境必须保存截图、HTML、Console、网络错误和 Trace。

第九,对浏览器、Context、Page 和任务设置资源上限与超时。

第十,不要把 Playwright 当作万能反爬工具,应在授权和合规范围内使用。


十二、结语

Playwright 的价值并不只是“自动点击网页”。

它真正解决的是:

如何让程序稳定地进入一个真实浏览器环境,并观察、操作和验证现代 Web 应用。

从能力上看,Playwright 横跨了多个领域:

text
自动化测试
动态网页采集
后台流程自动化
文件上传下载
接口调试与 Mock
页面截图与录制
浏览器 Agent
AI 自动化执行

它的核心优势来自几个关键设计:

  • 统一控制 Chromium、Firefox 和 WebKit;
  • 使用 BrowserContext 实现会话隔离;
  • 使用 Locator 应对动态 DOM;
  • 通过自动等待减少脚本偶发失败;
  • 通过网络监听获取页面背后的真实数据;
  • 通过 Trace 提升自动化任务的可调试性;
  • 通过 MCP 等方式成为 AI Agent 的浏览器执行层。

如果只是采集一个简单的静态页面,使用 Playwright 可能显得过重。

但当你面对登录系统、动态渲染、iframe、复杂交互、文件下载、浏览器测试或 AI Agent 操作网页时,Playwright 往往是目前最值得掌握的浏览器自动化工具之一。

基于 VitePress 构建