浏览器自动化
浏览器自动化 - 强大版
概述
浏览器自动化技能提供了使用 Playwright 构建生产级 Web 自动化工作流的全面工具和知识。该技能涵盖了数据提取、表单填写、截屏、会话管理以及反检测模式,旨在实现大规模且可靠的浏览器自动化。
适用场景:
- 从网站爬取结构化数据(表格、列表、搜索结果)
- 自动化多步骤浏览器工作流(登录、填写表单、下载文件)
- 截取网页截图或生成 PDF
- 从单页应用 (SPA) 和重 JavaScript 网站提取数据
- 构建可重复执行的基于浏览器的数据流水线
不适用场景:
- 编写浏览器测试或端到端 (E2E) 测试套件 —— 请使用 playwright-pro
- 测试 API 接口 —— 请使用 api-test-suite-builder
- 负载测试或性能基准测试 —— 请使用 performance-profiler
为什么选择 Playwright 而非 Selenium 或 Puppeteer:
- 内置自动等待 —— 大多数操作无需显式调用
sleep()或waitForElement()
- 单一 API 支持多浏览器 —— 无需配置更改即可支持 Chromium, Firefox, WebKit
- 网络拦截 —— 原生支持拦截广告、模拟响应和捕获 API 调用
- 浏览器上下文 (Browser Contexts) —— 隔离会话,无需启动多个浏览器实例
- 代码生成 (Codegen) ——
playwright codegen可记录操作并自动生成脚本
- 异步优先 —— 支持 Python async/await 以实现高吞吐量爬取
核心能力
1. Web 爬取模式
选择器优先级(从最可靠到最不可靠):
1. data-testid、data-id 或自定义数据属性 —— 在页面重新设计时保持稳定
2. #id 选择器 —— 唯一,但在不同部署版本间可能会更改
3. 语义化选择器:article, nav, main, section —— 对 CSS 更改具有鲁棒性
4. 基于类名:.product-card, .price —— 如果类名是动态生成的(如 CSS Modules)则较脆弱
5. 位置选择器:nth-child(), nth-of-type() —— 最后手段,布局更改时会失效
仅在 CSS 无法表达关系(如祖先节点遍历、基于文本的选择)时使用 XPath。
分页策略: 下一页按钮、基于 URL (?page=N)、无限滚动、加载更多按钮。完整的分页处理和滚动模式请参阅 data_extraction_recipes.md。
2. 表单填写与多步工作流
将多步骤表单分解为每个步骤对应的独立函数。每个函数负责填写字段、点击“下一步/继续”,并等待下一步加载(通过 URL 变化或 DOM 元素判断)。
关键模式:登录流程、多页表单、文件上传(包括拖拽区域)、原生及自定义下拉菜单处理。关于 fill(), select_option(), set_input_files() 和 expect_file_chooser() 的完整 API 参考,请参阅 playwright_browser_api.md。
3. 截图与 PDF 捕获
- 全页截图:
await page.screenshot(path="full.png", full_page=True)
- 元素截图:
await page.locator("div.chart").screenshot(path="chart.png")
- PDF (仅限 Chromium):
await page.pdf(path="out.pdf", format="A4", print_background=True)
- 视觉回归: 在已知状态下截屏,并将基准图存储在版本控制中
{page}_{viewport}_{state}.png
完整截图/PDF 选项请参阅 playwright_browser_api.md。
4. 结构化数据提取
核心提取模式:
- 表格转 JSON — 将
<thead>表头和<tbody>行提取为字典
- 列表转数组 — 使用字段选择器映射(支持
::attr()提取属性)来映射重复的卡片元素
- 嵌套/线程数据 — 对带有回复的评论、分类树进行递归提取
完整的提取函数、价格解析、数据清洗工具和输出格式助手(JSON, CSV, JSONL)请参阅 data_extraction_recipes.md。
5. Cookie 与会话管理
- 保存/恢复 Cookie:
context.cookies()和context.add_cookies()
- 完整存储状态(Cookie + localStorage):使用
context.storage_state(path="state.json")保存,使用browser.new_context(storage_state="state.json")恢复
最佳实践: 登录后保存状态,并在后续抓取会话中复用。在启动长时间任务前检查会话有效性 —— 向受保护页面发送一个轻量级请求,确认未被重定向至登录页。Cookie 和存储状态 API 详情请参阅 playwright_browser_api.md。
6. 反检测模式
现代网站通过多种维度检测自动化。请按优先级应用以下方案:
1. 移除 WebDriver 标志 — 通过初始化脚本移除 navigator.webdriver = true(至关重要)
2. 自定义 User Agent — 轮换使用真实的浏览器 UA;绝不要使用默认的无头浏览器 UA
3. 真实的视口尺寸 — 设置 1920x1080 或类似的真实维度(默认的 800x600 是明显的特征)
4. 请求节流 — 在操作之间添加 random.uniform() 随机延迟
5. 代理支持 — 配置浏览器级或上下文级代理
完整的隐身技术栈(navigator 属性加固、WebGL/canvas 指纹规避、行为模拟(鼠标移动、打字速度、滚动模式)、代理轮换策略及检测自测 URL)请参阅 anti_detection_patterns.md。
7. 动态内容处理
- SPA 渲染: 等待内容选择器 (
wait_for_selector),而非页面加载事件
- AJAX/Fetch 等待: 使用
page.expect_response("**/api/data*")拦截并等待特定的 API 调用
- Shadow DOM: Playwright 使用
>>操作符穿透开放的 Shadow DOM:page.locator("custom-element >> .inner-class")
- 懒加载图片: 使用
scroll_into_view_if_needed()将元素滚动到视口内以触发加载
等待策略、网络拦截和 Shadow DOM 详情请参阅 playwright_browser_api.md。
8. 错误处理与重试逻辑
- 指数退避重试: 将页面交互封装在带有指数退避(如 1s, 2s, 4s)的重试逻辑中
- 备用选择器: 发生
TimeoutError时,在失败前尝试备用选择器
- 错误状态截图: 在意外失败时捕获
page.screenshot(path="error-state.png")以便调试
- 频率限制检测: 检查 HTTP 429 响应并遵循
Retry-After响应头
完整的指数退避实现和频率限制类请参阅 anti_detection_patterns.md。
工作流
工作流 1:单页数据提取
场景: 从单个页面提取产品数据(Java...
脚本渲染内容。
步骤:
1. 开发期间以有头模式启动浏览器 (headless=False),生产环境切换至无头模式
2. 导航至 URL 并等待内容选择器加载
3. 使用 query_selector_all 结合字段映射提取数据
4. 验证提取的数据(检查空值、预期类型)
5. 以 JSON 格式输出
async def extract_single_page(url, selectors):
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
context = await browser.new_context(
viewport={"width": 1920, "height": 1080},
user_agent="Mozilla/5.0 ..."
)
page = await context.new_page()
await page.goto(url, wait_until="networkidle")
data = await extract_listings(page, selectors["container"], selectors["fields"])
await browser.close()
return data工作流 2:带分页的多页抓取
场景: 抓取 50 多个页面的搜索结果。
步骤:
1. 使用反检测设置启动浏览器
2. 导航至第一页
3. 提取当前页数据
4. 检查“下一页”按钮是否存在且可用
5. 点击下一页,等待新内容加载(而非仅等待页面跳转)
6. 重复直至没有下一页或达到最大页数
7. 根据唯一键对结果进行去重
8. 增量写入输出(避免全部保存在内存中)
async def scrape_paginated(base_url, selectors, max_pages=100):
all_data = []
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await (await browser.new_context()).new_page()
await page.goto(base_url)
for page_num in range(max_pages):
items = await extract_listings(page, selectors["container"], selectors["fields"])
all_data.extend(items)
next_btn = page.locator(selectors["next_button"])
if await next_btn.count() == 0 or await next_btn.is_disabled():
break
await next_btn.click()
await page.wait_for_selector(selectors["container"])
await human_delay(800, 2000)
await browser.close()
return all_data
工作流 3:身份验证自动化工作流
场景: 登录门户网站,填写多步表单,下载报告。
步骤:
1. 检查是否存在现有的会话状态文件
2. 若无会话,执行登录并保存状态
3. 使用保存的会话导航至目标页面
4. 使用提供的数据填写多步表单
5. 等待下载触发
6. 将下载的文件保存至目标目录
async def authenticated_workflow(credentials, form_data, download_dir):
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
state_file = "session_state.json"
# 恢复或创建会话
if os.path.exists(state_file):
context = await browser.new_context(storage_state=state_file)
else:
context = await browser.new_context()
page = await context.new_page()
await login(page, credentials["url"], credentials["user"], credentials["pass"])
await context.storage_state(path=state_file)
page = await context.new_page()
await page.goto(form_data["target_url"])
# 填写表单步骤
for step_fn in [fill_step_1, fill_step_2]:
await step_fn(page, form_data)
# 处理下载
async with page.expect_download() as dl_info:
await page.click("button:has-text('Download Report')")
download = await dl_info.value
await download.save_as(os.path.join(download_dir, download.suggested_filename))
await browser.close()
工具参考
| 脚本 | 用途 | 关键标志 (Flags) | 输出 |
|--------|---------|-----------|--------|
| scraping_toolkit.py | 生成 Playwright 爬虫脚本骨架 | --url, --selectors, --paginate, --output | Python 脚本或 JSON 配置 |
| form_automation_builder.py | 根据字段规范生成表单填充自动化脚本 | --fields, --url, --output | Python 自动化脚本 |
| anti_detection_checker.py | 审计 Playwright 脚本的检测风险点 | --file, --verbose | 包含评分的风险报告 |
所有脚本仅依赖标准库。运行 python3 <script> --help 查看完整用法。
反模式 (Anti-Patterns)
硬编码等待
错误: 在每个操作前使用await page.wait_for_timeout(5000)。
正确: 使用 wait_for_selector、wait_for_url、expect_response 或 wait_for_load_state。硬编码等待不稳定且速度慢。
缺乏错误恢复
错误: 线性脚本,一旦遇到第一个错误就崩溃。 正确: 将每个页面交互包裹在 try/except 中。在错误状态下截屏。实现带有指数退避机制的重试。忽略 robots.txt
错误: 在不检查 robots.txt 指令的情况下进行爬取。 正确: 爬取前获取并解析 robots.txt。遵守Crawl-delay。跳过被禁止的路径。大规模运行时在 User-Agent 中添加机器人名称。
在脚本中存储凭据
错误: 在 Python 文件中硬编码用户名和密码。 正确: 使用环境变量、.env 文件(加入 gitignore)或密钥管理器。通过 CLI 参数传递凭据。
无频率限制
错误: 以每秒 100 个请求的频率轰击网站。 正确: 在请求之间添加随机延迟(礼貌爬取建议 1-3 秒)。监控 429 响应。实现指数退避。选择器脆弱
错误: 依赖自动生成的类名(如.css-1a2b3c)或深层嵌套(如 div > div > div > span:nth-child(3))。
正确: 使用 data 属性、语义化 HTML 或基于文本的定位器。先在浏览器开发者工具中测试选择器。
未清理浏览器实例
错误: 启动浏览器后不关闭,导致资源泄漏。 正确: 始终使用try/finally 或异步上下文管理器以确保调用 browser.close()。
在生产环境中运行有头模式
错误: 在生产环境/CI 中使用headless=False。
正确: 开发时使用有头模式进行调试,部署时使用 headless=True。使用环境变量进行切换:headless = os.environ.get("HEADLESS", "true") == "true"。
交叉引用
- playwright-pro — 浏览器测试技能。用于 E2E 测试、测试断言和测试夹具。浏览器自动化用于数据提取和工作流自动化,而非测试。
- api-test-suite-builder — 当网站提供公共 API 时,直接调用 API 而非爬取渲染页面。速度更快,更可靠,且更难被检测。
- performance-profiler — 如果自动化脚本运行缓慢,在引入并发之前先分析性能瓶颈。
- env-secrets-manager — 用于安全管理身份验证自动化工作流中使用的凭据。