☰
从Selenium到Playwright:动态页面自动化采集与测试实战指南
2026/10/7 10:33:06 网站建设 项目流程

最近一个做采集的朋友问我:requests 拿不到前端渲染的数据,Selenium 又总是写一堆 sleep 还在半夜崩,到底换什么方案好。我直接回了一句:去试 Playwright,先把库装上,跑一个 codegen,你会回来谢我。这篇文章就是我从第一次安装 Playwright 到把它同时用在抓取、测试用例、动态 iframe 这些场景里的完整记录。新手可以照着走一遍,有一两处基础经验的老手也可以看看后半部分的动态页面和框架化用法。

1. 为什么我弃用 Selenium 投向 Playwright:核心设计理念

先说结论:Playwright 受欢迎不是因为名字比 Selenium 好记,而是它解决了我最痛的三个问题——等待不稳定、上下文管理繁琐、测试和爬虫两套逻辑割裂。理解这三点,你后续学到的 API 就全部串起来了。

1.1 自动等待机制:不是替代 sleep,而是“元素还能用”才动手

用 Selenium 写脚本,最常见的代码就是:

time.sleep(3) driver.find_element(By.ID, "submit").click()

这类写法的逻辑是:我猜 3 秒后元素会出现。但页面加载快慢取决于网络、图片、接口返回时间,于是 sleep(3) 有时太多,有时不够。后来有人换成 WebDriverWait,其实本质还是自己写轮询。

Playwright 把等待做进了所有操作里。你写:

page.get_by_role("button", name="提交").click()

它会自动重试,直到这个按钮满足“可点击”条件。这里的“可点击”不是简单判断元素存在,而是做了一套 actionability checks:元素必须连接在 DOM 上、是可见的、是稳定的(没有动画位移)、没有被遮挡、处在可接收事件的状态。任何一个不满足,就继续等,直到超时。所以它的等待叫“自动等待”,不需要你猜时间。

我在写动态 iframe 和无限滚动页面时感受最深:以前要专门写“等 iframe 加载完再切进去”的逻辑,现在直接对 iframe 里的元素操作,它能等多久就等多久,脚本稳定性直接上了一个档次。

1.2 context 隔离:Cookie、UA、存储都能独立

Selenium 里要模拟不同登录态,通常开多个 driver,或者麻烦地改 cookie、改 user-agent。Playwright 引入了一个清晰的中间层:Browser Context。

一个 context 就像浏览器里一个独立的“用户档案”:它有自己的 Cookie、localStorage、User-Agent、viewport 尺寸、权限设置。你可以在这个 context 里登录 A 账号,在另一个 context 里登录 B 账号,互相不干扰。同一个 browser 实例能否开多个 context?完全没问题,这也是 Playwright 跑并行测试用例的基础。

实际使用中,我经常把一个账号的登录态存成 storage_state 文件,下次直接用:

context = browser.new_context(storage_state="login.json")

省掉了每次重复登录,这招在采集类项目里非常实用。

1.3 一个库同时贯穿测试用例和爬虫

很多人以为 Playwright 就是个测试工具,其实它定位是“浏览器自动化”。同一个 API,既可以在测试文件里做断言,也可以跑数据采集。写测试用例时用 expect 断言;写爬虫时用 page.goto 打开页面、locator 提取数据、context 管理登录态。两者共用一套核心,不需要像过去那样:测试用 Selenium,爬虫用 Pyppeteer,各学一套。

我现在的项目组合是:用 Playwright 写端到端测试用例,用同一套浏览器控制能力做动态页面采集,再用 codegen 快速生成脚本初稿。一套知识,多个场景复用,这是我最推荐新手入坑它的原因。

2. 环境搭建:从 npm 安装到 npx playwright install 失败的完整排错

2.1 三个命令背后分别做了什么

安装 Playwright 并不复杂,但很多人一开始就栽在这。先理清概念:Playwright 这个 npm 包里包含的是驱动和 API 库,不包含浏览器本体。你需要单独把 Chromium / Firefox / WebKit 下载到本地,才能跑起来。

Node.js 项目里最标准的做法是:

npm init -y npm i -D playwright npx playwright install

第一条命令初始化项目;第二条安装 Playwright 库;第三条下载浏览器内核。如果只是临时体验,不建项目也可以,但实际工作时我建议建项目目录,后续 codegen、trace 功能都要依托项目环境。

Python 阵营的安装也类似:

pip install playwright playwright install

装完可以跑一下playwright --version确认。我见过很多新手在这两步卡住,往下一看基本都是同一个原因:网络下载浏览器那一步失败了。

2.2 npx playwright install 失败的典型场景与排错链路

这个热词搜的人非常多,说明确实是个高频坑。我把自己遇到和帮别人排查过的几类整理出来:

