1. 项目概述为什么Playwright是当下Web自动化的首选如果你最近在搞Web自动化、爬虫或者测试大概率听过Playwright这个名字。它不再是那个“微软出的新工具”而是已经成了很多团队和个人的默认选择。我自己从早期的Selenium到后来的Puppeteer再到全面转向Playwright感触最深的就是它把很多过去需要折腾半天、写一堆胶水代码才能搞定的事情变得异常简单。这个项目标题“Python Playwright 使用示例”涵盖的面很广从基础的浏览器控制到文件处理、移动端适配再到异步操作和测试框架集成几乎覆盖了Playwright在Python生态中的核心应用场景。这篇文章我就以一个过来人的身份把这些点串起来结合真实的代码和踩过的坑给你讲明白怎么用以及为什么要这么用。简单说Playwright是一个支持多浏览器Chromium, Firefox, WebKit的自动化库它提供了一套统一的API来控制浏览器进行页面导航、元素操作、网络拦截等。它的Python版本封装得很好异步支持原生而且自带等待机制能省去大量处理页面加载和元素出现时间的代码。对于新手你可以把它理解为一个更聪明、更快、更稳定的“机器人”能帮你自动完成在浏览器里做的各种事情比如填表单、点按钮、下载文件、截图甚至是模拟手机访问。对于有经验的开发者它的价值在于其强大的底层控制能力和与现代前端框架如React, Vue的出色兼容性。2. 环境准备与核心概念解析2.1 安装与初始化一步到位避开常见坑安装Playwright很简单但有几个细节决定了你后续的体验。首先确保你的Python版本在3.7以上。然后最推荐的方式是使用pip安装。pip install playwright安装完库之后千万不要直接开始写代码。Playwright需要下载它要控制的浏览器内核。这是最关键的一步很多人卡在这里。你需要运行以下命令来安装浏览器playwright install这个命令会下载Chromium、Firefox和WebKitSafari的开源核心三个浏览器。虽然看起来下载时间有点长但这是“一次痛苦终身受益”。我建议把这三个都装上因为不同项目、不同网站对浏览器的兼容性可能不同有备无患。如果你只想安装其中一个比如Chromium可以用playwright install chromium。注意在某些网络环境下下载可能会非常慢甚至失败。这时可以尝试设置环境变量来使用国内镜像加速例如set PLAYWRIGHT_DOWNLOAD_HOSThttps://npmmirror.com/mirrors/playwrightWindows或export PLAYWRIGHT_DOWNLOAD_HOSThttps://npmmirror.com/mirrors/playwrightMac/Linux然后再运行安装命令。安装完成后我强烈建议你验证一下。创建一个最简单的Python脚本import asyncio from playwright.async_api import async_playwright async def main(): async with async_playwright() as p: # 这里选择使用 chromium也可以换成 firefox 或 webkit browser await p.chromium.launch(headlessFalse) # headlessFalse 表示打开可见浏览器 page await browser.new_page() await page.goto(https://www.example.com) print(await page.title()) await browser.close() asyncio.run(main())如果能正常打开浏览器访问example.com并打印出标题那么恭喜你环境搭建成功了。这里引入了第一个核心概念异步操作。Playwright的Python API强烈推荐使用异步模式async/await因为浏览器操作本身就是I/O密集型的异步能极大提升效率尤其是在处理多个页面或并行任务时。上面的async_playwright上下文管理器和asyncio.run是标准的异步启动模式。2.2 同步与异步API的选择Playwright提供了两套API同步playwright.sync_api和异步playwright.async_api。怎么选异步API (async_api): 这是主流和推荐的方式尤其适用于爬虫、需要高并发的自动化任务或者你的项目本身就在一个异步框架如FastAPI、Sanic里。性能更好资源利用率更高。同步API (sync_api): 更简单直观写起来像传统的同步代码。适合写一些简单的脚本、教程或者当你对异步编程不熟悉时快速上手。但在处理多个页面时容易阻塞。我个人建议除非你的脚本非常简单不超过50行否则都从异步API开始学。虽然初期有点门槛但长远来看收益巨大。本文后续示例将主要使用异步API。3. 核心技能一精准的浏览器控制与页面交互浏览器控制是Playwright的基石其API设计得非常人性化。3.1 启动与导航不仅仅是打开网页启动浏览器时有一堆参数可以配置这些配置直接影响脚本的稳定性、速度和表现。import asyncio from playwright.async_api import async_playwright async def control_browser(): async with async_playwright() as p: # 启动浏览器配置参数详解 browser await p.chromium.launch( headlessFalse, # 显示浏览器窗口调试时非常有用 slow_mo500, # 将每个操作放慢500毫秒像慢动作便于观察调试 args[--disable-blink-featuresAutomationControlled] # 禁用WebDriver检测防反爬 ) # 创建浏览器上下文 (Context) 这是关键概念 context await browser.new_context( viewport{width: 1920, height: 1080}, user_agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ... # 自定义UA ) # 在上下文中创建新页面 page await context.new_page() # 导航到目标网址并等待特定条件 await page.goto(https://www.baidu.com, wait_untilnetworkidle) # 等待到网络空闲 # 更精确的等待等待某个特定元素出现 # await page.wait_for_selector(#kw, statevisible) # 模拟用户输入 await page.fill(#kw, Playwright Python教程) # fill 会先清空再输入 # 或者使用 type模拟逐个字符输入 # await page.type(#kw, Playwright, delay100) # 每个字符间隔100ms # 模拟点击 await page.click(#su) # 等待导航完成点击搜索后跳转 await page.wait_for_load_state(networkidle) # 获取页面内容 content await page.content() # 获取完整HTML # 或者获取文本 title await page.title() print(f页面标题: {title}) # 截图 await page.screenshot(pathsearch_result.png, full_pageTrue) # 截取整个滚动页 await browser.close() asyncio.run(control_browser())关键点解析browser.new_context()vsbrowser.new_page():Context上下文是一个独立的会话环境类似于一个隐身模式的浏览器窗口。每个Context拥有独立的cookie、缓存和本地存储。而Page页面是Context中的一个标签页。通常我们创建一个Context然后在其中创建多个Page。这样做的好处是隔离性好并且可以方便地应用全局设置如视口、UA。wait_until参数:page.goto()的wait_until参数决定了什么情况下算导航完成。‘load’默认等待load事件触发‘domcontentloaded’等待DOMContentLoaded事件‘networkidle’等待网络基本没有活动至少500ms。对于现代单页应用SPA‘networkidle’或结合wait_for_selector是更可靠的选择。fill与type:fill直接设置输入框的值效率高。type模拟键盘输入更真实但速度慢。根据目标网站是否有输入监听逻辑来选择。防检测:args[‘--disable-blink-featuresAutomationControlled’]和自定义user_agent是绕过一些简单反爬措施的基础手段。更高级的还需要处理WebGL、Canvas指纹等Playwright也提供了相应API如context.add_init_script注入JS。3.2 元素定位与操作多种选择器灵活运用Playwright支持丰富的选择器定位元素是其强项。# 接上面的 page 对象 # 1. CSS 选择器 (最常用) search_box await page.query_selector(input[namewd]) # 返回第一个匹配元素 all_links await page.query_selector_all(a.result-title) # 返回所有匹配元素列表 # 2. XPath first_item await page.query_selector(xpath//div[classresult]/h3/a) # 3. 文本选择器 (非常实用) await page.click(text登录) # 点击包含“登录”文本的元素 await page.click(text精确登录文本) # 点击文本完全等于“精确登录文本”的元素 # 4. 组合选择器 # 选择 id 为 form 的元素下第一个 type 为 submit 的 button await page.click(#form button[typesubmit]) # 或者使用 :has 伪类 await page.click(div.item:has(span.price)) # 选择包含 span.price 的 div.item # 元素操作 if search_box: await search_box.fill(关键词) await search_box.press(Enter) # 模拟键盘回车 # 获取元素属性、文本 link await page.query_selector(a.some-link) if link: href await link.get_attribute(href) text_content await link.text_content() inner_html await link.inner_html() print(f链接: {href}, 文本: {text_content})实操心得text选择器在定位没有固定id或class的按钮、链接时特别好用但要注意文本内容可能包含空格或换行。query_selector返回的是ElementHandle对象如果没找到元素会返回None所以操作前最好做一下判断避免NoneType错误。3.3 处理弹窗、框架和下拉菜单现代网页充满了各种交互组件。# 处理弹窗 (Dialog) page.on(dialog, lambda dialog: dialog.accept()) # 监听并自动接受所有弹窗 # 更精细的控制 async def handle_dialog(dialog): print(f弹窗信息: {dialog.message}) if 确认删除 in dialog.message: await dialog.accept() else: await dialog.dismiss() page.on(dialog, handle_dialog) # 处理新窗口/标签页 async with page.expect_popup() as popup_info: await page.click(a[target_blank]) # 点击会打开新窗口的链接 new_page await popup_info.value await new_page.wait_for_load_state() # ... 操作 new_page await new_page.close() # 操作 iframe frame page.frame(nameiframe-name) # 通过 name # 或 frame page.frame(urlr.*payment.*) # 通过 URL 正则匹配 if frame: await frame.fill(#card-number, 123456) # 在 frame 内定位元素需要从 frame 对象出发 # 操作下拉选择框 (Select) await page.select_option(#city-select, valueshanghai) # 通过 value 选择 await page.select_option(#city-select, label上海) # 通过显示文本选择 await page.select_option(#multi-select, value[opt1, opt2]) # 多选4. 核心技能二高效的文件上传与下载处理文件处理是自动化中常见的需求Playwright的处理方式非常直观。4.1 文件上传不再需要找隐藏的input传统自动化上传文件需要找到input type”file”元素并设置其值但现代前端可能用自定义组件隐藏了它。Playwright的set_input_files方法更强大。# 情况1标准文件输入框 await page.set_input_files(input[typefile], path/to/myfile.pdf) # 情况2多个文件 await page.set_input_files(input[typefile], [file1.jpg, file2.jpg]) # 情况3动态出现的上传组件例如点击后触发 # 先点击触发上传对话框实际上可能是一个隐藏的input被显示 await page.click(button.upload-button) # 然后直接设置文件Playwright会“绕过”系统的文件选择对话框 await page.set_input_files(input[typefile], myfile.pdf) # 情况4非input元素的上传如拖拽区域 # 有些组件监听的是 drop 事件。我们可以直接触发它。 file_input await page.query_selector(.drop-zone) if file_input: await file_input.set_input_files(myfile.pdf) # 或者如果组件非常复杂可以用更底层的 dispatch_event 模拟 drop # await page.dispatch_event(.drop-zone, drop, {‘dataTransfer’: {‘files’: [file]}}) # 伪代码实际需要构造 DataTransfer 对象关键点set_input_files是同步的它直接修改DOM不依赖操作系统对话框因此速度极快且稳定。如果页面上传逻辑是监听change事件这个方法会完美触发它。4.2 文件下载监听下载事件并保存处理下载比上传稍复杂因为需要监听浏览器发出的下载请求。import asyncio from playwright.async_api import async_playwright async def handle_download(): async with async_playwright() as p: browser await p.chromium.launch(headlessFalse) # 重要必须在创建 context 时接受下载 context await browser.new_context(accept_downloadsTrue) page await context.new_page() await page.goto(https://example.com/download-page) # 启动下载监听 async with page.expect_download() as download_info: # 执行会触发下载的操作比如点击下载链接或按钮 await page.click(a.download-link) # 或者有些下载是通过表单提交触发的 # await page.click(button[typesubmit]) # 获取下载对象 download await download_info.value # 获取下载建议的文件名 suggested_filename download.suggested_filename print(f正在下载: {suggested_filename}) # 指定保存路径 save_path f./downloads/{suggested_filename} # 等待下载完成并将文件保存到指定路径 await download.save_as(save_path) print(f文件已保存到: {save_path}) # 也可以获取文件的字节流进行处理而不保存到磁盘 # file_bytes await download.read() # with open(save_path, wb) as f: # f.write(file_bytes) await browser.close() asyncio.run(handle_download())注意事项accept_downloadsTrue是必须的否则浏览器会阻止下载。page.expect_download()返回一个上下文管理器它会在下载开始时触发。确保触发下载的操作如click在这个上下文管理器内部执行。download.save_as(path)会阻塞直到下载完成。对于大文件你可能需要结合异步操作来处理。下载路径的目录如./downloads/需要事先存在否则会报错。可以用os.makedirs(‘downloads’, exist_okTrue)来创建。5. 核心技能三移动端适配与设备模拟测试或爬取移动端页面时模拟真实的移动设备环境至关重要。Playwright内置了丰富的设备描述符。5.1 使用预设设备模拟from playwright.async_api import async_playwright import asyncio async def mobile_emulation(): async with async_playwright() as p: # 方法1使用 playwright.devices 字典中的预设 iphone_12 p.devices[iPhone 12] # 创建上下文时传入设备参数 browser await p.chromium.launch(headlessFalse) context await browser.new_context(**iphone_12) # 解包设备参数 page await context.new_page() await page.goto(https://m.baidu.com) # 此时页面视图、UA、触摸事件等都模拟了 iPhone 12 await page.screenshot(pathiphone12_view.png) await browser.close() asyncio.run(mobile_emulation())p.devices包含了数十种主流移动设备的配置如‘iPhone 11’,‘Pixel 5’,‘Galaxy S9’等。这些配置包括视口大小、设备缩放比例、是否支持触摸、用户代理字符串等。5.2 自定义移动端视图如果预设设备不满足需求可以手动配置上下文。async def custom_mobile_view(): async with async_playwright() as p: browser await p.chromium.launch(headlessFalse) context await browser.new_context( viewport{width: 375, height: 812}, # iPhone X 的典型尺寸 device_scale_factor3, # 设备像素比视网膜屏通常为2或3 is_mobileTrue, # 告诉网站这是移动设备 has_touchTrue, # 启用触摸事件 user_agentMozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15 ... ) page await context.new_page() await page.goto(https://example.com) # 模拟触摸滑动 await page.touchscreen.tap(100, 200) # 点击坐标 (100, 200) # 更复杂的滑动需要用到 page.mouse 模拟但设置 has_touchTrue 后页面会响应触摸事件。 await browser.close()移动端测试要点触摸事件设置has_touchTrue后Playwright会自动将鼠标事件转换为触摸事件。你也可以使用page.touchscreen进行更精确的触摸控制。地理定位可以模拟设备的地理位置用于测试基于位置的服务。await context.grant_permissions([geolocation]) await page.set_geolocation({latitude: 39.9042, longitude: 116.4074}) # 北京横屏模式交换viewport中的width和height即可模拟横屏。6. 核心技能四深入异步操作与并发控制Playwright的异步API与Python的asyncio深度集成利用好并发可以极大提升效率。6.1 单个浏览器上下文内的多页面并发在一个浏览器实例browser和一个上下文context内可以创建多个页面page并行工作。它们共享cookie和部分设置但拥有独立的DOM和执行环境。import asyncio from playwright.async_api import async_playwright async def worker(page, url): await page.goto(url) title await page.title() print(f{url} 的标题是: {title}) # 可以进行更多操作... return title async def concurrent_pages(): async with async_playwright() as p: browser await p.chromium.launch(headlessTrue) # 无头模式更节省资源 context await browser.new_context() urls [ https://www.example.com, https://www.github.com, https://www.python.org ] tasks [] for url in urls: page await context.new_page() # 为每个url创建一个页面和一个异步任务 task asyncio.create_task(worker(page, url)) tasks.append((task, page)) # 记住page对象以便后续关闭 # 等待所有任务完成 results await asyncio.gather(*[t for t, _ in tasks]) print(所有页面标题:, results) # 关闭所有页面 for _, page in tasks: await page.close() await browser.close() asyncio.run(concurrent_pages())6.2 多个浏览器上下文并发更高隔离性如果需要完全隔离的会话例如模拟多个独立用户应该为每个任务创建独立的browser context。async def worker_with_context(browser, url): # 每个worker有自己的上下文完全隔离 context await browser.new_context() page await context.new_page() try: await page.goto(url) title await page.title() print(f{url}: {title}) return title finally: await context.close() # 重要关闭上下文释放资源 async def concurrent_contexts(): async with async_playwright() as p: browser await p.chromium.launch(headlessTrue) urls [...] # 多个url tasks [] for url in urls: task asyncio.create_task(worker_with_context(browser, url)) tasks.append(task) results await asyncio.gather(*tasks) print(results) await browser.close()并发实践中的坑与技巧资源管理务必确保每个page和context在使用后被正确关闭page.close(),context.close()否则会导致内存泄漏。使用try...finally块或异步上下文管理器是好的实践。并发数限制虽然可以创建很多页面但受限于本地CPU、内存和网络带宽。盲目开几百个页面可能导致浏览器崩溃或脚本被目标网站封禁。通常根据任务类型和硬件将并发数控制在10-50个是比较稳妥的。可以使用asyncio.Semaphore来控制最大并发数。semaphore asyncio.Semaphore(10) # 最大并发10 async def limited_worker(url): async with semaphore: # ... 执行页面操作错误处理在并发任务中一个页面的错误不应该导致整个程序崩溃。用asyncio.gather(*tasks, return_exceptionsTrue)可以收集所有结果和异常然后统一处理。无头模式并发任务强烈建议使用headlessTrue不显示GUI能显著减少资源消耗。7. 核心技能五与Pytest测试框架深度集成Playwright不仅仅是一个自动化库它原生提供了对Pytest的强大支持让你能轻松编写结构清晰、功能强大的端到端E2E测试。7.1 安装与配置测试环境首先安装Playwright的Pytest插件pip install pytest-playwright然后你需要为测试安装浏览器如果之前没装过playwright installPytest插件会自动为你提供一些有用的fixture最核心的是page。7.2 编写第一个Pytest测试用例创建一个文件test_website.pyimport re import pytest # 测试类 class TestExampleWebsite: # 测试函数page fixture 由 pytest-playwright 自动注入 def test_homepage_title(self, page): page.goto(https://www.example.com) assert page.title() Example Domain def test_homepage_heading(self, page): page.goto(https://www.example.com) heading page.text_content(h1) assert heading Example Domain # 异步测试 pytest.mark.asyncio async def test_async_navigation(self, page): await page.goto(https://www.example.com) title await page.title() assert title Example Domain # 使用 fixture 进行测试前置和后置操作 pytest.fixture(scopefunction) def login_page(page): 每个测试函数前都会打开登录页并返回page对象 page.goto(https://example.com/login) yield page # 测试结束后可以清理比如退出登录 # page.click(#logout) def test_login_successful(login_page): login_page.fill(#username, testuser) login_page.fill(#password, testpass) login_page.click(button[typesubmit]) # 断言登录后跳转或出现特定元素 assert login_page.is_visible(.welcome-message)运行测试pytest test_website.py -v7.3 使用高级Fixture与配置Playwright的Pytest插件提供了多个级别的fixture用于精细控制浏览器和上下文。import pytest # 1. browser: 管理浏览器实例 def test_with_browser(browser): # 手动创建上下文和页面灵活性最高 context browser.new_context() page context.new_page() page.goto(https://example.com) # ... 测试逻辑 context.close() # 2. context: 管理浏览器上下文 def test_with_context(context): # 使用已创建的上下文 page context.new_page() page.goto(https://example.com) # ... 测试逻辑 # context 会在测试结束后自动关闭 # 3. page: 最常用直接使用已创建好的页面 (基于默认的context和browser) def test_with_page(page): page.goto(https://example.com) # ... 测试逻辑 # 在 conftest.py 中自定义 fixture # conftest.py import pytest from playwright.sync_api import Page pytest.fixture(scopesession) def browser_context_args(browser_context_args): 修改默认浏览器上下文的参数 return { **browser_context_args, viewport: {width: 1920, height: 1080}, ignore_https_errors: True, # 忽略HTTPS证书错误用于测试环境 } pytest.fixture def authenticated_page(page: Page): 创建一个已登录状态的页面fixture page.goto(https://example.com/login) page.fill(#username, admin) page.fill(#password, admin123) page.click(text登录) # 等待登录成功例如导航到首页 page.wait_for_url(**/dashboard) yield page # 测试后清理 page.click(#logout)7.4 录制测试代码与生成报告Playwright提供了一个强大的命令行工具来录制你的操作并生成测试代码。# 启动录制器打开Code Generator界面 playwright codegen https://example.com这会在浏览器中打开目标网站和一个录制窗口。你在浏览器中的所有操作点击、输入、导航都会被实时转换成Python或其它语言的Playwright代码。这是学习API和快速生成测试脚本原型的绝佳工具。对于测试报告Playwright可以自动录制测试过程中的视频、截图和追踪信息Trace。首先在pytest.ini或命令行中启用相关配置# pytest.ini [pytest] addopts --tbshort --screenshotonly-on-failure --videoretain-on-failure --tracingretain-on-failure或者在测试中手动添加def test_with_artifacts(page): # 开始录制追踪 context page.context context.tracing.start(screenshotsTrue, snapshotsTrue, sourcesTrue) # ... 执行测试步骤 ... # 如果测试失败保存追踪文件 if pytest.test_failed: context.tracing.stop(path “trace.zip”)当测试失败时会在test-results/目录下生成截图、视频和trace.zip文件。使用Playwright的命令行工具可以查看追踪playwright show-trace trace.zip这个追踪查看器是一个图形化界面可以一步步回放测试执行过程查看每个时刻的DOM状态、网络请求和Console日志是调试失败测试的利器。8. 常见问题排查与性能优化实战在实际使用中你肯定会遇到各种问题。这里总结一些高频问题和解决思路。8.1 元素定位失败超时与等待策略这是最常见的问题。页面还没加载完或者元素是动态生成的脚本就去操作了。解决方案使用智能等待Playwright的大部分操作如click,fill内部都有自动等待机制会等待元素可操作可见、稳定、可点击。但前提是选择器能找到元素。显式等待在操作前使用page.wait_for_selector(selector, state‘visible’, timeout10000)。state可以是‘attached’存在于DOM、‘visible’可见、‘hidden’等。等待导航在点击可能引发页面跳转的链接后使用page.wait_for_url(‘**/target-page’)或page.wait_for_load_state(‘networkidle’)。处理动态内容对于通过Ajax加载的内容等待某个标志性元素出现。# 点击“加载更多”按钮后等待新项目出现 await page.click(button.load-more) await page.wait_for_selector(.new-item:has-text(最新项目))超时设置全局设置page.set_default_timeout(30000)增加默认超时。或者在具体等待函数中传入timeout参数。8.2 脚本被网站检测为自动化工具越来越多的网站会检测Selenium、Playwright等自动化工具。应对措施由易到难基础伪装启动浏览器时添加args[‘--disable-blink-featuresAutomationControlled’]。使用自定义的、真实的user_agent。注入JS修改属性有些网站通过检测navigator.webdriver属性。await page.add_init_script( Object.defineProperty(navigator, webdriver, { get: () undefined }); )使用真实的浏览器配置文件通过user_data_dir参数指定一个包含真实用户数据书签、历史、Cookie的Chrome用户数据目录路径。这会让浏览器看起来更像一个真人使用的环境。context await browser.new_context( user_data_dir/path/to/your/chrome/profile )警告确保这个目录没有被正在运行的Chrome使用否则会损坏配置文件。模拟人类行为加入随机延迟、不规则的鼠标移动轨迹。Playwright本身提供slow_mo参数可以放慢所有操作。对于更高级的模拟可以使用page.mouse.move(x, y, steps10)让鼠标分步移动。终极方案如果对方防御非常严密可能需要研究其检测原理并编写更复杂的JS注入脚本来覆盖所有检测点或者考虑使用更底层的CDPChrome DevTools Protocol接口。但这已进入攻防对抗领域复杂度很高。8.3 性能优化与资源管理当脚本运行时间长或并发高时需要注意性能。重用浏览器实例避免在每个测试或任务中频繁启动和关闭浏览器。使用browser await p.chromium.launch()启动一次然后在整个脚本或测试会话中重复使用它来创建新的上下文和页面。合理使用无头模式headlessTrue可以节省大量GUI渲染资源速度更快。在调试时再切换为headlessFalse。拦截不必要的资源如果只需要HTML结构可以拦截图片、样式表、字体等大幅提升页面加载速度。await page.route(**/*.{png,jpg,jpeg,svg,gif,woff,woff2}, lambda route: route.abort()) # 或者更精细的控制 async def route_handler(route): if route.request.resource_type in [image, stylesheet, font]: await route.abort() else: await route.continue_() await page.route(**/*, route_handler)关闭不需要的页面及时调用page.close()释放内存。监控内存长时间运行的脚本可以定期检查并重启浏览器上下文防止内存泄漏。if some_memory_condition: await context.close() context await browser.new_context() page await context.new_page()8.4 网络请求拦截与模拟Playwright可以监听和修改页面发出的任何网络请求这对于测试和爬虫都非常有用。# 监听所有请求和响应 page.on(request, lambda request: print(f {request.method} {request.url})) page.on(response, lambda response: print(f {response.status} {response.url})) # 拦截并修改请求 await page.route(**/api/user, lambda route: route.continue_(headers{**route.request.headers, Authorization: Bearer fake-token})) # 拦截并模拟响应Mock await page.route(**/api/data.json, lambda route: route.fulfill( status200, content_typeapplication/json, bodyjson.dumps({mock: data}) # 返回模拟数据 )) # 捕获API响应数据 async def capture_response(response): if /api/data in response.url: data await response.json() print(f捕获到数据: {data}) page.on(response, capture_response)这个功能让你可以在不修改后端代码的情况下测试前端在各种网络情况如API失败、延迟、返回特定数据下的表现也可以用来绕过一些前端的数据加载逻辑直接获取数据。从基础的浏览器操作到复杂的并发控制和测试集成Playwright提供了一个强大而统一的工具箱。它的设计哲学是“为现代Web而生”因此对单页应用、动态内容、移动端模拟的支持都非常出色。上手初期可能会被其异步API和丰富的选项所困扰但一旦熟悉你会发现它几乎能优雅地解决所有Web自动化难题。我自己的项目从Selenium迁移到Playwright后代码量减少了三分之一稳定性和执行速度却提升了一倍以上。最重要的是它的官方文档非常详尽社区活跃遇到问题大多能找到解决方案。