拓冰建站拓冰建站
首页 / 资讯中心 / 正文

从Selenium到Playwright:WEB自动化框架核心解析与实战指南

之前做 WEB 自动化的时候不少人在 Selenium 和浏览器驱动的版本匹配上耗费过时间Chrome 一升级脚本就报错下载驱动还要先判断版本号。后来开始接触 Playwright发现这些常见的自动化痛点正在被系统性地解决内置浏览器管理、自动等待、原生多标签页与 iframe 支持定位器 API 也更接近用户的真实操作习惯。这篇文章会围绕 Playwright 做一个完整梳理从环境安装、核心 API、录制脚本、反检测原理、常见报错到工程化实践都会覆盖适合从 Selenium 迁移过来的测试、爬虫或后端开发同学。1. Playwright 是什么和 Selenium 有什么区别1.1 从 Selenium 到 Playwright搜索热词里经常能看到“playwright自动化框架”“selenium自动化测试框架”“playwright中文手册”这些关键词说明大家正在积极寻找新的 WEB 自动化方案。Selenium 诞生时间很早它的设计思路是模拟用户对浏览器进行操作通过 WebDriver 协议与浏览器通信。这套方案成熟且生态完善但也带来了几个典型问题需要单独下载与浏览器版本匹配的驱动驱动下载和版本匹配很容易出错脚本稳定性依赖显式等待和 sleep工作量偏大跨浏览器、跨平台时的兼容成本也比较高。Playwright 是微软开源的一个自动化测试库支持 Chromium、Firefox 和 WebKit。它最大的特点是通过 CDPChrome DevTools Protocol直接与浏览器通信并内置了浏览器下载管理。也就是说你不用像 Selenium 那样先纠结“浏览器驱动怎么判断下载哪个”也不需要去匹配驱动版本Playwright 会为指定版本的浏览器自动做捆绑和处理。与此同时它还内置了可操作性检查点击、填表之前会自动判断元素是否可见、稳定、可交互这从底层就解决了脚本时报“元素不存在”或“元素不可点击”的常见问题。1.2 Playwright 的核心能力先不用深入代码我们从能力视角把 Playwright 的亮点列出来自动等待内置等待机制元素不稳定时会等到稳定后再操作而不是盲目 sleep。定位器Locator通过 role、label、test-id、文本等语义化方式定位元素比 XPath 可读性好且自动重试。多浏览器支持Chromium、Firefox、WebKit 一套 API 跑通。多标签页和 iframe 原生支持不需要像 Selenium 一样手动切换 context。网络请求拦截可以直接 mock 接口、模拟慢网络、设置路由方便做接口测试和异常场景。录制生成脚本通过 Playwright Codegen 录制页面操作并快速生成脚本。截图与视频录制失败时保存现场方便排查问题。移动端模拟可以模拟 iPhone、Android 设备的环境配合移动端 Web 调试。这些能力让 Playwright 不只适合做自动化测试也非常适合做数据采集、页面监控、UI 回归。很多团队在做前端 bug 排查时也会用 playwright codegen 快速录制操作步骤帮助复现问题。1.3 Selenium 与 Playwright 的关键对比对比维度SeleniumPlaywright浏览器驱动需要手动下载对应版本驱动安装时自动下载浏览器无需单独匹配安装成本相对繁琐pip 安装后执行 install 即可等待元素主要靠显式等待或强制 sleep内置自动等待和可操作性检查定位方式find_element XPath/CSSLocator语义化定位更友好iframe 切换需要 switch_to_frame直接定位 iframe 内元素多标签页需要切换 window handle原生多页面管理自动等待新页面网络拦截支持较弱或依赖第三方库原生支持路由拦截、模拟接口录制脚本需要额外工具或 IDE 插件Codegen 一键生成脚本手机浏览器模拟主要靠 device emulation 配置内置常用 device descriptor这里要澄清一点很多文章喜欢用“Selenium 退出历史舞台”这种标题但现实情况是 Selenium 依然是很多存量项目的选择生态也很大。真正发生的变化是新建自动化项目时越来越多团队会优先评估 Playwright因为它解决了很多 Selenium 的“脏活累活”。更准确的说法是Playwright 代表了 WEB 自动化的新方向值得花时间系统学习。2. 环境准备与安装2.1 运行环境说明本文示例以 Python 3.10 环境为主操作系统使用 Windows 或 Linux 均可。如果需要在 CI 环境或者 Docker 中运行安装思路是一样的只需要注意在 Linux 环境下安装必要的系统依赖。版本方面不同阶段安装的 Playwright 版本会有差异本文不写死某个版本号大家按自己的项目实际锁定版本即可重点是理解安装流程和配置思路。推荐使用虚拟环境避免污染系统 Pythonpython -m venv .venvWindows 激活虚拟环境.venv\Scripts\activateLinux/macOS 激活虚拟环境source .venv/bin/activate如果你的项目里已经使用了 Anaconda 或者 poetry也可以用自己习惯的方式重点是后面安装的 playwright 库要和浏览器文件能够对应。2.2 安装 Playwright 与浏览器激活虚拟环境之后先安装 Playwright Python 包pip install playwright安装完成之后还需要下载浏览器内核。这一步是 Playwright 相比 Selenium 的一大优势不需要再去第三方网站手动下载驱动也不用关心“selenium 浏览器驱动怎么判断下载哪个”这类问题直接执行下面命令即可playwright install默认情况下这个命令会下载 Chromium、Firefox 和 WebKit 三套浏览器。如果只需要其中一种比如只需要 Chromium可以这样安装playwright install chromium如果网络环境受到限制也可以使用镜像源或离线安装包。离线安装的场景经常出现在服务器上需要提前在有网络的机器上把浏览器下载好拷贝到目标机器后再将PLAYWRIGHT_BROWSERS_PATH环境变量配置到浏览器目录。离线和内网环境下优先考虑提前把浏览器包准备好而不是在安装阶段反复重试。2.3 验证安装是否成功安装完成后可以运行一个最简脚本确认环境正常# quick_check.py from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch(headlessTrue) page browser.new_page() page.goto(https://example.com) print(page.title()) browser.close()运行python quick_check.py如果输出Example Domain说明 Playwright 已经能正常启动浏览器并访问页面。如果报错优先检查是否执行过playwright install以及系统依赖是否完整。3. 核心 API 与基础用法3.1 同步 API 与异步 APIPlaywright 的 Python 库提供了两套写法sync_playwright和async_playwright。同步 API 适合普通脚本、测试用例、爬虫任务几乎所有场景都可以用它完成代码也更好理解。异步 API 适合目标网页耗时较长、需要并发处理多个页面或与 asyncio 生态融合的场景。最简单的同步示例from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch(headlessFalse) page browser.new_page() page.goto(https://www.baidu.com) page.fill(#kw, playwright) page.click(#su) page.wait_for_timeout(3000) browser.close()这里需要注意headlessFalse表示有头模式方便观察浏览器行为headlessTrue则用于服务器或 CI 环境。脚本最后关闭浏览器避免残留进程。异步版本代码如下import asyncio from playwright.async_api import async_playwright async def main(): async with async_playwright() as p: browser await p.chromium.launch(headlessTrue) page await browser.new_page() await page.goto(https://example.com) title await page.title() print(title) await browser.close() asyncio.run(main())在大型采集任务中可以用异步写法同时打开多个页面配合asyncio.gather明显提升效率。不过异步调试起来更麻烦普通任务先掌握同步写法就够了。3.2 定位器 Locator 与自动等待Selenium 时代最常见的操作是find_element元素不在 DOM 中时会立刻抛异常所以开发者不得不写显式等待。Playwright 的 Locator 不只是定位元素它还会自动判断元素的可见性、稳定性、可交互性并自动重试。这个设计让很多“不确定元素什么时候出现”的页面场景变得很稳定。常见定位方式举例page.locator(#kw).fill(playwright) page.get_by_role(button, name百度一下).click() page.get_by_label(用户名).fill(admin) page.get_by_text(注册).click() page.get_by_test_id(submit).click()使用建议get_by_role是推荐首选因为它更接近用户看页面的方式。get_by_text适合点击包含指定文本的元素。get_by_label对表单字段很友好可以直接关联 label。get_by_test_id依赖开发埋入的># 定位 iframe 内部的输入框 frame page.frame_locator(iframe[srclogin.html]) frame.get_by_placeholder(邮箱).fill(demoexample.com)多标签页方面Playwright 的 context 概念比 Selenium 的 window handle 更好用。每个 browser context 相当于一个独立的浏览器会话cookie、localStorage 都是隔离的context browser.new_context() page1 context.new_page() with context.expect_page() as new_page_info: page1.click(a[target_blank]) new_page new_page_info.value new_page.wait_for_load_state()这样处理新标签页时不需要通过 window_handles 去切换直接用新拿到的 Page 对象操作即可。3.4 截图、下载文件与请求拦截自动化脚本失败后截图是很有用的现场证据。Playwright 截图很方便page.screenshot(pathscreenshot.png, full_pageTrue)下载文件的处理也很直接with page.expect_download() as download_info: page.locator(button:has-text(导出)).click() download download_info.value download.save_as(data.xlsx)网络请求拦截适合 mock 接口、模拟异常场景也可以在采集时过滤不必要的静态资源def block_resource(route): if route.request.resource_type image: route.abort() else: route.continue_() page.route(**/*, block_resource)这种网络拦截能力在 Selenium 中通常需要结合 BrowserMob 或 mitmproxy 才能实现在 Playwright 里是内置能力。如果你要做接口层面的异常模拟这个特性非常方便。3.5 连接已存在的浏览器实例有些场景并不是让 Playwright 自动启动浏览器而是希望连接到一个已经运行的浏览器进程。典型场景包括调试 Electron 应用内嵌的浏览器、远程 Chrome 调试、以及复用已经登录好的浏览器会话。Playwright 可以通过 CDP 协议连接这些浏览器# 先以远程调试模式启动 Chrome例如 chrome --remote-debugging-port9222 browser p.chromium.connect_over_cdp(http://localhost:9222) default_context browser.contexts[0] page default_context.pages[0] print(page.title())这种方式在处理 Electron 内嵌浏览器时很实用。需要注意目标进程必须开启远程调试端口且连接方与目标进程需要具备相同的访问权限。线上环境不建议随意开放调试端口避免被恶意利用。4. 完整实战案例登录并采集列表数据4.1 场景分析与设计这一节我们做一个可以运行的实战案例完整覆盖 Playwright 的常用能力。假设有一个管理后台需要登录后进入用户列表页面采集用户名、手机号和注册时间最后保存到 CSV 文件。真实场景中后台地址、账号、密码必须由你本人所在的测试环境提供。为了演示我把关键地址写为占位地址https://your-test-server.com你在本地运行时要替换成实际环境地址。设计思路如下创建独立的 browser context让会话之间互相隔离。定位登录表单时优先使用 get_by_label。后台页面可能有动态渲染采集时使用 locator.wait_for() 确保列表出现。采集完成后把数据写入 CSV最后截图留档。这里要特别提醒涉及登录、采集的操作如果目标系统是线上系统必须事先获得对方授权。自动化访问不能作为绕过权限的理由务必在测试环境验证并遵循平台条款。4.2 编写同步脚本创建文件login_and_scrape.pyimport csv from playwright.sync_api import sync_playwright LOGIN_URL https://your-test-server.com/login USERNAME your_username PASSWORD your_password def main(): with sync_playwright() as p: browser p.chromium.launch(headlessTrue) context browser.new_context() page context.new_page() # 1. 打开登录页 page.goto(LOGIN_URL) page.wait_for_load_state(networkidle) # 2. 登录 page.get_by_label(用户名).fill(USERNAME) page.get_by_label(密码).fill(PASSWORD) page.get_by_role(button, name登 录).click() # 3. 等待列表页跳转URL 模式按实际环境调整 page.wait_for_url(**/user/list) # 4. 等待关键列表元素出现 rows page.locator(table tbody tr) rows.first.wait_for() # 5. 提取每一行数据 data [] row_count rows.count() for i in range(row_count): row rows.nth(i) cells row.locator(td) data.append({ username: cells.nth(0).inner_text(), phone: cells.nth(1).inner_text(), create_time: cells.nth(2).inner_text(), }) # 6. 保存到 CSV with open(users.csv, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[username, phone, create_time]) writer.writeheader() writer.writerows(data) # 7. 截图留档 page.screenshot(pathuser_list.png, full_pageTrue) browser.close() print(f采集完成共 {len(data)} 条数据) if __name__ __main__: main()这段代码里先用wait_for_load_state(networkidle)等待页面网络空闲再用wait_for_url确认登录完成跳转。Playwright 的 locator 会等待元素可交互所以不需要手动 sleep。最后把 CSV 写成 utf-8-sig是为了让 Excel 打开时不出现中文乱码。4.3 运行与验证在目标测试环境正确配置账号后运行python login_and_scrape.py预期输出采集完成共 50 条数据同时目录下会生成users.csv和user_list.png。这个例子看似简单但它已经串起了自动等待、定位器、截图、文件输出这些常用能力。如果运行后提示找不到用户名输入框原因可能是页面用了自定义组件label 绑定没有生效。可以先使用page.content()输出页面 HTML 查看实际结构或者改用 placeholder、test-id 定位。注意改动只需要影响定位方式不需要改变整体脚本结构这正是 Locator API 的优势。4.4 用异步 API 扩展并发采集如果列表不止一页也需要并发处理多个页面可以参考异步写法。下面是一个思路示例import asyncio import csv from playwright.async_api import async_playwright async def fetch_page_data(page, page_no): await page.goto(fhttps://your-test-server.com/user/list?page{page_no}) await page.locator(table tbody tr).first.wait_for() rows page.locator(table tbody tr) items [] for i in range(rows.count()): cells rows.nth(i).locator(td) items.append({ username: await cells.nth(0).inner_text(), phone: await cells.nth(1).inner_text(), create_time: await cells.nth(2).inner_text(), }) return items async def main(): async with async_playwright() as p: browser await p.chromium.launch(headlessTrue) context await browser.new_context() # 登录逻辑与同步版本一致此处省略 pages [] for _ in range(3): pages.append(await context.new_page()) results await asyncio.gather( fetch_page_data(pages[0], 1), fetch_page_data(pages[1], 2), fetch_page_data(pages[2], 3), ) all_items [item for sublist in results for item in sublist] with open(users_async.csv, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[username, phone, create_time]) writer.writeheader() writer.writerows(all_items) await browser.close() asyncio.run(main())需要注意并发页面越多对目标服务器的压力也越大需要控制并发数量避免影响业务。如果只是做常规测试同步写法完全够用不一定需要异步。5. 使用 Codegen 快速生成脚本5.1 Codegen 基本用法Playwright 自带的 Codegen 录制工具可以节省大量手写定位器的时间。在命令行执行playwright codegen https://your-test-server.com/login打开录制窗口后你在页面上点击、输入、跳转工具会自动生成对应的 Python 脚本。录制结束时复制生成的代码即可。这个功能非常适合 UI 自动化的前 80% 工作尤其是页面结构复杂时录制出来的定位方式通常比手写 XPath 更稳定。5.2 录制后的脚本优化思路录制生成的脚本能跑但不等于可以直接上线。需要优化的点主要有三个将复杂 CSS 替换为语义化定位。录制器有时会生成较长 CSS 路径可以手动改成 get_by_role 或 get_by_label。删除无用的等待。录制过程中你停留的时间会被生成 wait_for_timeout这些需要根据场景清理。添加断言。录制的脚本没有校验结果需要补充 expect 断言来确认操作成功。比如录制后会看到这样的代码# 原始录制代码需要手动优化 page.wait_for_timeout(3000) page.click(#loginBtn)优化后page.get_by_role(button, name登录).click() expect(page).to_have_url(**/user/list)5.3 录制脚本的局限录制脚本适合快速验证流程和生成初稿但它不是万能的。动态加载、异步渲染、验证码、高权限场景录制工具很难完全拟真。尤其是页面元素被混淆或使用了复杂前端框架时录制出来的定位器可能非常脆弱。实际项目中建议把录制当作“起点”而不是“终点”最终脚本仍然需要人工 review 和稳定化处理。6. 网站如何检测 Playwright反检测与合规边界6.1 检测自动化浏览器的常见手段搜索热词里经常出现“网站如何检测到被playwright控制”和“playwright反检测”这说明大家比较关心自动化痕迹。网站检测自动化的手段通常包括navigator.webdriver 属性几乎所有自动化驱动都会把这个属性设置为 true。浏览器指纹比如常见的 window.chrome、navigator.plugins、navigator.languages 等特征。CDP 痕迹Playwright 默认会暴露 devtools 状态或 debugger 特征。行为特征鼠标轨迹、键盘事件过于线性点击间隔过于均匀。请求顺序与资源加载自动化的请求顺序通常比真实用户更规律。从原理上讲任何自动化工具都不可能完全模拟真实用户检测方只要足够细致总能找到差异。所谓“反检测”只是提高模拟程度不代表绝对隐身。6.2 测试环境下的浏览器伪装思路如果只是在自己的测试环境跑脚本不需要很重的伪装。但如果需要模拟更多真实用户特征比如移动端 H5 测试可以这样配置from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch( headlessFalse, args[ --disable-blink-featuresAutomationControlled, ] ) context browser.new_context( viewport{width: 375, height: 812}, user_agentMozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X), localezh-CN, ) # 移除 webdriver 标记 context.add_init_script(Object.defineProperty(navigator, webdriver, {get: () undefined});) page context.new_page() page.goto(https://example.com)这段代码做了三件事关闭 Chromium 的部分自动化控制标记模拟 iPhone 的 UA 和视口再通过 add_init_script 去掉 navigator.webdriver 特征。这些方法可以用在移动端兼容测试和调试中但不能用来绕过线上平台的认证、验证码或风控体系。6.3 合规提醒对自家系统做自动化回归、对公开页面做合理采集是正常需求。但使用 Playwright 模拟登录、绕过滑块验证、对抗网站风控属于灰色甚至违规操作。特别要注意不要对未取得授权的线上系统做爬取和压测。不要使用 Playwright 绕过登录认证、验证码或反爬机制。对生产环境的任何自动化操作都要提前评审并获得授权。数据采集时遵循 robots 协议和平台条款不要采集个人隐私数据。所以网上流传的“playwright 过瑞数”“playwright 过验证码”之类的方案本文不展开也不建议在实际业务中使用。做技术本身没有错但使用方式需要守住边界。7. 常见问题与排查思路7.1 高频问题速查表问题现象常见原因解决思路启动浏览器失败浏览器内核未安装或系统依赖缺失执行 playwright install必要时运行 install-deps运行时报 target closed页面或 context 被提前关闭通常由异步逻辑引起检查是否有后台任务提前关闭浏览器避免竞态定位不到元素页面动态加载太慢或定位器表达式不正确用 locator.wait_for() 等待优先使用 get_by_role元素被遮挡无法点击页面上有弹窗、遮罩层或 loading 动画关闭多余弹窗或使用 click(forceTrue) 但需慎重headless 模式下登录不成功网站对无头浏览器有检测或 JS 执行差异使用有头模式调试或配置真实 UA 和上下文下载文件无法保存缺少 expect_download 回调用 with page.expect_download() 包裹触发下载操作iframe 内元素定位失败没有使用 frame_locator使用 page.frame_locator(iframe[...]) 逐层定位7.2 启动浏览器失败的详细排查“playwright 启动浏览器失败”很常见。如果是 Linux 服务器环境通常是缺少系统依赖。在 Debian/Ubuntu 上可以先执行sudo playwright install-deps chromium如果依然失败检查环境变量PLAYWRIGHT_BROWSERS_PATH指向的目录是否存在尤其是离线安装场景。容器化环境下不要忘了在 Dockerfile 中把依赖和浏览器目录同时复制进去。7.3 target closed 问题的定位思路报错target closed: target page, context or browser has been closed通常发生在异步代码中。典型情况是某个任务还在使用 page 对象但浏览器已经被关闭或者同一个 page 被多个协程并发操作。排查时重点检查是否在 with 块外使用了 page 对象。是否在回调或后台任务中操作了已关闭的 context。是否启用了多线程但共享了同一个 browser 实例。解决方案是为每个线程或协程创建独立的 context避免多个任务共享同一个 page 对象。并发采集时尤其要注意这个错误。8. 最佳实践与工程建议8.1 定位器与选择器规范优先使用语义化定位get_by_role、get_by_label、get_by_test_id。如果页面缺少语义属性可以推动前端团队加入>context browser.new_context() context.set_default_timeout(15000)对于网络波动较大的场景不建议无限重试。建议设置最大重试次数并把失败详情记录到日志中。可以把失败截图统一保存到一个目录方便 CI 汇总分析。8.3 日志、截图与视频录制测试用例或采集任务稳定运行时不需要太多日志但一旦出问题日志和截图就是救命稻草。建议每个关键操作都打出步骤编号失败时截取当前页面try: page.locator(#submit).click() except Exception as e: page.screenshot(patherror_submit.png, full_pageTrue) raise e如果关心页面加载过程还可以开启 context 的 record_video_dir 录制会话视频。视频功能调试复杂交互时很有用但要注意视频文件会占用磁盘空间需要做好定时清理。8.4 并行执行与 CI/CD 集成在测试环境中可以用 pytest-playwright 插件组织测试用例按浏览器和 worker 并行运行。采集场景也可以使用多 context但要注意控制并发数量避免给目标站点带来压力。CI/CD 集成时最好在 Docker 镜像中预装浏览器。Dockerfile 思路如下FROM python:3.11-slim RUN pip install playwright playwright install chromium --with-deps构建镜像之后在流水线中运行具体脚本失败时把截图和日志作为 artifacts 上传。这样团队其他人可以直接复用环境不用重复踩坑。8.5 安全与生产环境注意事项生产环境操作前必须备份数据并走变更审批流程。账号密码不要硬编码在脚本里使用环境变量或密钥管理服务。涉及删除、更新数据的自动化流程先在小范围环境验证。处理用户个人信息时遵循最小化原则不要采集与任务无关的字段。脚本异常时要尽快释放浏览器资源避免内存泄漏和进程堆积。9. 总结与下一步学习建议到这里Playwright 的安装、核心 API、登录采集实战、Codegen 录制、反检测原理以及工程化建议都过了一遍。相比 SeleniumPlaywright 真正的优势不是“更强”这么简单而是把自动化开发体验系统性地提升了不需要匹配驱动内置自动等待原生支持 iframe 和多页面构造脚本的速度快很多。如果你是刚接触 WEB 自动化的新手建议先跑通第 2 节的快捷验证和第 4 节的完整案例再去看官方文档中 Locator 和 Context 的设计。如果你是从 Selenium 迁移过来的老手可以重点对比两者在等待机制、iframe、多页面处理上的差异把以前写的一堆 workaround 清理掉。后续可以继续学习的方向还有pytest-playwright 做测试用例管理、Playwright Test Runner 与 TypeScript 生态、性能测试中的数据 mock、移动端设备模拟和 CI 渲染集群。如果关注 AI 编程也可以留意 Playwright 官方与 AI 工具链的集成变化但这类方案还处在快速演化阶段落地时要以官方文档为准。核心思路始终是一致的把浏览器当作一个稳定的自动化执行环境把页面操作和校验写得足够清晰、足够可复现。如果这篇文章帮你解决了 Playwright 入门和迁移中的一些问题可以收藏备用。有问题也欢迎在评论区交流我会根据大家的反馈继续补充后续内容。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门