失败现象原因处理方式
卡在 Downloading Chromium x.x 一直不动下载源网络慢或中断设置国内镜像源后重试
报错 Error: EACCES 或权限不足默认下载目录 /root/.cache/ms-playwright 不可写用 PLAYWRIGHT_BROWSERS_PATH 指定可写目录
Linux 系统一启动就报缺 libnss3 之类的依赖库系统缺运行库执行npx playwright install-deps
公司网络环境下证书报错存在证书校验问题核实网络环境并配置合规的网络策略,再重新下载

最常碰到的就是第一种。解决方案是设置环境变量指向国内镜像源。以 Linux / macOS 为例:

export PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright/ npx playwright install

注意两点:一是这个环境变量是给“下载浏览器”这个过程用的;二是设置之后最好把之前的缓存目录清理一下,不然可能以为没生效。Windows 用户用set PLAYWRIGHT_DOWNLOAD_HOST=...就行,PowerShell 用$env:PLAYWRIGHT_DOWNLOAD_HOST="..."。

还有个忠告:不要一失败就去换系统重装。先看报错最后几行,是权限问题还是网络问题,然后按表排查。80% 的安装问题无非是下载不动和缺系统库这两类。

2.3 Python 与 Node 版本怎么选

你如果搜过 Playwright 教程,会发现网上有 JS 和 Python 两派。我的建议很简单:

  • 如果你要配合 Scrapy 做爬虫,选 Python。scrapy-playwright 插件是 Python 生态的,集成起来顺滑。
  • 如果你写测试用例、前端项目用 Node,选 Node 版,和前端工具链融合更好。
  • 如果你纯新手、没有既有的语言栈,选 Python 也行,因为它的 API 写法更像普通脚本,读起来直观。

两种版本的核心概念一致,学会一种,另一种看文档十分钟就能上手。下面代码示例我以 Python 版为主,因为后续会讲 Scrapy 集成。

3. 核心 API 上的三个基本功:三层模型、locator 定位与交互断言

3.1 browser / context / page 三层模型

Playwright 的对象层级是这样:

Browser(一个浏览器进程) └── BrowserContext(独立会话) └── Page(一个标签页)

新手最容易犯的错误是:拿到 browser 直接browser.new_page()用,跳过了 context 层。这个写法虽然能跑,但后面要做登录态隔离、设置设备模拟、控制权限时,会发现少了关键的一层。

正确打开方式:

from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch(headless=False) context = browser.new_context( viewport={"width": 1280, "height": 720}, locale="zh-CN", ) page = context.new_page() page.goto("https://example.com")

sync_playwright是同步 API,适合普通脚本和爬虫。写异步高并发场景可以用async_playwright。新手阶段先吃透同步版。

这里还有个细节:browser.new_context()里的参数都相当于持久化配置,viewport 设好后,页面打开就是你要的尺寸,比 Selenium 里各种 set_window_size 干净得多。

3.2 locator 定位体系:选择器不是一次性的

Selenium 里有一个根深蒂固的习惯:用find_element得到一个 WebElement,然后拿它去.click()。如果页面发生了局部刷新,这个引用可能就失效了,得重新查。

Playwright 的 locator 完全不同。它更像一个“定位描述”,不是对某个具体 DOM 节点的引用。每次操作时,它都会重新去页面里查找匹配的元素,所以页面变化了、元素被 re-render 了,也不影响你继续用同一个 locator。

常用的定位方式,按推荐程度排序:

# 基于可访问性角色定位,最接近用户视角 page.get_by_role("button", name="立即购买") # 基于文本定位,适合链接、按钮 page.get_by_text("查看更多") # 基于表单元素 page.get_by_placeholder("请输入手机号") page.get_by_label("用户名") # 兜底用 CSS / XPath page.locator(".user-item") page.locator("xpath=//div[@class='list']/span[2]")

我的经验是:测试用例里优先用 role 和 text 这类语义化定位,可读性强,页面重构了也能顶一阵。XPath 写起来快,但脆,我一般只在复杂场景兜底用。

locator 另一个好用的点是链式调用。比如先定位一个大容器,再在里面找子元素:

comment_list = page.locator("div.comment-list") first_comment = comment_list.get_by_role("listitem").first

它不会立刻执行查找,而是等真正操作时一起做,中间即使局部刷新也不怕。

3.3 交互细节:popup、download、fill 与 type 的区别

几个高频交互细节,新手容易踩:

新标签页弹窗:点击一个链接会打开新页面时,用expect_popup配合捕获:

with page.expect_popup() as popup_info: page.get_by_text("查看详情").click() new_page = popup_info.value new_page.wait_for_load_state()

下载文件:类似地,用expect_download捕获下载事件,然后download.save_as()保存。不要用 Selenium 的思路去猜下载路径。

