{/* This page is auto-generated from the skill's SKILL.md by website/scripts/generate-skill-docs.py. Edit the source SKILL.md, not this page. */}

Har Derived Api Client

把站点的 XHR 录成 HAR,推导 HTTP 客户端。

Skill 元数据

来源可选——使用 hermes skills install official/web-development/har-derived-api-client 安装
路径optional-skills/web-development/har-derived-api-client
版本0.1.0
作者Hermes Agent
许可证MIT
平台linux, macos, windows
标签Browser, HAR, API, Reverse-Engineering, Playwright

参考:完整 SKILL.md

INFO

以下是 Hermes 在触发此 skill 时加载的完整 skill 定义。这是 skill 激活时 agent 所看到的指令内容。

HAR 推导 API 客户端

用一个真实浏览器驱动网站一次,同时把它的网络流量录到 HAR 文件里,然后把这个 HAR 提炼成该站点私有的 JSON API,这样你就能用普通 HTTP 直接调用它——比每次请求都用浏览器控制页面便宜得多、快得多。灵感:Jared Longster 的技巧,由 Dax(thdxr)推广。它捕获并重放;它不绕过认证、不解 CAPTCHA、也不破机器人检测——如果站点需要登录会话,你把它的请求头/cookie 带过去,而不是伪造它们。

脚本是标准库 + Playwright:捕获需要 Playwright,推导纯标准库,重放只需 requests/httpx(或 curl)。

覆盖每一条 Hermes 浏览器通路:默认本地 browser_navigate 后端,加上云/远程后端(Browserbase、Browser-Use、Firecrawl)和任何 /browser connect CDP 端点。有两个捕获脚本——一个针对你启动的浏览器,一个针对你通过 CDP 附加的浏览器——因为 HAR 录制在两种情况下工作方式不同(见运行方式)。

使用时机

  • "给 <网站> 做个 CLI/客户端"——推导它的 API,而不是脚本化点击。
  • "这个站点没有公开 API,但页面明显在取 JSON。"
  • 你即将为同一个查询反复循环 browser_navigate——停下,一次性推导出端点。
  • 逆向一个自动补全、搜索、feed 或结账 XHR。
  • 你在云后端(Browserbase / Browser-Use / Firecrawl)或通过 /browser connect 捕获了一个会话,想要这个 API 而不续租浏览器。

前提条件

  • Playwright + 一个浏览器二进制(仅捕获步骤):
    • pip install playwright 然后 playwright install chromium --no-shell
    • (如果系统 Playwright 已在 ~/.cache/ms-playwright 下有浏览器,复用它。)
  • 重放步骤用 requests 或 httpx(标准库 urllib 也行)。
  • 无 API key。客户端需要的任何 key/token 都是 HAR 捕获到的那些。
  • 对于 CDP 通路(har_capture_cdp.py):一个可达的 CDP 端点。在 Hermes 上,运行 /browser connect 打印当前端点,或读 config 里的 BROWSER_CDP_URL / browser.cdp_url。云后端把它暴露为 cdpUrl/connectUrl。

运行方式

脚本在本 skill 的 scripts/ 下,通过 terminal 工具调用。按通路选捕获器——这是最容易踩坑的地方:

浏览器通路Hermes 如何到达它捕获器
本地 browser_navigate(默认,agent-browser/Playwright)本地启动har_capture.py
Camofox(设了 CAMOFOX_URL)本地 REST/CDP若暴露 CDP 用 har_capture_cdp.py,否则自己驱动
Browserbase / Browser-Use / Firecrawl(云)CDP(cdpUrl)har_capture_cdp.py
/browser connect <url> / BROWSER_CDP_URLCDPhar_capture_cdp.py

经验法则:如果 Hermes 启动了浏览器,用 har_capture.py;如果它通过 CDP 连接到一个浏览器,用 har_capture_cdp.py。 har_capture.py 用 Playwright 的 record_har_path,它只在本地拥有的 context 上工作。har_capture_cdp.py 用 connect_over_cdp() 附加,并从 page.on("request"/"response") 事件组装 HAR,因为 record_har_path 在已连接的浏览器上不可用。

然后,两条路径都一样:

  • har_to_client.py——把 HAR 过滤到 XHR/fetch/JSON,按端点分组,打印参数、请求头、请求体和重放提示(User-Agent / cookie / auth)。

路径按本 skill 目录解析。标准闭环:

# 1a. 捕获,本地浏览器(Hermes 启动的)
python3 scripts/har_capture.py "https://SITE/" out.har \
  --action "fill:input[name=search]:my query" --action "sleep:3" --wait 2

# 1b. 捕获,CDP 浏览器(云后端或 /browser connect)
#     从 /browser connect 或 BROWSER_CDP_URL 拿端点
python3 scripts/har_capture_cdp.py "ws://HOST/devtools/browser/..." out.har \
  --goto "https://SITE/" --action "fill:input[name=search]:my query" \
  --action "sleep:3" --wait 2

# 2. 推导——从 HAR 里读出端点
python3 scripts/har_to_client.py out.har --host SITE --max-body 400

# 3. 重放——按打印出的端点写一个小客户端(见流程)

快速参考

har_capture.py <url> <out.har> [--wait S] [--headed] [--action SPEC ...]
  action SPEC:  fill:SELECTOR:TEXT | press:SELECTOR:KEY | click:SELECTOR
                goto:URL | sleep:SECONDS      (页面加载后按顺序运行)
  当 Hermes 启动了浏览器时使用(本地 browser_navigate 默认)

