查看: 1237|回复: 3

Python Playwright自动化:Locator自动等待实战

[复制链接]
发表于 昨天 10:00 | 显示全部楼层 |阅读模式
网页自动化最容易被三件事拖住:chromedriver 版本必须和本机 Chrome 严丝合缝,浏览器一自动升级,脚本第二天就抛 SessionNotCreatedException;页面还没渲染完就 find_element,直接报 NoSuchElementException,只能满屏写 time.sleep(3);再换到 Firefox、Edge,驱动环境又得重来一遍。

Playwright 由微软团队维护,Python 版 API 设计得比较干净,刚好把这三点一起处理掉:自带浏览器内核(一条命令下载,版本天然匹配)、操作自带自动等待(不必手写 sleep)、一套 API 通吃 Chromium / Firefox / WebKit。

一、环境准备:两条命令

Playwright 支持 Python 3.8 及以上,同样建议用虚拟环境隔离依赖:
  1. python -m venv .venv
  2. source .venv/bin/activate   # Windows 用 .venv\Scripts\activate
  3. # 第一步:装 Python 包
  4. pip install playwright
  5. # 第二步:下载浏览器内核(约 100~300MB,只需执行一次)
  6. playwright install chromium
复制代码

第二步是新手最容易卡住的地方:它下载的是 Playwright 自带的浏览器,不是你桌面上的 Chrome,所以不存在版本对不上的问题。国内下载慢可以走镜像加速:
  1. # 使用国内镜像加速下载
  2. PLAYWRIGHT_DOWNLOAD_HOST=https://cdn.npmmirror.com/binaries/playwright playwright install chromium
复制代码

装完先跑一段最小验证代码:
  1. from playwright.sync_api import sync_playwright
  2. with sync_playwright() as p:
  3.     browser = p.chromium.launch(headless=True)
  4.     page = browser.new_page()
  5.     page.goto("https://example.com")
  6.     print("页面标题:", page.title())
  7.     browser.close()
复制代码

能打印出 Example Domain,就说明环境和浏览器内核都没问题。

二、最小可运行示例:打开页面并截图

Playwright 提供同步和异步两套 API,日常脚本用同步版最省心。下面的代码打开 python.org 并截取整页长图:
  1. from playwright.sync_api import sync_playwright
  2. with sync_playwright() as p:
  3.     # headless=True 表示无界面运行;调试时改成 False 能看到真实浏览器
  4.     browser = p.chromium.launch(headless=True)
  5.     page = browser.new_page(viewport={"width": 1280, "height": 800})
  6.     page.goto("https://www.python.org/")
  7.     # full_page=True 会截取整页长图,不只是可视区域
  8.     page.screenshot(path="python_org.png", full_page=True)
  9.     browser.close()
复制代码

对比 Selenium 同样的功能,这里少了驱动配置、少了显式等待,代码块从十几行压到十行以内。

三、三个核心概念:Locator、自动等待、上下文

1. Locator:定位元素的新写法

Playwright 推荐用 locator() 拿到元素句柄,后续操作都基于它,下面几种写法等价:
  1. # 下面几种写法等价,选你顺手的
  2. page.locator("button#submit").click()
  3. page.locator("text=登录").click()
  4. page.get_by_role("button", name="登录").click()   # 按语义角色,最稳
  5. page.get_by_placeholder("请输入用户名").fill("admin")
  6. page.get_by_text("下一步").click()
复制代码

Locator 是惰性的:写 page.locator(...) 时并不会真的去查 DOM,只有执行 .click()、.fill() 时才查找,并且会自动等到元素可见、可点击、不被遮挡。这就是“自动等待”的实现基础。

2. 自动等待:和 time.sleep 说再见

官方断言工具 expect 会轮询等待,而不是一次性判断,比 assert page.title() == ... 这类写法可靠得多:
  1. from playwright.sync_api import expect
  2. # 断言元素可见(默认超时 5 秒,期间反复重试)
  3. expect(page.get_by_text("提交成功")).to_be_visible(timeout=10000)
  4. # 断言 URL 变化
  5. expect(page).to_have_url("https://example.com/dashboard")
复制代码

需要改全局超时可以直接设置默认值:
  1. page.set_default_timeout(15000)   # 所有操作默认最多等 15 秒
复制代码

3. 上下文:多开互不干扰的会话