fill 与 type:fill()是一步设置值,适合大多数输入框;type()则是一个字符一个字符敲,有“打字过程”,会在某些带实时校验的输入框里触发不同行为。如果页面逻辑监听的是 input 事件,fill 可能不够“真实”,“type”反而更接近用户。单纯填表单用 fill 就好,别两个来回试。

等待页面加载:

page.wait_for_load_state("networkidle")

不要滥用 networkidle,它要等网络空闲,在广告多、轮询多的页面上可能等到超时。更靠谱的是等待某个关键元素出现。

4. 动态页面实战:iframe、无限滚动评论区与 Scrapy 集成

4.1 动态 iframe 的定位思路

“scrapy playwright 动态 iframe”这个热词说明大家都被 iframe 折磨得不轻。Selenium 处理 iframe 要先switch_to.frame,Playwright 里不需要切来切去,直接用frame_locator进入特定 frame:

frame = page.frame_locator("iframe[src*='comment']") frame.get_by_role("button", name="展开").click()

frame_locator 和 locator 一样是“懒定位”,每次操作时重新查找。iframe 还没加载出来时,它会自动等待。对付动态插入的 iframe 特别管用。

你还可以拿到 frame 后继续嵌套查找:

child_frame = page.frame_locator("#outer-iframe").frame_locator("#inner-iframe")

少数层级很多的页面,这一招比一层层 switch 清爽太多。

4.2 抓无限滚动评论区:思路与实现

很多社区页面,比如短视频平台和新闻客户端的评论区,是典型的无限滚动 + 懒加载。直接 requests 只能拿到前几页,或者什么都拿不到。用 Playwright 的思路是这样:

  1. 打开目标页面,定位评论容器。
  2. 在容器内反复滚动到底部,每滚一次停一下,等待新内容渲染。
  3. 每次滚动后提取新增的评论文本,累积起来。
  4. 直到内容不再增加或达到预期数量,停止。

以某个短视频平台公开评论区为例,核心代码框架长这样:

def scroll_until_no_new(container_locator): last_count = 0 while True: container_locator.evaluate("el => el.scrollTop = el.scrollHeight") page.wait_for_timeout(1000) current_count = container_locator.locator(".comment-item").count() if current_count == last_count: break last_count = current_count

注意两点:一是有些页面的滚动容器不是 window 而是某个 div,scrollTop = scrollHeight比mouse.wheel更可靠;二是不能死快,滚动一下等一等,模拟真实阅读节奏。抓取公开可见的评论时,我会额外控制请求频率,避免对目标服务造成压力。如果页面依赖登录态,可以先用context.storage_state保存登录状态,下次复用。

合规永远是前提。只采集公开可见的信息、遵守平台服务条款和 robots 约定、不对目标系统发起高频访问,这是自动化采集的分寸。

4.3 与 Scrapy 集成的配置细节

如果在 Scrapy 项目里遇到大量 JS 渲染页面,可以装scrapy-playwright插件,把单个请求交给 Playwright 渲染后再拿 HTML 给解析器。

settings.py 里的核心配置:

DOWNLOAD_HANDLERS = { "http": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler", "https": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler", } PLAYWRIGHT_LAUNCH_OPTIONS = { "headless": True, }

spider 里对需要渲染的请求加上 meta:

yield scrapy.Request( url=url, meta={ "playwright": True, "playwright_include_page": True, }, callback=self.parse_page, )

在回调里通过response.meta["page"]拿到 Page 对象,可以继续执行滚动、点击等操作。

这里有个大坑:交给 Playwright 的每个请求都会开一个浏览器上下文,内存和开销比普通 requests 高得多。并发数别照抄普通爬虫那套,我把CONCURRENT_REQUESTS调到 4~8,再配合PLAYWRIGHT_MAX_PAGES控制总量,不然很容易把机器资源吃满或触发对方风控。能用普通 requests 解决的列表页就不要走 Playwright,只有真正需要 JS 渲染的详情页、评论区才走这条路。

5. 把 Playwright 当测试框架:用例组织、并行执行与 trace 调试

5.1 断言体系:expect 会自动重试

Playwright 自带断言库。新手常犯的错是拿 Python 的assert去判断页面结果:

assert "成功" in page.text_content(".toast")

这种断言只检查一次,页面响应慢一点它就直接失败。Playwright 的 expect 断言会像 locator 一样自动等待:

from playwright.sync_api import expect expect(page.get_by_text("提交成功")).to_be_visible() expect(page).to_have_title("订单详情") expect(locator).to_have_text("已发货")

它的默认超时是 5 秒,期间会反复检查,直到通过或超时。这种“不断重试直到满足”的机制,才是稳定测试用例的底气。

5.2 用例组织、并行执行与 fixture

写测试用例不是把脚本堆在一起。我比较常用的组织方式:

