首页 / 资讯中心 / 文章详情

Playwright自动化实战指南:从原理到爬虫与AI Agent集成

Playwright自动化实战指南:从原理到爬虫与AI Agent集成 ★ FEATURED ARTICLE
我拿 Playwright 写了三年自动化代码从最早拿来爬数据到后来整个测试团队把脚本全部迁到这套框架上再到现在各种内部平台把 Playwright 当作执行器来用。可以说Playwright 这个词已经不只是某个开源库的名字它已经变成了浏览器自动化领域的一个事实标准。不过新手刚接触它的时候往往会被安装、驱动、定位、等待这些概念劝退。这篇文章我打算从真实使用角度把 Playwright 从入门到精通的路线完整捋一遍包括它为什么好、怎么装、怎么写、怎么爬、怎么集成到其他系统以及那些让人崩溃的报错到底怎么解决。适合刚接触自动化的小白想从 Selenium 迁移过来的老手用 Python 或 TypeScript 写爬虫的工程师还有想做 LLM Agent 浏览器操作的朋友。1. 从零认识 Playwright它到底解决什么问题1.1 为什么是 Playwright 而不是 Selenium如果你之前用过 Selenium一定能感受到两者的明显区别。Selenium 诞生得早生态成熟但它的架构是古老的 WebDriver 协议浏览器每次执行一个操作都要经过一个独立的 driver 进程中转。一旦并发开多个浏览器实例资源占用和稳定性问题就特别明显。而 Playwright 出生时就是微软牵头搞的直接走的是浏览器原生的 CDPChrome DevTools Protocol协议再加上 Firefox、WebKit 各自的原生调试协议等于和浏览器之间修了一条高速直连通道不再需要中间人。我用 Selenium 写过一个并发 20 个浏览器的采集服务跑不到半小时内存就爆了。换到 Playwright 之后同样的需求用 BrowserContext 隔离会话每个 context 就像浏览器里的一个独立隐身窗口内存可控切换干净利落。这个理念非常重要你在一个 Playwright 实例里可以创建多个互不干扰的 context每个 context 有自己的 Cookie、存储、UA、甚至是视口大小。这相当于把十几个“虚拟浏览器”塞进了同一个进程里极大地压低了资源成本。另一个让我彻底放弃 Selenium 的理由是 Playwright 的“自动等待”机制。Selenium 你需要自己写显式等待或隐式等待什么 WebDriverWait、expected_conditions 一大堆。Playwright 几乎把所有等待都封装好了你调 locator.click()它会等元素出现、等元素可点击、等元素在页面中稳定甚至等元素的位置不再变化。这套机制让脚本里基本不需要再出现 time.sleep(3) 这种丑陋的代码稳定性也直接提升了一个量级。1.2 Playwright 的核心架构与运行原理理解 Playwright 的原理用一句话概括你的代码通过一个本地 WebSocket 通道连接到一个无头浏览器实例然后向它发送“去看这个 URL”“去点那个按钮”的指令。Playwright 的驱动模型包含三层上层是 Python、Node.js、Java、.NET 等语言的 API 绑定中间是 Playwright Driver它负责将 API 调用翻译成协议消息底层是浏览器进程通过专门的调试管道接收消息并执行操作。在写代码时最常用的对象有四个Playwright入口对象、Browser浏览器实例、BrowserContext会话上下文、Page页面对象。关系大概是Playwright 启动一个 BrowserBrowser 里开一个 ContextContext 里开一个 Page。如果你只是偶尔自动化可以忽略 Context 直接用 Browser.new_page()但如果你要做爬虫或者测试多用户场景你必须理解 Context 的隔离价值。这里有个很常见的认知误区很多人以为 Playwright 只能跑无头浏览器也就是不显示界面的模式。实际上它默认就是有头模式只是你在 CI 上一般会用 headlessTrue。而且 Playwright 支持 headless 模式和新的 headless 模式之间的切换新的 headless 模式行为更贴近真实浏览器能绕开很多“因为检测到无头环境就拒绝访问”的站点。不过涉及站点反爬的问题后面我会专门谈总之不要想着用无头模式去突破什么。2. 环境准备与第一个脚本2.1 安装 Playwright 与浏览器内核安装两部分Python 包和浏览器二进制文件。很多人只装了 Python 包直接运行就会报 “Executable doesnt exist at path”因为 Playwright 并不用你系统里的 Chrome它会下载一个自己锁定的 Chromium 版本。这是有意为之因为不同浏览器版本对自动化支持差异很大Playwright 必须保证你跑的浏览器版本和它的 API 是测过的一组。Python 安装方式很简单pip install playwright playwright install chromium如果你是 Node 项目则npm init -y npm install playwright/test npx playwright install chromium第二条命令很重要。它会去下载特定版本的 Chromium并放到本地的用户缓存目录里。在 Linux 服务器上如果缺系统库还需要执行playwright install --with-deps它会自动安装所有浏览器依赖的系统库推荐在 Docker 镜像或者干净的云服务器上用这个命令。这里需要特别提醒一下不要因为你的系统里有 Chrome 就试图绕过这个下载直接用 executable_path 指向系统浏览器。我试过一次当时图省事结果因为浏览器版本太新、协议不匹配各种定位失败。而且这样等于放弃了 Playwright 的版本管理优势建议还是老老实实安装它自带的浏览器。如果下载太慢或经常失败原因和解决办法我放在后面第 6 章的排查部分那里面有国内环境可用的镜像方案请务必看完。2.2 第一个自动化脚本的完整拆解装好环境先写一个最简单的 Python 脚本用同步 API 打开百度首页输入关键词点击搜索按钮最后截图from playwright.sync_api import sync_playwright def main(): with sync_playwright() as p: browser p.chromium.launch(headlessFalse) page browser.new_page() page.goto(https://www.baidu.com) page.get_by_role(textbox, name搜索).fill(Playwright) page.get_by_role(button, name百度一下).click() page.wait_for_load_state(networkidle) page.screenshot(pathbaidu.png) browser.close() if __name__ __main__: main()这段代码里有几个值得新手注意的点。第一with sync_playwright() as p:这个上下文管理器是固定的它负责启动驱动程序并自动清理资源。Python 用户可以选择同步 APIsync_api或异步 APIasync_api。在爬虫或服务场景里强烈建议用异步 API它不阻塞事件循环可以并发处理多个页面但作为新手入门同步 API 更好理解。第二page.goto()之后其实不需要像 Selenium 那样手写 sleep 等待页面加载。Playwright 内部有各种加载状态检测page.wait_for_load_state(networkidle)表示等到网络空闲。但这种等待也不是万能的如果页面一直有轮询接口networkidle 可能会等待很久实际项目中我常常不依赖它而是等具体元素出现。第三get_by_role(textbox, name搜索)这种语义化定位是 Playwright 的亮点。它会根据 HTML 的 role 和可访问名称来定位元素而不是像 XPath 一样傻傻匹配路径。这个“像人一样找东西”的思路会让脚本非常有韧性。用 TypeScript 写第一个测试用例则更简洁因为 Playwright 本身就内置了测试运行器import { test, expect } from playwright/test; test(查看标题, async ({ page }) { await page.goto(https://www.baidu.com); await expect(page).toHaveTitle(/百度/); });运行这个测试只需要npx playwright test。它会自动启动浏览器、执行用例、输出报告还能生成追溯文件。3. 核心 API 深度解析定位器、等待、断言3.1 定位器从 CSS 到语义化定位是自动化的地基。Playwright 的推荐写法是 Locator也就是page.locator()。你可以传 CSS 选择器、XPath也可以使用它封装好的一整套语义化定位方法。我平时用得最多的是这些page.get_by_text(你好)按文本定位适合链接、按钮等。page.get_by_role(button, name确定)按 ARIA role 定位适合表单控件。page.get_by_placeholder(请输入密码)按输入框 placeholder 定位。page.locator(#user-list li)直接写 CSS适合列表项。为什么要用语义化定位而不是直接抄一个很长的 CSS因为前端的类名和 DOM 结构太容易变了。你辛辛苦苦写了一个.content .wrap .btn-primary前端同事改个样式就全挂了。但语义化定位基于的是按钮显示给用户的名称只要页面功能不变测试就还能跑。我举个例子假设页面上有一个会变文本的按钮有时候显示“确认”有时候显示“已提交”。如果用page.locator(button.submit-btn)按钮还是那个按钮好使。但如果它被改成了a标签或者加了别的样式你就得改代码。而page.get_by_role(button, name确认)或page.get_by_text(确认)无论它底层是什么标签都能找到。另外要注意locator()本身并不立即去页面里找元素。它只是一个“配方”等到你调用.click()、.fill()等动作时Playwright 才会去解析这个配方并自动等待。这个设计让代码写起来非常自然也方便在 Page Object 里预先声明所有的定位器。3.2 自动等待机制与显式等待新手最容易犯的错就是不理解 Playwright 的等待哲学。前面说了Playwright 的很多操作都会自动等待但这个等待不是“等固定时间”而是检查元素的 actionability 状态。以 click 为例它在点击前会检查元素是否附加到 DOM元素是否可见元素是否稳定没有持续动画元素是否接收事件没有被遮挡元素是否可编辑如果是输入框只有在这些条件都满足时才会真正点击。默认超时是 30 秒你可以通过timeout参数调整。这一套机制解决了 Selenium 里 90% 的 “Element is not clickable at point” 问题。但自动等待不代表你完全不用写等待逻辑。有些场景需要显式等待比如你要验证某个请求已经发出或者等待一个弹窗出现。常用的写法是page.expect_response(**/api/comments) # 触发动作前先声明 page.locator(button#load-more).click()上面这个expect_response是动作和响应之间的“混合等待”它先注册一个期望然后再执行点击点击后如果触发了匹配的请求/响应才会继续往下走。这个模式在爬虫场景中尤其好用它可以替代while循环加 sleep 的方式非常稳定。还有一种显式等待是page.wait_for_selector()但我不太推荐滥用。因为一旦你用wait_for_selector你就放弃了自动等待的优雅性回归到“等到一个选择器出现”的传统模式。我更建议用expect(locator).to_be_visible()这种断言等待它更语义化而且失败时能给出很清晰的错误信息。3.3 断言与 Playwright 的 count 方法断言的本质是“等到一个条件满足再继续”所以它天然也具备等待能力。在 Python 中断言通常在expect()模块中from playwright.sync_api import expect expect(page.get_by_text(操作成功)).to_be_visible() expect(page.locator(.articles)).to_have_count(10)第一个断言等待“操作成功”出现第二个断言等待.articles的元素数量变成 10。这里的to_have_count配合count()方法特别实用。很多人在面对“判断一个元素到底存不存在”的时候会下意识用page.locator(...).count() 0来判断。要注意count()方法是同步的它不会自动等待。如果页面还在加载可能返回 0然后你就误以为元素不存在。正确写法应该是使用expect(locator).to_have_count(1)或者to_be_attached()。但如果你的目的是“往一个列表里追加数据直到数量超过某个阈值”那count()就非常适合写进while循环while page.locator(.comment-item).count() 100: page.mouse.wheel(0, 1000) page.wait_for_timeout(500)这段代码的意思是如果评论数量还没到 100 条就滚动一屏停 500 毫秒再看数量。很多无限滚动页面都可以这么处理。至于为什么不用 wait_for_selector 去等某个元素因为评论数量是动态增长的没有一个固定的“结束标记”所以用 count 轮询是最直观的办法。4. 进阶实战爬虫场景与页面交互4.1 用 Playwright 爬取评论区动态内容爬评论区是很多人接触 Playwright 的第一动力。比如抖音评论区、微博评论区它们都是动态加载的直接 requests 只能拿到空壳。Playwright 可以模拟真人浏览很自然地拿走内容。但我要先声明一句爬取公开数据一定要遵守目标平台的用户协议和 robots 协议不要爬取个人隐私数据也不要高频请求给站点造成压力。下面的示例只是技术演示。以爬取某平台视频评论区为例核心思路是打开页面滚动加载抓取每条评论的文本。这里用 count 做轮询是一种简单可靠的做法from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch(headlessFalse) page browser.new_page() page.goto(https://example.com/video/123) page.wait_for_selector(.comment-list) last_count 0 for i in range(20): items page.locator(.comment-item) current_count items.count() if current_count last_count: break last_count current_count page.mouse.wheel(0, 1200) page.wait_for_timeout(500) comments [] for item in page.locator(.comment-item).all(): text item.locator(.content).inner_text() comments.append(text) print(len(comments), comments[:5]) browser.close()这段代码有几个细节需要说明。第一page.wait_for_selector(.comment-list)是在等待评论列表容器出现因为只要没有容器后面的一切都没有意义。第二循环里的if current_count last_count: break是一个很实用的“停止条件”意思是如果连续两轮没有新增评论就认为已经到底了。第三items.count()每次调用都会实时查询 DOM所以不会有缓存问题。如果你想更精准地捕获在滚动过程中新增的请求可以用page.expect_response()去监听评论接口然后从接口返回的 JSON 里直接拿结构化数据。这比解析 DOM 更高效也更稳。但需要你提前在浏览器 DevTools 里找到评论接口的 URL 规律这一步往往是爬虫项目里最费时间的部分。再提醒一个坑有些页面的评论数据是通过 iframe 加载的主 DOM 里根本找不到评论节点。这时候就要用到下一小节说的 frame 处理。4.2 处理 iframe、新页面与文件下载动态 iframe 是爬虫里让人头疼的东西。常见场景是页面里嵌了一个第三方评论区组件整个区域是独立的文档。用 Playwright 处理 iframe 有两种姿势。第一种如果 iframe 有明确的 name 或 id可以用page.frame_locator(#comment-frame)在 iframe 内部继续定位元素。比如frame page.frame_locator(#comment-frame) frame.locator(.item).all()frame_locator返回的也是一个 Locator只不过它把查找范围限定在 iframe 内部。这里要注意Playwright 的 frame_locator 只支持同一页面内的 iframe不支持跨域 iframe 的 DOM 操作实际上跨域 iframe 也能定位和操作因为 Playwright 是通过 CDP 直接插进每个 frame 的不受同源策略限制这是它比 Selenium 高明的地方。第二种如果你要面对的是嵌套 iframeiframe 套 iframe可以链式使用page.frame_locator(#outer).frame_locator(#inner).get_by_text(确认)链式框架定位可以把层层嵌套的 iframe 串成一句代码非常舒适。处理新页面也是一大痛点。点击一个链接浏览器新开了 tabSelenium 需要switch_to.window()来回切。Playwright 的做法是优先使用context.expect_page()它有点像前面说的expect_responsewith context.expect_page() as new_page_info: page.click(a[target_blank]) new_page new_page_info.value new_page.wait_for_load_state() print(new_page.title())这段代码通过expect_page捕获点击后新打开的页面然后拿到新页面的 Page 对象继续操作。这样你就再也不用去维护“窗口句柄列表”了哪个链接开哪个窗口一一对应逻辑非常清爽。文件下载也是一样原理。with page.expect_download() as dl_info:点击下载按钮然后download dl_info.value; download.save_as(file.zip)。整个过程不会真的把文件保存到临时目录而是给你一个 Download 对象由你自己决定存哪避免了一堆垃圾文件。4.3 Playwright 与 Scrapy 结合处理动态页面如果你的主要爬虫框架是 Scrapy但又需要执行 JavaScript可以使用scrapy-playwright插件。这个插件的原理是在 Scrapy 的下载器中间件里面集成 Playwright让请求可以按需渲染。安装和配置pip install scrapy-playwright在 settings.py 中DOWNLOAD_HANDLERS { http: scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler, https: scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler, } TWISTED_REACTOR twisted.internet.asyncioreactor.AsyncioSelectorReactor然后在 spider 中对于需要渲染的 Request加上meta{playwright: True}def start_requests(self): yield scrapy.Request(url, meta{playwright: True, playwright_include_page: True}) def parse(self, response): page response.meta[playwright_page] # 等待页面中某个数据出现 page.wait_for_selector(.comment) # 用 page.content() 获取渲染后的 HTML html page.content()这里有个经典坑如果你返回了playwright_include_page: True那 Scrapy 必须在请求结束之后关闭这个 page否则会内存泄漏。比较好的做法是在解析完数据后page response.meta[playwright_page] # ... 解析数据 ... yield item page.close()还有另外一个坑scrapy-playwright 默认是异步的如果你在里面调用 Playwright 的同步 API会直接阻塞整个 Scrapy 的回调线程效率极低。所以建议在配合 Scrapy 时使用 Playwright 的异步 APIasync_playwright或者让每个请求只做非常有限的渲染操作。如果只是想在 Scrapy 里抓取某个动态加载的字段不一定非要集成 Playwright先看看目标数据是不是在某个 XHR 接口里如果是用 Scrapy 直接请求那个接口更快更稳。Playwright 只适合那些接口加密、数据必须经过完整 JS 执行才能出现的场景。5. 框架集成与扩展TypeScript、Dify、midscene5.1 TypeScript Playwright 的工程化如果你是一个测试开发工程师我强烈建议用 TypeScript Playwright而不是 Python。这不代表 Python 不行而是 TypeScript 版本和 playwright/test 运行器的结合几乎天生就是为测试框架准备的。它自带断言、Fixture、平行运行、HTML 报告、重试机制、Trace 视频录制很多东西开箱即用。一个最基本的测试文件可以这样写import { test, expect } from playwright/test; test.describe(登录流程, () { test(正常登录, async ({ page }) { await page.goto(https://example.com/login); await page.get_by_label(用户名).fill(testuser); await page.get_by_label(密码).fill(123456); await page.get_by_role(button, { name: 登录 }).click(); await expect(page).toHaveURL(/\/dashboard/); }); });除了基础的 test 之外你还可以在playwright.config.ts里配置多个 project比如一个跑 Chromium、一个跑 Firefox、一个跑 WebKit还可以配置 baseURL、视口尺寸、是否 headless、报告格式等。这种多浏览器矩阵测试在真实项目中可以说是最大的卖点。Fixture 是另一个强大功能。比如你想在每个 Test 之前都准备一份登录状态可以定义一个 fixtureimport { test as base, expect } from playwright/test; export const test base.extend({ loggedInPage: async ({ page }, use) { await page.goto(/login); await page.get_by_label(用户名).fill(prepared); await page.get_by_label(密码).fill(pass); await page.get_by_role(button, { name: 登录 }).click(); await use(page); }, });然后每个测试都可以直接用loggedInPage这个参数同时保证了测试之间的隔离。这套玩法很成熟适合几十上百个用例的大型项目。TypeScript 的静态类型也让重构和维护变得轻松至少比字符串拼接的 Python 脚本要安全得多。5.2 midscene 被 Playwright 调用的原理最近很多朋友在问 midscene 和 Playwright 的关系特别是“midscene 被 Playwright 调用的原理”这个话题。简单说midscene 是一个“视觉驱动的自动化 agent”它能通过截图识别页面上的元素位置然后让 Playwright 去执行点击、输入等操作。本质上它就是把“人眼看图”的能力接入到了 Playwright 的执行流程里。原理可以分为四步。第一步midscene 使用 Playwright 打开一个页面并截图第二步把截图传给一个多模态大模型例如 GPT-4V 等让模型识别出“屏幕上哪个位置是搜索按钮”第三步大模型返回元素的坐标或语义描述第四步midscene 再把坐标或描述转换成 Playwright 的page.mouse.click(x, y)或locator.click()调用。这样只要模型识别够准你的自动化就能绕开 DOM 结构的限制直接按照视觉效果操作页面。用代码概念来理解大概是# 伪代码 page.goto(url) image page.screenshot() element_info visual_model.find_element(image, 登录按钮) page.mouse.click(element_info.x, element_info.y)midscene 并不是要替代 Playwright而是把 Playwright 当作浏览器操作的“手脚”自己当“大脑”。这种思路非常适合那些 DOM 层级混乱、class 随机化比较严重、传统定位方式难以稳定使用的页面。但代价是每次操作都需要调用一次大模型延迟和成本都会比纯 Playwright 高不少。如果你准备在项目里引入 midscene我建议先做好两件事第一把传统 Playwright 能搞定的场景先全部用传统定位器做掉不要一上来就上大模型第二把 midscene 封装成一个服务只对真正困难的步骤做视觉推理比如验证码、滑块、图形按钮。这个混合模式在成本和稳定性之间取得了比较好的平衡。5.3 将 Playwright 集成到 Dify 流程中Dify 这类 LLM 应用编排平台现在已经很流行了。很多团队希望让 AI Agent 不仅能聊天还能真实操作网页。把 Playwright 集成到 Dify 里通常的做法是写一个小工具服务然后把服务注册成 Dify 的自定义工具节点让工作流在需要时调用它。举个实际案例我想做一个“智能查快递”的 Agent。用户问“我的快递到哪了”Agent 计划要查快递单号。但快递公司的网页只支持输入单号后动态渲染物流轨迹普通 HTTP 请求拿不到数据。于是我用 FastAPI 包了一个 Playwright 查询接口from fastapi import FastAPI from pydantic import BaseModel from playwright.sync_api import sync_playwright app FastAPI() class Query(BaseModel): tracking_number: str app.post(/query_kd) def query_kd(q: Query): with sync_playwright() as p: browser p.chromium.launch(headlessTrue) page browser.new_page() page.goto(https://example-kd.com/track) page.get_by_placeholder(请输入运单号).fill(q.tracking_number) page.get_by_role(button, name查询).click() text page.locator(.process-list).inner_text() browser.close() return {result: text}然后在 Dify 的自定义工具配置里填入这个 API 的 OpenAPI SchemaAgent 就能通过函数调用来触发它。这个方案的好处是把“浏览器操作”抽象成一个可以复用的工具和 LLM 的规划能力解耦。哪怕 LLM 说什么都不影响你的 Playwright 代码逻辑你只要保证接口入参和返回结构足够稳定就行。需要注意的是在 Dify 流程中调用 Playwright 工具的并发不要开太大。因为每个请求都会启动一个独立浏览器进程如果同时来了 10 个请求服务器很容易被压垮。我通常会加一个信号量控制并发或者预先用p.chromium.launch()复用一个浏览器实例每个请求各自开新 context这样能降低重复启动浏览器的开销。6. 常见问题排查与避坑指南6.1 npx playwright install 失败的解决方案这个问题在群里被问过上百次尤其是国内网络环境下载 Chromium 基本很容易失败。常见的报错是Failed to download ChromiumConnection refused或者TimeoutHost name lookup failed解决思路分两类。第一类是网络问题方案是设置镜像地址。Playwright 的下载地址可以通过环境变量PLAYWRIGHT_DOWNLOAD_HOST指定。你可以使用npmmirror提供的镜像export PLAYWRIGHT_DOWNLOAD_HOSThttps://npmmirror.com/mirrors/playwright/ npx playwright install chromium如果你用的是 Python 的playwright install也可以设这个环境变量因为它底层用的同样机制。如果设置了镜像还是失败先检查一下是不是没有挂代理或者公司网络屏蔽了磁盘写入目录。这种情况建议改一下缓存目录export PLAYWRIGHT_BROWSERS_PATH/path/to/your/cache把浏览器放到一个你有权限写的位置。第二类是缺少系统依赖库的报错。如果你在一个最小化的 Linux 镜像上运行playwright install chromium成功但一启动浏览器就报error while loading shared libraries: libnss3.so那就需要补库。最简单的做法playwright install --with-deps这个命令在 Debian/Ubuntu 下会调用 apt-get 自动安装所有依赖。如果你们服务器不允许自动 apt那就要看错误里具体缺哪个库比如libnss3-dev、libatk-bridge2.0-0手动安装。还有一个常见问题是版本不匹配npm 包和 Python 包安装的 Playwright 版本不同你在一台机器上混用两个环境容易导致浏览器二进制文件版本互相覆盖。建议一个项目一个虚拟环境并且固定 Playwright 版本号。6.2 “未安装 Playwright”的诡异报错明明pip install playwright成功了为什么运行脚本时还报ModuleNotFoundError: No module named playwright这种问题多数时候不是真的没装而是你运行的 Python 环境和安装时用的不是同一个。比如你在 base 环境用 pip 安装了但却用 conda 环境的 python 跑脚本自然找不到。排查步骤import sys print(sys.executable)看看当前脚本解释器的路径再确认一下你的 playwright 装在了哪里。可以在命令行里执行python -m playwright --version如果这个命令报没有说明当前解释器下确实没装。如果你用 PyCharm很常见的情况是项目解释器没选对或者虚拟环境没激活。还有一种情况是 IDE 里安装了 Python 插件但它默认使用的内置解释器不是你想要的。另一个“未安装”相关的报错是The file is not a valid Playwright driver或者Error: Cannot find module playwright。这通常是 npm 项目里npx playwright install之前没有安装playwright/test。记住npm 和 Python 的是两套独立组件手动混搭就会出问题。6.3 处理动态内容与反爬的注意事项很多朋友用 Playwright 的主要目的是采集所以不可避免地会遇到目标站点的反爬手段。像瑞数这类动态防护产品本质上是在不断检测浏览器环境和用户行为通过生成动态 Token 来保证请求来自真实浏览器。我必须明确告诉各位绕过这些防护措施是不符合平台规则、也可能违反法律的我不鼓励、不提供任何“过瑞数”的技巧。但我们可以从合法合规的角度来讨论如何让自动化的行为更像真人降低触发风控的概率这适用于你访问自己的网站或者已经获得授权的采集项目。一些可落地的策略不要用默认的 webdriver 特征明显的 UA改成一个常见的 Chrome UA并且设置viewport为真实分辨率。不要每次打开页面后立刻暴力点击而是随机暂停 0.5 到 1.5 秒模拟用户阅读。用page.mouse.move()模拟鼠标轨迹不要直接瞬移到目标。避免高频短时间的重复请求给每次访问设置一个随机间隔。使用独立的 BrowserContext每次启动都清空 cache 和 cookie模拟“新访客”身份。这些策略只是让自动化行为更自然并不是保证能绕过任何防护。我还是建议在做任何采集之前先查看目标站点的robots.txt并严格遵守相关法律法规只采集公开、合法、允许访问的数据。另外Playwright 本身有一个很实用的功能叫做page.route()可以拦截网络请求并修改请求头或响应。它在应付某些前端硬编码的场景时非常有用比如你想去掉页面里某个影响加载的资源。但同样地如果你用它来伪造请求头去欺骗服务器那就已经进入了灰色地带我不会推荐这种做法。合规红线一定要守好。最后分享一点个人体会做了这么久的自动化我最大的感悟是Playwright 真正的难点不在于 API 记不住而在于你要理解浏览器和页面的生命周期。很多脚本不稳定不是框架的问题而是等待模型没选对、定位表达式太脆弱、或者根本没有考虑异常情况。如果你在项目里能始终做到“少用固定等待、多用自动等待和断言等待尽量用语义化定位而不是 CSS 路径依赖”你的脚本稳定率会提升一大截。还有一个小技巧遇到瓶颈时可以打开 Playwright 的 Trace 查看器它会记录每一步操作前后的页面截图、DOM 快照、网络请求和浏览器控制台日志。这个工具的调试效率比盲目加 print 高得多但它需要你在脚本里先开启 trace。context.tracing.start(screenshotsTrue, snapshotsTrue, sourcesTrue) # 你的操作 context.tracing.stop(pathtrace.zip)然后用playwright show-trace trace.zip就能看到完整回放。我每次给团队排查问题都会先导出 trace这比什么都好用。最后建议大家别好高骛远先从“自动登录一个网站”“自动填写表格”这种小目标开始等你真正理解定位和等待之后再去做爬虫、做集成、做 Agent都会顺畅很多。
阅读完成 · 觉得有帮助?
咨询建站