一句话总结:Playwright 解决的是「这个网页操作我每周要做一遍」的问题。这篇给三个可直接改用的模板:登录态复用(不用每次重新登录)、翻页采集(等对了才不丢数据)、截图对比(改版了能第一时间发现)。
先说它和 Selenium 的差别,因为这决定你要不要换。Playwright 自带浏览器、自带等待机制、原生支持文件下载和网络拦截,装一次 playwright install 就完事。代价是包大(几百 MB),但省下的是调驱动的痛苦。
前置
pip install playwright
playwright install chromium
只要 Chromium 就够跑大部分场景,需要测 Safari 内核时再加 webkit。
最小脚本
from playwright.sync_API import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://example.com", wait_until="domcontentloaded")
print(page.title())
page.screenshot(path="shot.png", full_page=True)
browser.close()
headless=False 建议先用来看清它在干什么,跑通了再改 True。
模板一:登录态复用
最烦的场景是「每天跑一次,每次都要登录」。登录本身不难,难的是验证码、二次验证、以及频繁登录触发的风控。
解法是把登录态存下来复用:
from playwright.sync_api import sync_playwright
def login_and_save():
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
ctx = browser.new_context()
page = ctx.new_page()
page.goto("https://example.com/login")
page.get_by_label("用户名").fill("your_user")
page.get_by_label("密码").fill("your_pass")
page.get_by_role("button", name="登录").click()
# 等到登录后的特征元素出现,再保存状态
page.get_by_role("navigation").wait_for(timeout=30_000)
ctx.stoRAGe_state(path="auth.json")
browser.close()
def reuse():
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
ctx = browser.new_context(storage_state="auth.json")
page = ctx.new_page()
page.goto("https://example.com/dashboard")
print(page.title())
browser.close()
storage_state 存的是 cookie 和 localStorage,一个 JSON 文件。这个文件等于你的登录凭证,别提交到 git,最好加进 .gitignore 并限制文件权限。
登录态会过期。工程上的处理是:先试复用,检测到被踢回登录页就自动走一次登录流程。这比「每n天手动更新一次」靠谱。
模板二:翻页采集
翻页的核心不是点击,是等待。点完下一页立刻取数据,拿到的还是上一页的内容,这是最常见的错误。
def crawl_list(uRL, pages=5):
rows = []
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto(url, wait_until="domcontentloaded")
for i in range(pages):
# 等表格出现并稳定
page.locator("table tbody tr").first.wait_for(timeout=15_000)
for tr in page.locator("table tbody tr").all():
cells = tr.locator("td").all_inner_texts()
if cells:
rows.append(cells)
nxt = page.get_by_role("button", name="下一页")
if not nxt.is_enabled():
break
# 关键:等 URL 或首行内容变化,再进入下一轮
first_before = page.locator("table tbody tr").first.inner_text()
nxt.click()
page.wait_for_function(
"prev => document.querySelector('table tbody tr')"
" && document.querySelector('table tbody tr').innerText !== prev",
arg=first_before, timeout=15_000,
)
browser.close()
return rows
那个 wait_for_function 是这段的核心。它等的不是「点击完成」,而是「内容真的变了」。用 sleep(1) 也能跑,但网慢就丢数据,网快就白等。
模板三:截图对比
页面改版、样式错乱、按钮被遮挡,肉眼很难每次发现。截图对比能自动盯住:
from PIL import Image, ImageChops
def diff_ratio(a_path, b_path):
a = Image.open(a_path).convert("RGB")
b = Image.open(b_path).convert("RGB")
if a.size != b.size:
return 1.0 # 尺寸变了,视为完全不同
diff = ImageChops.difference(a, b).convert("L")
changed = sum(1 for v in diff.getdata() if v > 12)
return changed / (a.size[0] * a.size[1])
用法是:第一次跑存基线,之后每次跑对比,变化超过阈值(比如 2%)就告警。
两个前提必须满足:同一浏览器版本、同一视口尺寸、同一无头模式。这三点任意一个变了,截图就会整体偏移,误报一片。
我自己的经验是,截图对比比断言元素存在更早发现「页面视觉崩了」这类问题,因为它不关心你的选择器,只看像素。
图 1:左为 Browser → Context → Page 的层级关系,右为三个模板的关键动作
定位器:优先用角色和文字
选择器的稳定性直接决定脚本的寿命。
优先级从高到低:
get_by_role("button", name="提交"):语义定位,最稳get_by_label("邮箱"):表单首选get_by_text("订单已创建"):适合断言get_by_test_id("subMIT-btn"):前端配合加data-testid,最可控- CSS / XPath:兜底方案,
div > div:nth-child(3)这种活不过一次改版
nth-child 这类索引选择器是最脆的。前端加了一个隐藏元素,你的脚本就全错位。
坑清单
坑一:用 time.sleep 等加载。 网快时白等,网慢时丢数据。全部换成 wait_for_selector、wait_for_function 或 expect(...)。
坑二:无头和有头模式截图不一致。 字体渲染、滚动条宽度都可能不同。做截图对比时,两种模式不能混着建基线。
坑三:下载文件没接住。 点下载按钮后要包在 with page.expect_download() as d: 里,否则文件落在临时目录被清掉。
with page.expect_download() as dl:
page.get_by_role("button", name="导出").click()
dl.value.save_as("report.xlsx")
坑四:元素在 iframe 里。 直接 page.locator 找不到。要用 page.frame_locator("#iframe-id").get_by_role(...)。
坑五:把登录态提交进了代码库。 auth.json 里有完整 cookie,泄露等于账号泄露。加 .gitignore,CI 里用环境变量或密钥管理。
坑六:遇到 Cloudflare 硬刚。 这类反爬不是「加个 UA」能过的。要么用官方 API,要么放慢频率,要么放弃。硬绕既不划算也容易踩合规线。
边界
自动化只对你有权操作的站点做。给你自己的后台、你公司的系统、明确允许自动化的公开数据源,这些没问题。
需要注意的三条:目标站点的服务条款是否允许自动化;请求频率会不会影响对方服务;抓到的数据怎么存、存多久。这三点比技术实现更值得先想清楚。顺带一提:如果目标是拿接口数据而不是操作页面,抓包工具比浏览器自动化直接得多,见抓包三件套那篇。
FAQ
和 Selenium 比该选哪个?
新项目选 Playwright。它的自动等待机制省掉大量 WebDriverWait 样板代码,调试工具(playwright codegen 录制)也好用得多。
能跑在服务器上吗?
能,Linux 上装依赖后 headless=True 直接跑。Docker 里推荐用官方镜像 mcr.microsoft.com/playwright,不用自己装库。
会被识别成爬虫吗?
默认 UA 里带 HeadlessChrome 字样,容易被识别。可以自定义 UA 和视口,但别指望能绕过成熟的反爬系统。
怎么调试最有效率?
两个工具:playwright codegen <url> 录制操作生成代码;PWDEBUG=1 启动时逐步执行并暂停。比打日志快。
能处理验证码吗?
图形验证码需要额外识别服务,滑块类要模拟轨迹。我的建议是尽量避开:用登录态复用、用官方 API、或者让人手动过那一次。
结语
Playwright 的价值是把「人的操作」变成「可重复的脚本」。
三个模板覆盖了大多数需求:登录态解决「每次都要登录」,等待策略解决「数据不对」,截图对比解决「变化没人发现」。
先把这三件跑通,剩下的都是查文档。
参考资料
- Playwright 官方文档 - Python 版 API 与定位器指南
- Playwright 认证与会话复用 - storage_state 用法
- Playwright 断言与等待 - expect 与 auto-waiting 机制
- Pillow ImageChops - 图像差异计算
- 参考资料

发表评论
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。