Selenium 装驱动太麻烦?Playwright 入门指南:Python 网页自动化新标准
一、开头痛点:驱动、等待、玄学报错
做过网页自动化的同学,大概率被这三件事折磨过:
- 装驱动 :
chromedriver的版本必须和本机 Chrome 严丝合缝,浏览器一自动升级,脚本第二天就SessionNotCreatedException。 - 等元素 :页面还没渲染完就
find_element,报NoSuchElementException;于是满屏time.sleep(3),慢且不稳。 - 换浏览器:Selenium 要分别配 Chrome、Firefox、Edge 的驱动,环境一换全得重来。
Playwright 把这三点一次性解决了:自带浏览器 (一条命令下载,版本永远匹配)、操作自带自动等待 (不用手写 sleep)、一套 API 通吃 Chromium / Firefox / WebKit。它由微软团队维护,Python 版 API 设计得很干净,写起来比 Selenium 顺手不少。
二、环境准备:两条命令
Playwright 支持 Python 3.8+。同样建议用虚拟环境隔离:
bash
python -m venv .venv
source .venv/bin/activate # Windows 用 .venv\Scripts\activate
# 第一步:装 Python 包
pip install playwright
# 第二步:下载浏览器内核(约 100~300MB,只需执行一次)
playwright install chromium
第二步是很多新手卡住的地方------它下载的是 Playwright 自带的浏览器,不是你桌面上那个 Chrome,所以不存在版本对不上的问题。国内下载慢可以走镜像:
bash
# 使用国内镜像加速下载
PLAYWRIGHT_DOWNLOAD_HOST=https://cdn.npmmirror.com/binaries/playwright playwright install chromium
验证安装:
python
# check_env.py
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
# shot.py
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、自动等待、上下文
4.1 Locator:定位元素的新写法
Playwright 推荐用 locator() 拿到元素句柄,所有操作都基于它:
python
# 下面几种写法等价,选你顺手的
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() 时才查找,并且会自动等到元素可见、可点击、不被遮挡。这就是「自动等待」。
4.2 自动等待:和 time.sleep 说再见
python
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")
expect 是 Playwright 官方的断言工具,比 assert page.title() == ... 更可靠,因为它会轮询等待而不是一次性判断。需要改全局超时可以这样:
python
page.set_default_timeout(15000) # 所有操作默认最多等 15 秒
4.3 上下文:多开互不干扰的会话
context 相当于一个独立的浏览器会话(独立的 Cookie、缓存),比开多个 browser 省资源:
python
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()
五、进阶用法:表单、上传、拦截
5.1 填表单与键盘鼠标
python
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")
5.2 文件上传与下载
python
# 上传:直接给本地路径,不用再找系统文件框
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")
5.3 拦截请求:屏蔽图片提速
抓数据时图片、字体往往没用,直接拦掉能明显加速:
python
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")
六、实战场景:自动登录并抓取列表(完整代码)
下面这个例子把上面几点串起来,带登录态抓取分页数据,并保存截图:
python
# crawl_demo.py
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()
下次运行时直接加载已保存的登录态:
python
ctx = browser.new_context(storage_state="auth.json")
七、和 Selenium 怎么选
| 维度 | Playwright | Selenium |
|---|---|---|
| 浏览器驱动 | 自带,一条命令安装 | 需手动下载、版本必须匹配 |
| 等待机制 | 操作自带自动等待 | 需自己写 WebDriverWait 或 sleep |
| 多浏览器 | 一套 API 支持 Chromium/Firefox/WebKit | 每个浏览器各自适配 |
| 录制脚本 | playwright codegen 官方支持 |
依赖第三方插件 |
| 网络拦截 | 原生 page.route |
需借助代理 |
| 生态与资料 | 较新,中文资料相对少 | 十年积累,资料最多、岗位要求常见 |
结论:新项目优先 Playwright ,效率和稳定性都更好;老项目或需要兼容既有 Selenium 资产的继续用 Selenium,两者并不互斥。
八、常见坑与解决办法
1)playwright install 卡住或超时 换国内镜像,见第二节。公司网络有代理时设置 HTTPS_PROXY 环境变量再执行。
2)locator.click() 超时 先确认是不是在 iframe 里。跨框架要先切进去:
python
frame = page.frame_locator("iframe#login-frame")
frame.get_by_role("button", name="登录").click()
3)被网站识别为自动化 默认 headless 会带自动化特征,可以加启动参数缓解:
python
browser = p.chromium.launch(
headless=True,
args=["--disable-blink-features=AutomationControlled"],
)
4)抓到的中文乱码 确认 new_context(locale="zh-CN"),并且写入文件时指定编码 encoding="utf-8",Windows 上尤其容易踩。
5)调试时看不到过程 打开录制,事后回看每一步:
python
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。