context 相当于一个独立的浏览器会话,拥有独立的 Cookie 和缓存,比开多个 browser 省资源,也可以用来模拟移动端和中文环境:
  1. with sync_playwright() as p:
  2.     browser = p.chromium.launch()
  3.     # 模拟移动端 + 中文环境
  4.     ctx = browser.new_context(
  5.         viewport={"width": 390, "height": 844},
  6.         user_agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15",
  7.         locale="zh-CN",
  8.     )
  9.     page = ctx.new_page()
  10.     page.goto("https://example.com")
  11.     ctx.close()      # 关上下文,Cookie 一起清掉
  12.     browser.close()
复制代码

四、进阶操作:表单、上传下载与请求拦截

1. 填表单与键盘鼠标
  1. page.get_by_label("用户名").fill("zhangsan")
  2. page.get_by_label("密码").fill("secret123")
  3. page.get_by_label("记住我").check()
  4. page.get_by_role("combobox").select_option("beijing")
  5. page.keyboard.press("Enter")
复制代码

2. 文件上传与下载

上传直接给本地路径,不用再操作系统文件选择框;下载必须用 expect_download 包住触发动作,否则拿不到下载对象:
  1. # 上传:直接给本地路径,不用再找系统文件框
  2. page.set_input_files("input[type=file]", "report.xlsx")
  3. # 下载:必须用 expect_download 包住触发动作
  4. with page.expect_download() as dl:
  5.     page.get_by_text("导出报表").click()
  6. download = dl.value
  7. download.save_as("data/export.xlsx")
复制代码

3. 拦截请求:屏蔽图片提速

抓数据时图片、字体往往没用,直接拦掉能明显加速:
  1. def block_images(route):
  2.     if route.request.resource_type in ("image", "font", "media"):
  3.         route.abort()
  4.     else:
  5.         route.continue_()
  6. page.route("**/*", block_images)
  7. page.goto("https://news.example.com")
复制代码

五、实战:带登录态抓取分页列表

下面这段完整代码把登录、断言等待、分页抓取、保存登录态串在一起,下次运行可以直接复用 auth.json,省掉重复登录:
  1. from playwright.sync_api import sync_playwright, expect
  2. BASE = "https://example.com"
  3. def main():
  4.     with sync_playwright() as p:
  5.         browser = p.chromium.launch(headless=True)
  6.         ctx = browser.new_context(locale="zh-CN")
  7.         page = ctx.new_page()
  8.         page.set_default_timeout(15000)
  9.         # 1. 登录
  10.         page.goto(f"{BASE}/login")
  11.         page.get_by_label("账号").fill("your_account")
  12.         page.get_by_label("密码").fill("your_password")
  13.         page.get_by_role("button", name="登录").click()
  14.         # 等到跳转完成,不要用 sleep
  15.         expect(page).to_have_url(f"{BASE}/dashboard")
  16.         # 2. 抓多页列表
  17.         rows = []
  18.         for page_no in range(1, 4):
  19.             page.goto(f"{BASE}/items?page={page_no}")
  20.             expect(page.locator(".item-row").first).to_be_visible()
  21.             items = page.locator(".item-row").all()
  22.             for it in items:
  23.                 rows.append({
  24.                     "title": it.locator(".title").inner_text(),
  25.                     "price": it.locator(".price").inner_text(),
  26.                 })
  27.             print(f"第 {page_no} 页抓到 {len(items)} 条")
  28.         # 3. 保存登录态,下次直接复用,省掉重复登录
  29.         ctx.storage_state(path="auth.json")
  30.         print("共抓取", len(rows), "条")
  31.         browser.close()
  32. if __name__ == "__main__":
  33.     main()
复制代码

下次运行时直接加载已保存的登录态:
  1. ctx = browser.new_context(storage_state="auth.json")
复制代码

六、和 Selenium 怎么选

从驱动、等待、多浏览器、录制、网络拦截几个维度对比:


  • 浏览器驱动:Playwright 自带,一条命令安装;Selenium 需手动下载,版本必须匹配。
  • 等待机制:Playwright 操作自带自动等待;Selenium 需自己写 WebDriverWait 或 sleep。
  • 多浏览器:Playwright 一套 API 支持 Chromium/Firefox/WebKit;Selenium 每个浏览器各自适配。
  • 录制脚本:Playwright 官方支持 playwright codegen;Selenium 依赖第三方插件。
  • 网络拦截:Playwright 原生 page.route;Selenium 需借助代理。
  • 生态与资料:Playwright 较新,中文资料相对少;Selenium 十年积累,资料最多,岗位要求常见。


