AI 浏览器代理 — Browser Agent
你现在运行 browser-agent 技能。目标:让 coding agent 真的能用浏览器 —— 登录、点击、填表、滚动、跨页抓取 —— 然后产出可回放、可审计的「行为 trace + 截图 + 结构化数据」三件套。
底层是开源 browser-use(MIT、PyPI 公开包,v0.12.x)+ Playwright Chromium。课程直接调用上游 PyPI 包。
这门课做什么(边界写在第一屏)
- ✅ 做:给你一个公开 URL(或一组目标站)→ agent 在本地启动 Chromium → 按 browser-use 的 controller-level actions(
navigate/click/input/scroll/extract/find_elements/search_page/screenshot/done…)一步步推进 → 每一步落out/run.json一条 + 一张 Chromium 截图 → 任务终态落out/extracted.json。 - ✅ agent-as-LLM:决定「下一步该做什么 action、参数怎么填」的"LLM"就是你(用户 session 里的 Claude Code / Cursor / Codex CLI / VS Code + Copilot 等)。
- ✅ 三件套真的能回放:
run.json单调时间戳 + 每条非空 dom_summary + 每条对应一张 ≥ 5 KB 的真实 Chromium PNG;extracted.json字段齐全、与目标站真实数据一致。课程详情页的 showcase 就是按这个 schema 渲染的。 - ❌ 不做:把一整个文档站抓成 Markdown 知识库(→
web-to-knowledge-base);深解析一份硬 PDF(→parse-docs);把一堆 Office 文件转 Markdown(→any-to-markdown);在浏览器外部直接打 HTTP API(不是本课,本课的语义是「真的用浏览器」,否则就是普通爬虫了)。 - 🔒 agent 自带模型即可:本课只用
browser_use的 controller /BrowserSession低层 API,不实例化任何接受llm参数的高层Agent类——决策由你 session 里的 coding agent 完成。
一句话定位:
web-to-knowledge-base= 静态文档站 → markdown 知识库(一次性、写入式);browser-agent= 任意网站 → agent 实际去做一件事(交互式、有 trace、可回放)。两门正交,按用户的真实任务选一门。
前置条件
- Python ≥ 3.11(browser-use 0.12.9 要求;
python3 --version检查) - 能访问公开 PyPI(一次安装两个包 + 一次
playwright install chromium≈ 170 MB,之后离线可跑) - 任一带 agent 能力的 IDE 做推理引擎:Claude Code / Cursor / Codex CLI / VS Code + Copilot Chat / Cline 等
- CPU 即可,没有 GPU;无需任何商业 LLM key(OpenAI / Anthropic / 任何第三方)、无需 Clawvard API key、无需 clone 任何私有仓库
- 公开演示目标站(QA 复现用):
https://books.toscrape.com(公共爬虫沙盒,无登录无 ToS 风险)、https://the-internet.herokuapp.com/login(公共登录 demo:tomsmith/SuperSecretPassword!)、https://httpbin.org/forms/post(公共表单 echo)
安装(一次到位)
强烈建议装在 venv 里,避免和系统 Python 打架(Ubuntu 24+ 的 PEP 668 会拒绝直接 pip install 到系统环境):
python3 -m venv .bavenv
source .bavenv/bin/activate
pip install --upgrade pip
# IMPORTANT: browser-use 0.12.x 不把 Playwright Python 模块作为依赖,必须显式装。
# 漏装 playwright 会让下一行的 `playwright install chromium` 直接 "No module named playwright"。
pip install browser-use playwright
playwright install chromium
python -c "import browser_use, playwright; print('browser-agent deps ok')"
browser-use 是 controller / BrowserSession 等核心 API 的来源,playwright 是它驱动 Chromium 的引擎,两者缺一不可。装完两个 PyPI 包 + 一份 Chromium 二进制就具备了本课需要的全部本地能力。
Step 0 — drop in the helper(自包含、无外部 URL 依赖)
本课的 record_step.py 单步记账小工具完整代码列在下面,把它写到当前目录的 record_step.py 即可——不需要 curl 任何外部 URL,也不需要 clone 任何仓库。文件同时被收录在 public/skills/browser-agent/examples/record_step.py,PR merge 之后才会从公开域名 served,所以这里给一份内嵌副本作为 first-class 安装方式。
helper 只负责单步记账——每步拍一张 Chromium 截图、追加一条 run.json 条目、任务结束时一并落盘 extracted.json。它不调任何 LLM 客户端、不实例化任何 browser-use 高层 Agent 类、不碰任何外部端点。
# record_step.py — drop this next to your runner; ~150 lines, zero deps beyond Playwright.
from __future__ import annotations
import json, re
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
OUT_DIR = Path("out")
SHOTS_DIR = OUT_DIR / "screenshots"
_run_log: list[dict[str, Any]] = []
_step_counter = 0
def _now_iso() -> str:
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
def _slugify(s: str) -> str:
s = re.sub(r"[^A-Za-z0-9]+", "-", s or "").strip("-").lower()
return s[:32] or "step"
async def record(page, *, action: str, target: str, dom_summary: str,
detail: dict | None = None, status: str = "ok",
take_screenshot: bool = True) -> dict:
"""Record one controller-level step. Bumps a counter, snaps a Chromium
screenshot to ./out/screenshots/NN-<slug>.png, appends a normalised
entry to the in-memory step log, returns the entry."""
global _step_counter
_step_counter += 1
SHOTS_DIR.mkdir(parents=True, exist_ok=True)
idx = f"{_step_counter:02d}"
slug = _slugify(target or action)
shot_name = f"{idx}-{slug}.png"
shot_path = SHOTS_DIR / shot_name
screenshot_rel: str | None = None
if take_screenshot:
try:
await page.screenshot(path=str(shot_path), full_page=False)
screenshot_rel = f"screenshots/{shot_name}"
except Exception as e:
dom_summary = f"{dom_summary} (screenshot failed: {e})"
entry: dict[str, Any] = {
"step": _step_counter,
"action": action,
"target": target,
"dom_summary": (dom_summary or "")[:200],
"screenshot": screenshot_rel,
"timestamp": _now_iso(),
"status": status,
}
if detail is not None:
entry["detail"] = detail
_run_log.append(entry)
return entry
def flush(*, extracted: dict | list | None = None,
task: str = "browser-agent task",
target_url: str | None = None) -> None:
"""Write ./out/run.json (always) and ./out/extracted.json (if given).
Call once at task end. Together with ./out/screenshots/ this is the
course's deliverable three-piece set."""
OUT_DIR.mkdir(parents=True, exist_ok=True)
run_doc = {
"task": task,
"target_url": target_url,
"started_at": _run_log[0]["timestamp"] if _run_log else _now_iso(),
"ended_at": _run_log[-1]["timestamp"] if _run_log else _now_iso(),
"total_steps": _step_counter,
"steps": _run_log,
}
(OUT_DIR / "run.json").write_text(
json.dumps(run_doc, indent=2, ensure_ascii=False), encoding="utf-8"
)
if extracted is not None:
(OUT_DIR / "extracted.json").write_text(
json.dumps(extracted, indent=2, ensure_ascii=False), encoding="utf-8"
)
def reset() -> None:
"""Reset internal counters (useful for tests / repeated runs)."""
global _step_counter
_step_counter = 0
_run_log.clear()
写完上面这段到
./record_step.py后跑一句python -c "import record_step; print('helper ok')"自检。
工作流程
1. 起一个 Chromium,拿到 page
from playwright.async_api import async_playwright
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
context = await browser.new_context(viewport={"width": 1280, "height": 800})
page = await context.new_page()
如果你想用 browser-use 的 BrowserSession(自带 selector_map / element index / DOM 摘要等好处):
from browser_use.browser import BrowserSession
session = BrowserSession()
await session.start()
page = await session.get_current_page()
两条路都可以;本课不强求路径,只强求每个 action 调用前后 record 一次。
2. 每一步走 controller-level action
browser-use 0.12.x 内置的 controller 暴露了一组小而稳定的动作:
| 动作 | 用途 |
|---|---|
navigate |
打开 URL(等价 page.goto(...)) |
click |
按 element index 或 selector 点击(旧名 click_element_by_index) |
input |
在 input/textarea 写文本(旧名 input_text) |
scroll |
上下滚动一段 |
extract |
从 DOM 抽取结构化数据(旧名 extract_structured_data) |
find_elements / search_page |
在 DOM 里找元素 / 全文搜索 |
screenshot |
拍一张截图(本课用 record_step.py 统一管,不直接调) |
send_keys |
发送按键(Enter / Tab / Shift+Tab / Esc / 方向键) |
dropdown_options / select_dropdown |
列出与选中 <select> 选项 |
wait |
等 N 秒(用于动画 / 异步加载) |
done |
任务结束信号(写最终 deliverable + 退出循环) |
agent 推理回路:每个 step 你(session 里的 agent)读完上一帧的 dom_summary + 截图后,自己决定下一个 action 是什么、参数怎么填,再调对应 controller 动作。不要用 browser-use 任何接受
llm参数的高层Agent类、不要导入任何商业 LLM 客户端 SDK、不要把任何 base URL 切到任何转发层。
3. 每一步记账(这是 deliverable 三件套的生成方式)
from record_step import record, flush
# 打开首页
await page.goto("https://books.toscrape.com/")
await record(page,
action="navigate",
target="https://books.toscrape.com/",
dom_summary="homepage loaded; sidebar lists categories")
# 切到 Mystery 分类
await page.goto("https://books.toscrape.com/catalogue/category/books/mystery_3/index.html")
await record(page,
action="navigate",
target="…/mystery_3/index.html",
dom_summary="Mystery category page loaded; product pods visible")
# 抽数据
cards = await page.locator("article.product_pod").all()
await record(page,
action="find_elements",
target="article.product_pod",
dom_summary=f"found {len(cards)} book pods on Mystery index")
# … 抽前 10 本 → 累加到 books_by_category["Mystery"] …
# 任务收尾
await record(page,
action="done",
target="out/extracted.json + out/run.json + out/screenshots/",
dom_summary="task complete; wrote deliverable three-piece set")
flush(extracted={
"categories": books_by_category,
"global_top10": top10,
"total_books_seen": total,
"scraped_at": "<ISO>",
},
task="popularTask 2 — books.toscrape.com cross-category Top-N",
target_url="https://books.toscrape.com/")
4. 自检三件套(这是 SOP 的「绿灯」标准)
out/run.json.steps长度 ≥ 8,每条带:step(单调递增)/action/target/dom_summary(非空、≤ 200 字符)/screenshot(指向 screenshots/NN-*.png)/timestamp(ISO 单调递增)/status。out/screenshots/至少 8 张 PNG,每张 ≥ 5 KB(一张完全白屏的 1280×800 PNG 也有 ~3 KB;< 5 KB 通常意味着 navigation 没等好,截早了)。out/extracted.json字段齐全、与目标站真实数据一致(books.toscrape.com 的 30 本书必须各自有 title/price_gbp/rating_1to5/in_stock/source_url/category)。- 任意一项不达标 = 这一轮跑废了,不要靠手改 JSON 凑齐;回去看是哪一步 dom_summary 漏写、截图截早、还是 selector 没命中。
三类典型场景(覆盖 popularTasks)
A. 登录后页面的结构化抓取(popularTask 1)
公共 demo:https://the-internet.herokuapp.com/login,凭据 tomsmith / SuperSecretPassword!。
- 步骤序:
navigate(login)→input(username)→input(password)→click(login_btn)→navigate(secure_area)(自动跳转)→extract(success_message, secure_url, logout_text)→done。 - 关键铁律:用户必须自己提供凭据——SOP 默认只演示公共 demo 凭据;遇到真实登录站时由用户在 prompt 中显式贴密码,agent 不持久化、不向外泄露、不写进 run.json 的 dom_summary。
B. 跨页比价 / Top-N 汇总(popularTask 2,showcase 来源)
公共目标:https://books.toscrape.com(公共爬虫沙盒,每页 20 本,无登录)。
- 步骤序:进首页 → 找 sidebar 三类(Mystery / Travel / Classics)→ 各抓前 10 本(title / price_gbp / rating_1to5 / in_stock / source_url / category)→ 合并 30 本按评分降序、价格升序 ranking → 取前 10 写 global_top10 →
done。 - 课程 showcase 里那份
run.json+ 13 张截图 + 30 条extracted.json就是按这条流程真跑出来的;按同一 SOP 在干净环境中应能复现到同等数量级。
C. 表单填写提交 + 长流程监控(popularTask 3)
公共目标:https://httpbin.org/forms/post(表单)+ https://httpbin.org/delay/2(可循环监控)。
- 步骤序:进表单页 → 按字段
input+select_dropdown+click(submit)→ 从结果页 JSON 抽form.*字段写form_submission.json→ 进/delay/2循环 5 次(导航式)→ 每次 record + 取响应耗时 → 触发停止条件(5 次或 60 s)→ 把 form + 监控合并到extracted.json→done。 - 关键铁律:长流程监控用浏览器导航(保证 user-agent / cookie / DOM 真实),不要偷偷 fetch 直接打接口——那是
web-to-knowledge-base都不肯做的事。
铁律(红线)
- 零商业 LLM key、零第三方推理转发。SOP、参考脚本、popularTasks 的默认执行路径不调任何 LLM 推理 API;不允许导入任何商业 LLM 客户端 SDK(无论 OpenAI 系、Anthropic 系还是 LangChain 兼容包装类);不允许把任何 base URL 切到任何 OpenAI-compatible 中继端点;不允许实例化 browser-use 任何接受
llm参数的高层Agent类——本课只用browser_use的 controller /BrowserSession低层 API,由 session 里的 agent 当推理引擎。这条 QA 会以字面值 grep 检查。 - 不接 Clawvard 一方 service SDK:本课
commercialApi=false、runsLocally=true;不引入 Clawvard 的 npm SDK 包、不调用 Clawvard service 命名空间,service 化(例如未来一行 SDK 调用版本)是后续产品化方向,不在本课范围。 - deliverable 三件套必须真实:
run.json≥ 8 步、每步含非空 dom_summary 与对应截图;screenshots/每张 ≥ 5 KB;extracted.json字段齐全、可机器读。任何一项是 mock / placeholder / 灰盒占位 = 任务失败。 - 目标站尊重:每个 action 之间 ≥ 300 ms 间隔;不并发对单站超过 1 个 page;遵守目标站 robots.txt 与公开 ToS;本课公开 popularTasks 全部用公共 demo 站,不要替用户去碰生产业务站。
- 不替用户管凭据:遇到登录站,密码由用户在每次 prompt 里显式贴入;agent 不持久化、不写进 dom_summary、不上传到任何第三方。
产出物
out/
├── run.json # 1 份步骤日志(task + steps[])
├── extracted.json # 1 份结构化结果
└── screenshots/
├── 01-<slug>.png
├── 02-<slug>.png
└── …
三个文件一起就是这门课的唯一deliverable。课程详情页的 showcase 直接渲染同名三件套——QA 用出来的更好版本可以直接覆盖回 PR。
学习完成后
告诉用户:
我已经学会了 browser-agent。给我一个网站 + 一段自然语言任务(登录抓取 / 跨页比价 / 表单提交 / 长流程监控),我在本地起 Chromium 用
browser-use的 controller actions 一步步推进,落盘out/run.json+out/screenshots/*.png+out/extracted.json三件套:可回放、可审计、可机器读。全程本地、零商业 LLM key、零第三方推理转发、零 Clawvard 后端调用。