尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Playwright自动化测试与数据抓取实战:从入门到精通

Playwright自动化测试与数据抓取实战:从入门到精通 1. 从零开始认识Playwright一个现代Web自动化测试框架如果你正在寻找一个能稳定、高效地操作浏览器进行自动化测试、数据抓取或者网页监控的工具那么Playwright绝对值得你花时间深入了解。它不是一个新概念但绝对是近年来在开发者社区中口碑迅速崛起的一个。简单来说Playwright是一个由微软开源的Node.js库它提供了一套统一的API让你可以用同样的代码去驱动ChromiumChrome、Edge、Firefox和WebKitSafari三大浏览器引擎。这意味着你写一次脚本就能在几乎所有主流浏览器上运行这对于需要做跨浏览器兼容性测试的场景来说简直是福音。我最初接触它是为了解决一个老项目里用Puppeteer和Selenium时遇到的痛点动态内容加载不稳定、iframe难以处理、等待逻辑复杂导致脚本脆弱。Playwright在设计之初就充分考虑了现代Web应用的复杂性它对单页应用SPA、网络请求拦截、文件上传下载、甚至移动设备模拟都提供了原生支持用起来非常顺手。无论是前端开发者想给自己的项目做端到端E2E测试还是数据工程师需要应对那些反爬机制复杂的网站Playwright都能提供强大的能力。它的学习曲线相对平缓特别是如果你有JavaScript/TypeScript或Python的基础上手会非常快。接下来我会从一个实际使用者的角度带你一步步拆解它的核心用法避开我当初踩过的那些坑。2. 环境搭建与核心概念解析2.1 安装与初始化一步到位还是按需索取Playwright的安装非常灵活主要取决于你的使用语言和场景。最主流的方式是通过Node.js的包管理器npm或yarn当然Python版本也同样强大。对于Node.js项目你可以在项目目录下执行npm init playwrightlatest这个命令是一个交互式的初始化工具它会问你几个问题比如是用JavaScript还是TypeScript测试目录放在哪是否需要安装GitHub Actions工作流等。对于新手我强烈推荐使用这个方式它能帮你搭建一个结构清晰、开箱即用的项目骨架。如果你已经有一个项目只想添加Playwright依赖可以npm install playwright需要注意的是仅仅安装playwright包并不会自动下载浏览器。浏览器二进制文件是通过一个单独的playwright/browser包来管理的。更常见的做法是使用上面提到的init命令或者直接安装带有浏览器的完整包npm install playwright playwright/test这里引入了playwright/test这是Playwright官方的测试运行器它比直接用playwright库写脚本然后node执行要强大得多内置了断言、测试钩子、并行执行和报告生成等功能。对于做自动化测试这是首选。对于Python用户安装同样简单pip install playwright playwright installplaywright install命令会下载所有三大浏览器的二进制文件。如果你只想安装Chromium以节省磁盘空间可以使用playwright install chromium。注意浏览器的下载可能会比较慢因为它需要从官方CDN拉取几百兆的文件。如果遇到网络问题可以考虑配置环境变量PLAYWRIGHT_DOWNLOAD_HOST使用国内镜像源或者耐心等待。第一次安装成功后后续使用就无需再下载了。安装完成后一个关键的概念是Browser Context。你可以把它理解为一个独立的“隐身会话”。每个Context拥有独立的cookie、缓存和本地存储相互隔离。这非常有用比如你可以用一个Context模拟登录用户A另一个Context模拟未登录用户B两者互不干扰而它们都共享同一个Browser实例的资源。这比为每个场景都启动一个全新的浏览器要高效得多。2.2 Playwright的核心架构Browser, Context, Page与Frame理解Playwright的四个核心对象层级关系是写出健壮脚本的基础。它们的关系就像俄罗斯套娃Browser代表一个浏览器进程实例。通过await chromium.launch()或await playwright.chromium.launch()启动。你可以在这里配置是否使用无头模式、指定浏览器路径、设置代理等全局参数。Context浏览器上下文。由Browser创建await browser.newContext()。这是隔离的关键层级。你可以为Context设置视口大小、用户代理User-Agent、地理位置、权限如摄像头、通知等。一个Browser可以创建多个Context。Page标签页。由Context创建await context.newPage()。这是我们最常打交道的对象代表一个网页。绝大多数操作如跳转URL、点击元素、输入文本、获取内容都在Page对象上进行。Frame页面框架。一个Page至少包含一个主Framepage.mainFrame()。如果页面内有iframe或frame标签就会产生子Frame。Playwright可以非常方便地定位和操作这些子Frame这对于处理那些把登录框或核心内容放在iframe里的网站至关重要。这种层级设计带来了巨大的灵活性。例如在数据抓取中你可以为每个任务创建一个新的Context任务结束后关闭Context这样就能彻底清除痕迹避免被网站通过浏览器指纹关联。而在测试中你可以利用Context的隔离性来并行执行多个独立的测试用例。3. 基础操作与元素交互实战3.1 启动浏览器与导航控制一切始于启动浏览器。下面是一个最基础的脚本它启动一个Chromium浏览器默认是无头模式即不显示图形界面打开一个新页面访问百度然后截图并关闭。const { chromium } require(playwright); // 或 import { chromium } from playwright; (async () { // 1. 启动浏览器 const browser await chromium.launch({ headless: false, // 设置为 true 则在后台运行不显示窗口 slowMo: 500, // 操作间延迟500毫秒方便观察 }); // 2. 创建一个浏览器上下文 const context await browser.newContext({ viewport: { width: 1920, height: 1080 }, // 设置视口大小 userAgent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..., // 可自定义UA }); // 3. 打开一个新页面 const page await context.newPage(); // 4. 导航到目标网址 await page.goto(https://www.baidu.com); // 等待页面达到“网络空闲”状态对于SPA尤其重要 await page.waitForLoadState(networkidle); // 5. 进行一些操作比如截图 await page.screenshot({ path: baidu.png, fullPage: true }); // 6. 关闭资源务必关闭释放内存 await context.close(); await browser.close(); })();page.goto()是导航的核心方法。它默认会等待页面触发load事件。但对于大量使用Ajax的现代网站load事件触发后页面内容可能还在加载。因此Playwright提供了更强大的等待选项await page.goto(url, { waitUntil: domcontentloaded })等待HTML文档加载完成。await page.goto(url, { waitUntil: networkidle })等待网络活动基本停止大约500ms内没有超过2个网络请求。这是最常用也最可靠的选项。await page.goto(url, { waitUntil: commit })当接收到响应时立即返回不等待页面加载。在实际项目中我习惯在goto后再加上page.waitForLoadState(networkidle)双保险确保页面完全加载。对于某些特别“重”的页面你可能还需要等待某个特定元素出现这就要用到下面介绍的选择器和等待机制。3.2 元素定位与等待策略告别脆弱的脚本定位元素是自动化的基石。Playwright支持多种选择器引擎最常用的是CSS选择器和XPath但它也内置了非常方便的文本定位和角色定位。1. CSS选择器与文本定位// 通过CSS ID点击搜索框 await page.click(#kw); // 通过CSS类名输入文本 await page.fill(.s_ipt, Playwright教程); // 通过文本内容点击按钮模糊匹配 await page.click(text百度一下); // 通过文本内容精确匹配 await page.click(text新闻);text选择器极其好用尤其是当元素没有稳定的ID或Class时直接通过肉眼可见的文本定位直观又省事。2. XPath定位对于复杂的DOM结构XPath有时是唯一的选择。// 点击第一个包含“知道”文本的a标签 await page.click(//a[contains(text(), 知道)]);3. 角色定位ARIA这是Playwright的一大特色对于可访问性友好的网站通过角色定位非常语义化。// 点击一个按钮rolebutton await page.click(rolebutton[name登录]); // 点击一个链接rolelink await page.click(rolelink);等待策略是自动化脚本稳定性的生命线。Playwright的API大多是“自动等待”的比如click、fill会在执行前检查元素是否可交互可见、未被禁用、在视口内。但有时你需要显式等待。page.waitForSelector(selector, options): 等待某个元素出现在DOM中。// 等待搜索按钮出现最多等10秒 await page.waitForSelector(#su, { timeout: 10000, state: visible });state可以是attached存在于DOM、visible可见、hidden等。page.waitForFunction(predicate): 等待一个JavaScript条件成立。// 等待页面标题变成包含“百度”字样 await page.waitForFunction(() document.title.includes(百度));page.waitForTimeout(ms): 强制等待指定毫秒数。这是最后的手段应尽量避免使用因为它会让脚本变得低效且不可靠。优先使用基于条件的等待。实操心得处理动态加载内容时最稳健的模式是“导航 - 等待网络空闲 - 等待关键元素出现”。例如在点击一个可能触发新内容加载的按钮后立即跟上await page.waitForLoadState(networkidle)和await page.waitForSelector(.new-content)。另外Playwright的locator模式如page.locator(#kw)比直接使用page.$或page.$$更强大它支持链式调用和更丰富的等待选项是现代Playwright脚本的推荐写法。3.3 模拟用户输入与事件除了简单的点击和输入Playwright能模拟几乎所有用户交互。键盘操作await page.fill(#kw, Playwright); // 模拟按下回车键 await page.press(#kw, Enter); // 组合键如CtrlA全选 await page.keyboard.press(ControlA); // 输入特殊字符 await page.type(#kw, Hello World!, { delay: 100 }); // 每个字符间隔100ms模拟真人输入鼠标操作const button page.locator(button); // 悬停 await button.hover(); // 右键点击 await button.click({ button: right }); // 双击 await button.dblclick(); // 拖放从source元素拖到target元素 await page.dragAndDrop(#source, #target);文件上传这是很多自动化工具的痛点Playwright处理起来非常优雅。// 方法1对于input[typefile]元素直接设置文件路径 await page.setInputFiles(input[typefile], ./my-file.pdf); // 方法2上传多个文件 await page.setInputFiles(input[typefile], [./file1.png, ./file2.png]); // 方法3通过文件选择器对话框更接近真实用户操作 const [fileChooser] await Promise.all([ page.waitForEvent(filechooser), // 监听文件选择事件 page.click(textUpload), // 点击触发文件选择对话框的按钮 ]); await fileChooser.setFiles(./my-file.pdf);方法3尤其适用于那些不是通过标准input元素触发上传的复杂前端组件。下拉选择// 通过value选择 await page.selectOption(#city, beijing); // 通过label文本选择 await page.selectOption(#city, { label: 北京 }); // 选择多个值 await page.selectOption(#multi-select, [opt1, opt2]);4. 处理高级场景与疑难杂症4.1 征服动态Iframe与多页面现代网站特别是那些包含第三方登录如微信、支付宝、富文本编辑器或地图服务的页面大量使用iframe。Playwright处理iframe的思路很清晰先定位到iframe元素然后获取其对应的Frame对象之后的所有操作都在这个Frame对象上进行。// 方法1通过iframe的元素句柄获取Frame对象 const iframeElement await page.$(iframe[nameeditor]); const iframe await iframeElement.contentFrame(); await iframe.fill(body, Hello from inside iframe!); // 方法2使用page.frame()系列方法更推荐 // 通过名称或URL定位iframe const iframeByName page.frame({ name: editor }); const iframeByUrl page.frame({ url: /.*login-dialog.*/ }); await iframeByUrl.click(button:has-text(登录)); // 方法3使用Locator的frameLocator方法进行链式操作最新、最优雅的方式 await page .frameLocator(iframe[title富文本编辑器]) .locator(body) .fill(直接在iframe里编辑内容);frameLocator是处理嵌套iframe的利器它返回一个作用域被限定在该iframe内的定位器后续的locator调用都会在这个iframe的DOM中进行查找。处理多页面新标签页也很简单// 点击一个会打开新标签页的链接并等待新页面出现 const [newPage] await Promise.all([ context.waitForEvent(page), // 监听新page事件 page.click(a[target_blank]), // 点击链接 ]); // 现在可以操作新页面了 await newPage.bringToFront(); // 切换到该页 console.log(await newPage.title()); await newPage.close(); // 关闭新页面4.2 拦截与修改网络请求Playwright强大的网络拦截能力可以用于性能测试、屏蔽广告、修改请求/响应、模拟API返回等。// 拦截所有请求并打印URL await page.route(**/*, route { console.log(Request:, route.request().url()); route.continue(); // 继续请求 }); // 拦截图片请求并阻止加载以加快速度 await page.route(**/*.{png,jpg,jpeg,svg}, route route.abort()); // 拦截特定API请求并返回模拟数据 await page.route(**/api/user/profile, async route { const mockData { name: Mock User, id: 12345 }; // 以JSON格式返回模拟数据 await route.fulfill({ status: 200, contentType: application/json, body: JSON.stringify(mockData), }); }); // 修改请求头例如添加Token await page.route(**/api/**, route { const headers { ...route.request().headers(), Authorization: Bearer fake-token-123, }; route.continue({ headers }); });这个功能在测试中极其有用你可以轻松模拟后端API的各种响应成功、失败、超时而无需搭建复杂的测试环境。4.3 应对反爬与复杂验证Playwright能模拟真人浏览器环境本身就绕过了许多基于简单HTTP库的检测。但对于更高级的反爬措施如一些动态加密的验证参数需要更多技巧。伪装成普通浏览器创建Context时使用完整的、更新的User-Agent字符串并设置合理的视口大小、时区、语言等。const context await browser.newContext({ viewport: { width: 1920, height: 1080 }, userAgent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, locale: zh-CN, timezoneId: Asia/Shanghai, });注入JavaScript代码有些网站会检测浏览器环境变量如navigator.webdriver。Playwright启动的浏览器默认会将此属性设为true可能被检测。可以在创建Page时注入脚本修改它。const page await context.newPage(); await page.addInitScript(() { Object.defineProperty(navigator, webdriver, { get: () undefined }); // 也可以修改其他属性如plugins, languages等 });重要警告关于修改webdriver属性以绕过检测其效果因网站而异且可能涉及合规性问题。请仅将此技术用于对自己拥有权限的网站进行自动化测试或明确允许自动化的公开数据获取。用于规避他人网站的访问限制可能违反服务条款。慢速操作与随机等待避免脚本行为过于规律。在关键操作之间加入随机延迟。const delay (ms) new Promise(resolve setTimeout(resolve, ms)); await page.type(#kw, test, { delay: Math.random() * 100 50 }); // 50-150ms随机延迟 await delay(Math.random() * 2000 1000); // 1-3秒随机等待处理Canvas指纹等对于极其严格的检测可能需要更复杂的方案如使用浏览器插件或更底层的CDPChrome DevTools Protocol命令。但这已超出基本使用范畴且维护成本很高。关于“过瑞数”等特定反爬网络热词中提到的“playwright过瑞数”指的是应对瑞数一种动态安全技术的挑战。这通常需要分析其JavaScript混淆代码找到生成动态令牌如cookie或参数的逻辑并在Playwright中复现或直接执行相关JS代码。这是一个非常深入的话题涉及逆向工程没有通用的一键解决方案。核心思路是使用page.evaluate()在页面上下文中执行JS获取生成的令牌再将其设置到后续请求中。5. 测试集成与高级工具链5.1 使用Playwright Test进行结构化测试playwright/test框架让编写和组织测试用例变得专业且轻松。它提供了测试夹具Fixtures、断言、并行执行和精美报告。一个基本的测试文件如下// tests/example.spec.js const { test, expect } require(playwright/test); test(百度搜索Playwright, async ({ page }) { // page fixture由框架自动提供 await page.goto(https://www.baidu.com); await page.fill(#kw, Playwright); await page.click(#su); // 等待搜索结果出现 await page.waitForSelector(.result.c-container); // 使用Playwright Test内置的断言 await expect(page).toHaveTitle(/Playwright/); // 更灵活的断言检查页面是否包含特定文本 await expect(page.locator(text微软开源)).toBeVisible(); }); test(测试文件上传, async ({ page }) { await page.goto(https://the-internet.herokuapp.com/upload); await page.setInputFiles(#file-upload, ./test-file.txt); await page.click(#file-submit); await expect(page.locator(h3)).toHaveText(File Uploaded!); });你可以通过命令行运行测试npx playwright test # 运行所有测试 npx playwright test example.spec.js # 运行特定文件 npx playwright test --headed # 在UI模式下运行显示浏览器 npx playwright test --projectchromium # 指定在Chromium上运行 npx playwright test --grep 搜索 # 运行标题包含“搜索”的测试运行后会生成HTML报告直观展示通过/失败的测试、步骤、截图和追踪信息。5.2 代码生成与调试利器Playwright CLIPlaywright自带一个强大的命令行工具对于快速探索和调试网站非常有帮助。1. 代码生成器 (playwright codegen):这是新手的神器。运行它会在打开浏览器的同时记录你的所有操作并实时生成对应的Playwright代码。npx playwright codegen https://www.baidu.com执行后一个浏览器窗口和一个代码生成器窗口会同时打开。你在浏览器里的点击、输入等操作都会自动转换成代码显示在另一个窗口。你可以直接复制这些代码到你的脚本中使用。这对于学习API和快速编写脚本原型至关重要。2. 调试工具 (playwright debug):以调试模式运行脚本会启动一个支持“时间旅行”的调试器。npx playwright debug your-script.js你可以暂停执行、检查元素、查看调用栈、单步执行并且浏览器界面会高亮显示Playwright正在操作的元素。这对于排查“元素找不到”、“点击没反应”这类问题效率极高。3. 追踪查看器 (playwright show-trace):在测试配置中启用追踪后每次测试失败都会生成一个.zip追踪文件。// playwright.config.js module.exports { use: { trace: on-first-retry, // 仅在第一次重试时记录追踪节省资源 // trace: on, // 始终记录 // trace: retain-on-failure, // 仅在失败时保留 }, };运行测试失败后使用以下命令查看追踪npx playwright show-trace trace.zip追踪查看器提供了一个图形化界面你可以逐帧回放测试执行的全过程查看每个时刻的DOM快照、控制台日志、网络请求是分析偶发性失败的终极武器。5.3 与CI/CD集成与打包部署Playwright测试可以无缝集成到GitHub Actions、GitLab CI、Jenkins等持续集成流程中。官方甚至提供了预配置的GitHub Actions工作流模板。关于“python 打包 playwright”的热词这里有一个常见问题当你用PyInstaller、PyOxidizer等工具将Python脚本打包成独立可执行文件exe时Playwright的浏览器二进制文件可能不会被包含进去。解决方案通常是在打包后通过代码在首次运行时检查并自动下载浏览器或者将浏览器二进制文件手动复制到打包目录的特定位置。Playwright Python库提供了playwright install命令你可以在你的打包后脚本或安装程序中调用它。对于“cursor怎么配置playwright mcp”和“dify平台中使用playwright mcp”这涉及到Playwright作为一个工具被集成到更上层的AI辅助编程Cursor或AI应用开发平台Dify中。MCPModel Context Protocol是一种协议允许AI模型使用外部工具。在这些平台中配置Playwright MCP本质上是让AI如Claude Code能够调用Playwright来浏览网页、执行操作从而实现更复杂的AI智能体功能。这通常需要在平台的插件或工具配置页面指定Playwright的安装路径和启动命令。6. 常见问题排查与性能优化6.1 高频问题速查表问题现象可能原因解决方案Error: page.goto: Navigation timeout页面加载超时默认30秒。网络慢、页面过重、或目标网站有阻塞。1. 增加超时时间page.goto(url, { timeout: 60000 })。2. 检查网络或代理设置。3. 尝试使用waitUntil: domcontentloaded代替networkidle。Error: Element is not visible元素存在但不可见被遮挡、样式display: none、visibility: hidden。1. 确保元素在视口内await element.scrollIntoViewIfNeeded()。2. 等待元素变为可见状态await page.waitForSelector(selector, { state: visible })。3. 检查是否有弹窗、遮罩层覆盖。Error: No node found for selector选择器找不到元素。元素尚未加载、选择器写错、或在iframe内。1. 在操作前增加等待await page.waitForSelector(selector)。2. 使用浏览器开发者工具检查选择器是否正确。3. 确认元素是否在iframe中使用frameLocator。点击/输入没有效果元素可能被其他透明元素覆盖或者页面状态未就绪。1. 使用page.click(selector, { force: true })强制点击不检查可交互性。2. 先尝试element.hover()再点击。3. 在操作前等待页面状态稳定await page.waitForLoadState(networkidle)。文件上传不工作文件选择器不是标准的input typefile可能是自定义的div模拟的。1. 使用page.waitForEvent(filechooser)方法见上文。2. 如果页面是React/Vue组件可能需要触发特定的事件。脚本在无头模式下运行正常但非无头headed下失败无头模式和有头模式在渲染、计时上可能存在细微差异。1. 在非无头模式下添加slowMo参数放慢操作速度便于观察。2. 检查是否有依赖视觉的逻辑如懒加载增加等待时间。3. 统一测试环境尽量在CI中也使用无头模式。浏览器无法启动浏览器二进制文件损坏、权限问题、或端口冲突。1. 重新安装浏览器npx playwright install或playwright install。2. 检查是否有其他进程占用了Playwright使用的端口。3. 在Linux/Mac上检查浏览器二进制文件是否有执行权限。6.2 性能优化与最佳实践复用Browser实例启动和关闭浏览器的开销很大。对于需要执行多个独立任务的脚本应该只启动一个Browser实例然后为每个任务创建独立的Context。任务完成后关闭Context但保留Browser。const browser await chromium.launch(); // 任务1 const context1 await browser.newContext(); const page1 await context1.newPage(); // ... 执行任务1 await context1.close(); // 任务2 const context2 await browser.newContext(); // ... 执行任务2 await context2.close(); await browser.close(); // 所有任务完成后关闭浏览器并行执行利用Promise.all并行执行多个独立操作可以大幅缩短总耗时。// 串行慢 await page.goto(url1); await page.goto(url2); // 并行快- 注意这需要多个Page通常在一个Context下创建 const [page1, page2] await Promise.all([ context.newPage(), context.newPage(), ]); await Promise.all([ page1.goto(url1), page2.goto(url2), ]);禁用不必要的资源加载如果不需要图片、样式、字体等来执行你的操作例如只做API接口测试可以拦截并阻止它们加载极大提升页面加载速度。await page.route(**/*.{png,jpg,jpeg,svg,gif,css,woff,woff2}, route route.abort());合理使用等待避免sleep始终使用条件等待waitForSelector,waitForFunction而非固定等待page.waitForTimeout。条件等待在条件满足后立即继续而固定等待总是消耗固定时间效率低下且不可靠。清理资源避免内存泄漏确保在脚本结束时包括发生异常时关闭Page、Context和Browser。使用try...catch...finally块是个好习惯。const browser await chromium.launch(); try { const context await browser.newContext(); const page await context.newPage(); // ... 你的主要逻辑 } catch (error) { console.error(执行出错:, error); } finally { await browser.close(); }从我自己的使用经验来看Playwright最大的优势在于其稳定性和开发者体验。它的错误信息通常很清晰调试工具链完善社区活跃。相比于Selenium它不需要额外的WebDriver管理相比于Puppeteer它提供了多浏览器支持和更丰富的内置功能。对于任何涉及浏览器自动化的任务无论是测试、爬虫还是监控它目前都是我的首选工具。刚开始可能会觉得API繁多但一旦熟悉了Browser-Context-Page-Frame这个核心模型和Locator模式你会发现它既强大又直观。
返回列表