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 全文
HAR-Derived API Client
本物のブラウザーでサイトを一度だけ操作し、そのあいだの通信を 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 の窓口も含みます。 記録用のスクリプトが 2 本あるのは、自分で起動したブラウザーと、CDP でつないだ ブラウザーとで HAR の取り方が違うからです(「実行のしかた」を参照)。
こんなときに使います
- 「<website> 用の CLI やクライアントを作って」— クリックを自動化するのではなく、API を割り出します。
- 「このサイトに公開 API はないけれど、ページは明らかに JSON を取ってきている」
- 同じ問い合わせで
browser_navigateを何度も回そうとしているとき。手を止めて、窓口を一度だけ割り出してください。 - 入力補完・検索・フィード・購入手続きの 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 キーは要りません。クライアントに必要なキーやトークンは、HAR に記録されたものです。
- CDP を使う場合(
har_capture_cdp.py)は、到達できる CDP の窓口が要ります。Hermes では
/browser connect を実行すると今の窓口が表示されます。設定の 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_URL |
CDP | har_capture_cdp.py |
目安はこうです。**Hermes が自分でブラウザーを*起動した*なら har_capture.py、 CDP で*つないだ*なら har_capture_cdp.py。** har_capture.py は Playwright の record_har_path を使いますが、これは自分の持ち物のコンテキストでしか働きません。 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 / 認証)を表示します。
パスはこの skill のディレクトリーを基準に組み立ててください。基本の流れはこうです:
# 1a. Capture, LOCAL browser (Hermes launched it)
python3 scripts/har_capture.py "https://SITE/" out.har \
--action "fill:input[name=search]:my query" --action "sleep:3" --wait 2
# 1b. Capture, CDP browser (cloud backend or /browser connect)
# get the endpoint from /browser connect or 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. Derive — read the endpoints out of the HAR
python3 scripts/har_to_client.py out.har --host SITE --max-body 400
# 3. Replay — write a tiny client from the printed endpoint (see Procedure)早見表
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 (run in order after page load)
use when Hermes LAUNCHED the browser (local browser_navigate default)
har_capture_cdp.py <cdp_url> <out.har> [--goto URL] [--wait S] [--action SPEC ...]
same action SPEC; attaches to an existing CDP browser and does NOT close it
use for cloud backends (Browserbase/Browser-Use/Firecrawl) & /browser connect
har_to_client.py <in.har> [--host SUBSTR] [--include-static] [--max-body N]
default: keeps only XHR/fetch/JSON; --host narrows to one domain
prints per endpoint: query params, non-boring req headers, req body sample,
response status/content-type + body sample
prints "### Replay hints": the browser User-Agent, cookie/auth presence手順
- 経路に合わせて記録用のスクリプトを選びます(「実行のしかた」の表を参照)。ローカルで起動したなら
har_capture.py、CDP で届いているならhar_capture_cdp.pyです。Hermes では、クラウドやリモートを使っているときに/browser connectが CDP の窓口を教えてくれます。 - 操作を見つけます。
browser_navigate(または--headedを付けた記録)でサイトを開き、どのセレクターに入力・クリックすればよいかを確かめ、開発者ツールのネットワークで JSON の XHR が飛ぶことを確認します。 - HAR を記録します。
terminalツールから実行してください。目当ての通信に届くよう--actionを並べます。入力欄をfillしてから、遅らせて飛ぶ XHR に間に合うだけsleepし、最後には必ず--waitを置いて、遅れて返ってくる応答も書き出させます。どちらの記録用スクリプトも応答の本文を埋め込むので、割り出したクライアントは本物のデータの形を見られます。 - 割り出します。
har_to_client.py --host <domain>を実行し、メソッド、URL とパスの型(数字や UUID の区間は{id}にまとめられます)、クエリーのパラメーター、リクエスト本文の JSON、そして### Replay hintsの欄を読み取ります。 - クライアントを書きます。 同じメソッド・パス・クエリー・本文で、リクエストをそっくり再現します。サイトが実際に必要とするヘッダーを送ってください。少なくとも、再現の手がかりにある User-Agent はそのまま写します。Cookie や認証・トークンのヘッダーが挙がっていれば、それも一緒に送ります。
- ブラウザーなしで試します。
terminalツールからクライアントを実行し、ブラウザーで見えたのと同じデータが返ることを確かめます。ここが狙いどおりの成果です。もうブラウザーは要りません。 - (任意)CLI に仕立てます。 割り出した呼び出しを小さな
argparseのスクリプトで包みます。たとえばsearch.py "frank herbert"のように。
実際にやってみた例(Wikipedia の search-title を割り出して、そのまま呼んだもの):
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"}, # from HAR
timeout=15,
)
for p in r.json()["pages"]:
print(p["title"], "-", p.get("description"))つまずきどころ
- ライブラリー既定の User-Agent は 403 になります。 多くのサイト(Wikipedia、Cloudflare 越しの API など)は
python-requests/x.yを弾きます。再現の手がかりにあるブラウザーの UA を必ず送ってください。ブラウザーでは成功したのにクライアントが失敗する原因の第 1 位がこれです。 --actionが 1 つ失敗すると、HAR が書き出される前に止まります。 ファイルは残りません。セレクターでエラーになったなら、その回は何も生んでいないので、セレクターを直して(--headedで目視すると早いです)やり直してください。存在しない HAR を調べても仕方ありません。- サーバー側で組み立てるページには、割り出せる XHR がありません。
har_to_client.pyは「No API-looking entries」と表示します。データは HTML に入って届いているので、そこから取り出すか、JSON を取りに行く操作を探してください。 - 入力補完のような遅らせて飛ぶ XHR には、実際の待ちが要ります。
fillのあとに--action "sleep:3"を足してください。入力しただけでは、HAR を閉じる時点でまだリクエストが飛んでいません。 - 認証やセッションの窓口には、記録された
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から取ってください。経路と記録用のスクリプトを合わせましょう(「実行のしかた」の表)。 - ヘッドレス Chrome の UA は目立ちます。 ローカルや agent-browser での記録では
HeadlessChrome/...という User-Agent になり、この「Headless」の文字を見ているサイトがあります。クラウド(Browserbase / Browser-Use)は本物のデスクトップ版 Chrome の UA を送るので、そこから割り出したクライアントのほうが素直に動きます。ブラウザーでは通ったのにヘッドレス由来のクライアントが 403 になるなら、窓口が変わったと決めつける前に、「Headless」入りの UA をふつうの Chrome の UA に差し替えてみてください。 - CDP での記録はブラウザーを閉じません。
har_capture_cdp.pyは自分の持ち物でないブラウザーにつなぎ、そのまま動かし続けます。Hermes が面倒を見ているクラウドやリモートのセッションでは、これが正しい振る舞いです。閉じる処理を足さず、持ち主側に片づけさせてください。
検証
API キーなしで、実際のサイト相手に端から端まで確かめます:
python3 scripts/har_capture.py "https://en.wikipedia.org/wiki/Main_Page" ~/.hermes/cache/scratch/wiki.har \
--action "fill:input[name=search]:dune messiah" --action "sleep:3" --wait 2
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 でも同じ題名が返って くることを確かめてください。