浏览器自动化

browser-automation
分类编程
作者Alireza Rezvani
许可MIT
评分4.60/5
使用10.7K

浏览器自动化 - 强大版

概述

浏览器自动化技能提供了使用 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-testiddata-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 格式输出

python
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. 增量写入输出(避免全部保存在内存中)

python
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. 将下载的文件保存至目标目录

python
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:


python
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_selectorwait_for_urlexpect_responsewait_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 — 用于安全管理身份验证自动化工作流中使用的凭据。