har_capture_cdp.py <cdp_url> <out.har> [--goto URL] [--wait S] [--action SPEC ...]
  相同的 action SPEC;附加到一个已存在的 CDP 浏览器,不关闭它
  用于云后端(Browserbase/Browser-Use/Firecrawl)和 /browser connect

har_to_client.py <in.har> [--host SUBSTR] [--include-static] [--max-body N]
  默认:只保留 XHR/fetch/JSON;--host 收窄到一个域名
  每个端点打印:查询参数、非平凡请求头、请求体样本、
                 响应 status/content-type + 响应体样本
  打印 "### Replay hints":浏览器 User-Agent、cookie/auth 是否存在

流程

  1. 按通路选捕获器(见运行方式表)。本地启动 → har_capture.py;通过 CDP 到达 → har_capture_cdp.py。在 Hermes 上,当云/远程后端激活时,/browser connect 会告诉你 CDP 端点。
  2. 找到交互。 用 browser_navigate(或 --headed 捕获)打开站点,看要往哪个选择器输入/点击,并在 devtools/network 里确认会发出一个 JSON XHR。
  3. 通过 terminal 工具捕获 HAR。 安排 --action 顺序到达该请求:fill 输入框,然后 sleep 足够长让防抖的 XHR 发出,并始终在末尾留 --wait,让迟到的响应 flush。两个捕获器都内嵌响应体,所以推导出来的客户端看到真实的载荷形状。
  4. 用 har_to_client.py --host <domain> 推导。 读出:方法、URL/路径模板(数字/UUID 段折叠为 {id})、查询参数、请求体 JSON、以及 ### Replay hints 块。
  5. 写客户端。 精确重现请求——相同方法、路径、查询参数、请求体。发送站点实际需要的请求头:至少从重放提示里复制 User-Agent。如果提示报告了 cookie 或 auth/token 头,也重新发送。
  6. 无头测试。 用 terminal 工具运行客户端,确认它返回浏览器看到的相同数据。这就是回报:闭环里没有浏览器。
  7. (可选)包成 CLI——在推导出来的调用上套一个小 argparse 脚本,例如 search.py "frank herbert"。

完整示例(维基百科 search-title,已推导并线上重放):

import requests
r = requests.get(
    "https://en.wikipedia.org/w/rest.php/v1/search/title",
    params={"q": "frank herbert", "limit": 5},
    headers={"accept": "application/json",
             "User-Agent": "Mozilla/5.0 ... Chrome/131 Safari/537.36"},  # 来自 HAR
    timeout=15,
)
for p in r.json()["pages"]:
    print(p["title"], "-", p.get("description"))

常见陷阱

  • 默认库 User-Agent 会被 403。 很多站点(维基百科、Cloudflare 前置的 API)拒绝 python-requests/x.y。始终发送重放提示里的浏览器 UA。这是浏览器成功而推导客户端失败的 #1 原因。
  • 失败的 --action 会在 HAR flush 之前中止——你拿不到文件。如果捕获在某个选择器上报错,这次运行什么都没产生;修选择器(用 --headed 观察)并重跑。不要去调试一个不存在的 HAR。
  • 服务端渲染页面没有 XHR 可推导——har_to_client.py 打印 "No API-looking entries"。数据在 HTML 里;抓取它,或找到那个确实取 JSON 的交互。
  • 防抖/自动补全 XHR 需要真实停顿。 在 fill 后加 --action "sleep:3";仅打字的话,HAR 关闭时请求还没发出。
  • auth/会话端点需要捕获的 Cookie/Authorization 头,而那些会过期。推导客户端的寿命取决于凭据;它 401 时重新捕获。HAR 包含活的机密——把 out.har 当敏感数据,推导后删除。
  • record_har_content="embed" 会产生大 HAR。 用 --max-body 限制打印量;对媒体密集页面,文件本身可能很大。
  • 端点会变。 站点会无通知地改私有 API。客户端坏了时重跑 捕获→推导 闭环,而不是手改 URL。
  • 选错捕获器 = 空/无 HAR。 在云/CDP 后端上用 har_capture.py 什么都录不到(它启动了自己的本地浏览器,而不是你想要的那个)。har_capture_cdp.py 需要端点;在 Hermes 上从 /browser connect 或 BROWSER_CDP_URL 拿。让捕获器匹配通路(运行方式表)。
  • Headless-Chrome UA 是个弱特征。 本地/agent-browser 捕获会得到一个 HeadlessChrome/... User-Agent;有些站点嗅探 "Headless" token。云后端(Browserbase/Browser-Use)发送真实桌面 Chrome UA,所以从云捕获推导的客户端重放更可靠。如果一个 headless 推导的客户端在浏览器没被挡的地方 403,先把 "Headless" UA 换成普通 Chrome UA 字符串,再假设端点变了。
  • CDP 捕获不关闭浏览器。 har_capture_cdp.py 附加到一个它不拥有的浏览器并让它继续运行——这正是 Hermes 管理的云/远程会话所需要的。不要加关闭;让拥有它的后端去拆。

验证

对着一个无 API key 的线上站点做端到端证明:

python3 scripts/har_to_client.py ~/.hermes/cache/scratch/wiki.har --host wikipedia.org --max-body 200

期望推导出打印 GET https://en.wikipedia.org/w/rest.php/v1/search/title,带 q 和 limit 参数和一个 JSON pages 响应——然后用流程里的片段重放它,确认通过普通 HTTP 返回匹配的标题。