def test_add_to_cart(page): page.goto("https://example.com/product/1001") page.get_by_role("button", name="加入购物车").click() expect(page.get_by_text("已加入")).to_be_visible()

这里page是测试框架自动注入的 fixture。每个测试用例拿到的是一个独立 context,互不干扰,天然适合并行。

运行测试时:

pytest --workers=4

加--headed可以看着浏览器跑,加--trace=on会在失败时生成 trace 文件。浏览器模式用 headless 时,跑完还能出报告。pytest-playwright 插件直接把--reporter=html这类参数也接进来了,报告里能看到每步的截图和 console 报错,排查问题比以前只看 print 日志省力太多。

5.3 codegen:先录制脚本,再改造成用例

新手最好的起步方式不是手写 API,而是用代码生成器:

npx playwright codegen https://example.com

会弹出一个浏览器窗口和一个实时生成代码的面板。你在页面上点一点、填一填表单,代码就自动出来了。拿这份代码作为骨架,再补断言、补参数,比自己凭记忆写快得多。

我的用法是:先开 codegen 把关键路径走一遍,确认每一步的 locator 生成了什么,再回到编辑器里整理。尤其适合第一次接触某个业务系统、需求又不复杂的时候。

trace 查看器的用法也值得单独说:运行npx playwright show-trace trace.zip,能看到页面每一步的 DOM 快照、网络请求、console 日志、鼠标事件时间线。遇到偶发失败,它不是玄学,而是能把当时的页面状态完整带回来给你看。这也是 Playwright 在调试体验上碾压 Selenium 的一个点。

6. 进阶玩法:AI 驱动的 midscene 生态与自动化红线

6.1 midscene 被 Playwright 调用的原理

“midscene 被 Playwright 调用的原理”这个搜索热词,指向的是 AI Agent 和浏览器自动化结合的方向。我按自己的理解拆一下,不一定完全贴合源码,但链路是对的。

midscene 这类工具把“大模型理解界面”和“Playwright 操作浏览器”两者拆开了。大模型负责“看”,Playwright 负责“做”。大致流程是:

  1. midscene 通过 Playwright 连接到浏览器(常见是 CDP 协议连接已有 Chrome,或由 Playwright 启动一个新浏览器)。
  2. 把当前页面的可交互元素、文本、截图等信息整理成模型可读的内容。
  3. 模型根据用户给出的自然语言任务,决策下一步应该点击哪个按钮、输入什么内容。
  4. midscene 把这个决策转成具体的 Playwright 操作(click、fill、press 等)并执行。
  5. 循环这个过程,直到任务完成。

对 Playwright 使用者来说,这意味着不必再写死每一步的操作逻辑,可以用一句“帮我把这个列表导出成 Excel”让模型自己规划步骤。但对新手,我的建议仍然是:先把 Playwright 本身搞熟,再上 AI 层。因为 AI 生成的操作步骤最终还是要落到 locator 和 page API 上,你不懂底层,出了问题完全没法调试。

6.2 关于“过瑞数”这类对抗技术的态度

搜索热词里出现了“playwright 过瑞数”。关于这类型自动化对抗的话题,我明确说:绕过反爬不是自动化技术的正确用法,我也不在这里讲任何绕过手段。

真实项目里遇到高强度的风控,正确的处理路径是我一贯坚持的三条:

  • 有官方 API 就优先用官方 API,成本和时间都最少。
  • 没有 API 的,确认数据是否公开可见,控制采集频率,必要时先取得授权。
  • 不要为了绕过反爬而去伪装、对抗,这既可能违反目标平台服务条款,也会让你的 IP、账号、业务都处在不确定风险里。

反爬和绕过反爬是军备竞赛,普通项目掺和进去只有劣势。把精力放在合规数据源和真正的业务逻辑上,长期看更划算。

6.3 新手进阶路线图

最后给你一条我自己验证过的进阶路径,按这个顺序学不会乱:

  1. 先跑通一个简单页面:启动浏览器、打开页面、用 locator 拿到一段标题。
  2. 理解 context 的价值:做一次“保存登录态-下次复用”,你会发现爬虫和测试的很多问题迎刃而解。
  3. 学会监听事件:page.on("request")、page.on("response"),观察页面到底调了哪些接口。这一步开始,你能区分“页面静态渲染”和“接口动态返回”。
  4. 用它做一次真实的动态页面采集(比如你平时会手动打开搜索的某个业务页面)。
  5. 再看 codegen 和 trace,把调试效率提起来。
  6. 最后,配合 pytest 或 Node 的测试 runner,把手动流程固化成测试用例。

我个人在实际操作中的体会是:Playwright 学习曲线最陡的其实是“心态转变”——从 Selenium 那种“等固定时间、手动找元素”的思路,切换到“自动等待、语义化定位、事件驱动”上来。这个弯转过来了,后面基本就是一马平川。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询