网页自动化最容易被三件事拖住:chromedriver 版本必须和本机 Chrome 严丝合缝,浏览器一自动升级,脚本第二天就抛 SessionNotCreatedException;页面还没渲染完就 find_element,直接报 NoSuchElementException,只能满屏写 time.sleep(3);再换到 Firefox、Edge,驱动环境又得重来一遍。
Playwright 由微软团队维护,Python 版 API 设计得比较干净,刚好把这三点一起处理掉:自带浏览器内核(一条命令下载,版本天然匹配)、操作自带自动等待(不必手写 sleep)、一套 API 通吃 Chromium / Firefox / WebKit。
一、环境准备:两条命令
Playwright 支持 Python 3.8 及以上,同样建议用虚拟环境隔离依赖:
- python -m venv .venv
- source .venv/bin/activate # Windows 用 .venv\Scripts\activate
- # 第一步:装 Python 包
- pip install playwright
- # 第二步:下载浏览器内核(约 100~300MB,只需执行一次)
- playwright install chromium
复制代码
第二步是新手最容易卡住的地方:它下载的是 Playwright 自带的浏览器,不是你桌面上的 Chrome,所以不存在版本对不上的问题。国内下载慢可以走镜像加速:
- # 使用国内镜像加速下载
- PLAYWRIGHT_DOWNLOAD_HOST=https://cdn.npmmirror.com/binaries/playwright playwright install chromium
复制代码
装完先跑一段最小验证代码:
- from playwright.sync_api import sync_playwright
- with sync_playwright() as p:
- browser = p.chromium.launch(headless=True)
- page = browser.new_page()
- page.goto("https://example.com")
- print("页面标题:", page.title())
- browser.close()
复制代码
能打印出 Example Domain,就说明环境和浏览器内核都没问题。
二、最小可运行示例:打开页面并截图
Playwright 提供同步和异步两套 API,日常脚本用同步版最省心。下面的代码打开 python.org 并截取整页长图:
- from playwright.sync_api import sync_playwright
- with sync_playwright() as p:
- # headless=True 表示无界面运行;调试时改成 False 能看到真实浏览器
- browser = p.chromium.launch(headless=True)
- page = browser.new_page(viewport={"width": 1280, "height": 800})
- page.goto("https://www.python.org/")
- # full_page=True 会截取整页长图,不只是可视区域
- page.screenshot(path="python_org.png", full_page=True)
- browser.close()
复制代码
对比 Selenium 同样的功能,这里少了驱动配置、少了显式等待,代码块从十几行压到十行以内。
三、三个核心概念:Locator、自动等待、上下文
1. Locator:定位元素的新写法
Playwright 推荐用 locator() 拿到元素句柄,后续操作都基于它,下面几种写法等价:
- # 下面几种写法等价,选你顺手的
- page.locator("button#submit").click()
- page.locator("text=登录").click()
- page.get_by_role("button", name="登录").click() # 按语义角色,最稳
- page.get_by_placeholder("请输入用户名").fill("admin")
- page.get_by_text("下一步").click()
复制代码
Locator 是惰性的:写 page.locator(...) 时并不会真的去查 DOM,只有执行 .click()、.fill() 时才查找,并且会自动等到元素可见、可点击、不被遮挡。这就是“自动等待”的实现基础。
2. 自动等待:和 time.sleep 说再见
官方断言工具 expect 会轮询等待,而不是一次性判断,比 assert page.title() == ... 这类写法可靠得多:
- from playwright.sync_api import expect
- # 断言元素可见(默认超时 5 秒,期间反复重试)
- expect(page.get_by_text("提交成功")).to_be_visible(timeout=10000)
- # 断言 URL 变化
- expect(page).to_have_url("https://example.com/dashboard")
复制代码
需要改全局超时可以直接设置默认值:
- page.set_default_timeout(15000) # 所有操作默认最多等 15 秒
复制代码
3. 上下文:多开互不干扰的会话
context 相当于一个独立的浏览器会话,拥有独立的 Cookie 和缓存,比开多个 browser 省资源,也可以用来模拟移动端和中文环境:
- with sync_playwright() as p:
- browser = p.chromium.launch()
- # 模拟移动端 + 中文环境
- ctx = browser.new_context(
- viewport={"width": 390, "height": 844},
- user_agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15",
- locale="zh-CN",
- )
- page = ctx.new_page()
- page.goto("https://example.com")
- ctx.close() # 关上下文,Cookie 一起清掉
- browser.close()
复制代码
四、进阶操作:表单、上传下载与请求拦截
1. 填表单与键盘鼠标
- page.get_by_label("用户名").fill("zhangsan")
- page.get_by_label("密码").fill("secret123")
- page.get_by_label("记住我").check()
- page.get_by_role("combobox").select_option("beijing")
- page.keyboard.press("Enter")
复制代码
2. 文件上传与下载
上传直接给本地路径,不用再操作系统文件选择框;下载必须用 expect_download 包住触发动作,否则拿不到下载对象:
- # 上传:直接给本地路径,不用再找系统文件框
- page.set_input_files("input[type=file]", "report.xlsx")
- # 下载:必须用 expect_download 包住触发动作
- with page.expect_download() as dl:
- page.get_by_text("导出报表").click()
- download = dl.value
- download.save_as("data/export.xlsx")
复制代码
3. 拦截请求:屏蔽图片提速
抓数据时图片、字体往往没用,直接拦掉能明显加速:
- def block_images(route):
- if route.request.resource_type in ("image", "font", "media"):
- route.abort()
- else:
- route.continue_()
- page.route("**/*", block_images)
- page.goto("https://news.example.com")
复制代码
五、实战:带登录态抓取分页列表
下面这段完整代码把登录、断言等待、分页抓取、保存登录态串在一起,下次运行可以直接复用 auth.json,省掉重复登录:
- from playwright.sync_api import sync_playwright, expect
- BASE = "https://example.com"
- def main():
- with sync_playwright() as p:
- browser = p.chromium.launch(headless=True)
- ctx = browser.new_context(locale="zh-CN")
- page = ctx.new_page()
- page.set_default_timeout(15000)
- # 1. 登录
- page.goto(f"{BASE}/login")
- page.get_by_label("账号").fill("your_account")
- page.get_by_label("密码").fill("your_password")
- page.get_by_role("button", name="登录").click()
- # 等到跳转完成,不要用 sleep
- expect(page).to_have_url(f"{BASE}/dashboard")
- # 2. 抓多页列表
- rows = []
- for page_no in range(1, 4):
- page.goto(f"{BASE}/items?page={page_no}")
- expect(page.locator(".item-row").first).to_be_visible()
- items = page.locator(".item-row").all()
- for it in items:
- rows.append({
- "title": it.locator(".title").inner_text(),
- "price": it.locator(".price").inner_text(),
- })
- print(f"第 {page_no} 页抓到 {len(items)} 条")
- # 3. 保存登录态,下次直接复用,省掉重复登录
- ctx.storage_state(path="auth.json")
- print("共抓取", len(rows), "条")
- browser.close()
- if __name__ == "__main__":
- main()
复制代码
下次运行时直接加载已保存的登录态:
- 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 里。跨框架要先切进去再操作:
- frame = page.frame_locator("iframe#login-frame")
- frame.get_by_role("button", name="登录").click()
复制代码
3)被网站识别为自动化
默认 headless 会带自动化特征,可以加启动参数缓解:
- browser = p.chromium.launch(
- headless=True,
- args=["--disable-blink-features=AutomationControlled"],
- )
复制代码
4)抓到的中文乱码
确认 new_context(locale="zh-CN"),并且写入文件时指定编码 encoding="utf-8"。Windows 上尤其容易踩这个坑。
5)调试时看不到过程
打开录制,事后回看每一步:
- ctx.tracing.start(screenshots=True, snapshots=True)
- # ... 你的操作 ...
- 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。 |