结论:新项目优先 Playwright,效率和稳定性都更好;老项目或需要兼容既有 Selenium 资产的继续用 Selenium,两者并不互斥。

七、常见坑与解决办法

1)playwright install 卡住或超时

换国内镜像(见第一节)。公司网络有代理时,先设置 HTTPS_PROXY 环境变量再执行安装。

2)locator.click() 超时

先确认目标是不是在 iframe 里。跨框架要先切进去再操作:
  1. frame = page.frame_locator("iframe#login-frame")
  2. frame.get_by_role("button", name="登录").click()
复制代码

3)被网站识别为自动化

默认 headless 会带自动化特征,可以加启动参数缓解:
  1. browser = p.chromium.launch(
  2.     headless=True,
  3.     args=["--disable-blink-features=AutomationControlled"],
  4. )
复制代码

4)抓到的中文乱码

确认 new_context(locale="zh-CN"),并且写入文件时指定编码 encoding="utf-8"。Windows 上尤其容易踩这个坑。

5)调试时看不到过程

打开录制,事后回看每一步:
  1. ctx.tracing.start(screenshots=True, snapshots=True)
  2. # ... 你的操作 ...
  3. ctx.tracing.stop(path="trace.zip")
复制代码

再用 playwright show-trace trace.zip 在浏览器里逐步回放。

八、总结与下一步

Playwright 的三个核心优势可以概括为:自带浏览器(告别驱动地狱)、自动等待(告别满屏 sleep)、统一 API(一套代码跑三种内核)。如果之前被 Selenium 的驱动和等待折腾过,迁移过来基本是纯收益。

下一步可以从三件事入手:用 playwright codegen https://你的目标站点 录一遍,观察官方推荐的选择器写法;把现有脚本里的 time.sleep 逐步替换成 expect(...) 断言;需要并发时改用异步 API(from playwright.async_api import async_playwright)配合 asyncio.gather。
回复

使用道具 举报

发表于 昨天 19:00 | 显示全部楼层

Re: Python Playwright自动化:Locator自动等待实战

楼主这篇总结得很到位,尤其是把 Selenium 里驱动版本、显式等待、跨浏览器环境这三个坑串起来讲,读起来很有共鸣。Locator 惰性查找加自动等待这点很关键,以前满屏写 time.sleep 确实难受,expect 轮询断言这个细节也很实用。环境准备部分两步走和国内镜像加速对新手很友好,最小验证代码也能快速确认装没装好。上下文模拟移动端和中文环境这点我还没试过,感觉做多会话挺方便。想问下如果要跑 Firefox 或 WebKit,是不是也要再单独执行一次对应的浏览器内核下载?整体写得很系统,准备照着跑一遍。
回复 支持 反对

使用道具 举报

发表于 昨天 19:10 | 显示全部楼层

Re: Python Playwright自动化:Locator自动等待实战

感谢分享,这篇把 Playwright 的几个核心点讲得挺清楚。自带浏览器内核、自动等待、一套 API 通吃,确实比 Selenium 省心很多。Locator 惰性查找和 expect 轮询断言这两块很实用,以前写 time.sleep 和一次性 assert 经常踩坑。国内镜像加速那条也记下了,准备按最小示例先跑一下。想请教下,如果元素在 iframe 里,是不是要先用 frame_locator 切换进去,自动等待还能正常生效吗?另外上下文模拟移动端那段似乎没贴完,有空期待补全。
回复 支持 反对

使用道具 举报

发表于 昨天 19:20 | 显示全部楼层

Re: Python Playwright自动化:Locator自动等待实战

感谢楼主分享,这篇把 Playwright 解决驱动版本、自动等待和跨浏览器这三点讲得挺直观。环境准备那两步确实是新手最容易卡的地方,尤其要理解下载的是自带浏览器内核,不是本机 Chrome,不然容易一直纠结版本问题。locator 惰性查找配合自动等待的思路很实用,expect 轮询断言也比一次性 assert 稳,能少写很多 sleep。context 独立会话这点对多账号或模拟移动端也很友好,比反复开浏览器省资源。期待后面继续把上下文和同步、异步 API 的取舍讲完。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-9-29 06:17 , Processed in 0.045999 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部