Fragments of verbose memory

冗長な記憶の断片 - Web技術のメモをほぼ毎日更新

Mar 9, 2026 - 日記

Web→Adapter→Tool→Agent: 自己学習型スキルで『再訪を実測で平均98%トークン削減』する

English version

web-adapter-tool-agent-architecture cover image

Webから情報を取る処理を、LLMに毎回「生HTMLを読ませて頑張らせる」形で組むと、だいたい高くて遅くて壊れやすいです。 しかも、同じサイトを何度も読むユースケース(ニュース監視、ドキュメント追跡、価格改定検知など)だと、同じ失敗を何度も繰り返します。

この手の問題は、気合のスクレイピングテクで解決するというより、「一度うまくいった抽出方法を“道具”として固定化し、次回以降は再利用する」と割り切った方が素直に効きます。 この記事では、Web→Adapter→Tool→Agentという変換パイプラインで、スクレイピングを学習で道具化する設計をまとめます。

元々の着想は、以前の記事で紹介した web2cliGitHubリポジトリ )です。 あの記事の「Every website is a Unix command」という思想を、エージェント運用(再訪/トークン/ドリフト)に寄せていくと、だいたいこの方向に収束します。

最近、その延長線として self-learning-web-adapter という自己学習型スキル(skill: エージェントに渡す手順とツールのパッケージ)を追加しました。 スキル本体は skills/self-learning-web-adapter に置いてあります。

なぜつらいのか: 生HTMLをそのままLLMに渡すとコストが増える

まず前提を揃えます。ここで言うLLMはLarge Language Model(大規模言語モデル)で、文章だけでなく「外部ツールを呼び出して作業する」エージェント(agent: LLMが道具を使って目的を達成する仕組み)として動く想定です。

生HTMLをLLMに渡して抽出させると、次のコストが積み上がります。

  • トークン(token: LLMが読む入力単位)コストが高い
  • レイテンシ(latency: 処理待ち時間)が伸びる
  • DOM(Document Object Model: HTMLを木構造として扱う概念)がちょっと変わるだけで壊れる
  • 失敗時の再試行が増えて、さらに高くなる

個人的には「初回はそれでもいいけど、2回目以降は同じ探索を繰り返したくない」が本音です。

解決の方向性: 探索を1回に閉じ込め、実行を軽くする

考え方はシンプルで、Webを毎回スクレイピングするのをやめて、こう変換します。

1
2
3
4
5
6
7
website
  ↓ (探索: 1回)
adapter
  ↓ (固定化)
tool / CLI
  ↓ (再利用)
agent

このモデルだと、LLMが毎回やることは「生HTMLの読解」ではなく「道具を呼ぶ」になります。 うまくいくと、LLMの入力は数百トークンのJSON(JavaScript Object Notation: 構造化データ形式)に圧縮できます。

参考として、特定のサイト(ブログ/マーケティングページ)を例に「生HTML」と「アダプター出力」を比べると、95%〜99%台の入力トークン削減が出ることがあります。 ここは誇張しない方が良くて、初回学習コストは別にかかりますし、サイトによって効きが違います。 ただ、「再訪が多い」ワークロードでは回収しやすい、という方向性はかなり安定しています。

実測: 再訪でどれくらいトークンが減る?

「本当にそんなに減るの?」という話なので、簡単な実測も入れておきます。 トークン計測には tiktoken (tokenizer: 文字列をトークンに分割する仕組み)を使い、o200k_base で数えました。

比較は3パターンです。

  • 生HTMLをそのままLLMに渡す
  • 学習済みアダプターのJSON出力をLLMに渡す
  • web2cli風ラッパーのJSON出力をLLMに渡す

学習は各サイト3記事で行い、別の1記事(ホールドアウト)で評価しています。

SiteHTML tokensAdapter tokensweb2cli tokensReduction vs AdapterReduction vs web2cli
blog.python.org15,05726535198.24%97.67%
blog.rust-lang.org7,65626336196.56%95.28%
vercel.com224,73525533599.89%99.85%

平均すると、入力トークンの削減率は次の通りでした。

  • 平均削減率(アダプター直出力): 98.23%
  • 平均削減率(web2cli風コマンド出力): 97.60%
  • 平均削減量(アダプター直出力): 82,221.7 tokens / page
  • 中央値削減量(アダプター直出力): 14,792 tokens / page

ポイントは2つです。

  1. 生HTMLは、サイトによっては「それだけで」数万〜数十万トークンになる
  2. いったん学習してしまえば、必要な情報だけを数百トークンのJSONに圧縮できる

特に vercel.com のような重いページだと、1ページあたり22万 tokens 以上削れていました。

注意点も書いておきます。

  • 今回は3サイトだけの小規模実測です
  • 抽出項目は主に title author published に絞っています
  • 初回アクセス時は学習コストがあるので、本質的な効果は「再訪」で出ます

雑な見積もりはこの式でできます。

1
saved_cost = saved_tokens_per_page * pages_per_month / 1_000_000 * model_input_price

軽い記事中心なら中央値(14,792 tokens / page)で見るのが安全です。 SPA(Single Page Application: 1ページ内で画面遷移するWebアプリ)やマーケティングページが多いなら、平均値(82,221.7 tokens / page)側に寄る可能性があります。

Adapterとは何か: サイト固有差分を閉じ込める契約

Adapter(アダプター)は「このホスト(host: example.comのようなドメイン)ならこう取る」を閉じ込めた設定+ルールです。 ポイントは、アダプターがLLMの思考ではなく、抽出の契約(contract)として残ることです。

例えば、記事ページ(article: ブログ記事など)なら、抽出したいのはこのへんです。

  • title(タイトル)
  • author(著者)
  • published(公開日時)

抽出戦略(strategy: どの情報源を優先するか)も明示します。

  • JSON-LD (JSON for Linking Data: HTML内に埋め込める構造化メタデータ)
  • Open Graph protocol (OG: SNS向けメタタグ仕様)や通常のmetaタグ
  • CSSセレクタ(CSS selector: HTML要素を指定する記法)

さらに、実運用で重要なのが「壊れたかどうかを機械的に判断する」ことです。 ここでDOM fingerprint(DOMフィンガープリント: DOM構造の指紋)を使います。 学習時にDOMの特徴量を保存し、実行時にそれとズレたら「ドリフト(drift: 構造変化)」として扱い、再学習に回します。

Tool/CLIとは何か: 1行で叩ける“Webの窓口”

アダプターをそのまま置いても良いのですが、エージェントに使わせるなら、CLI(Command Line Interface: コマンドラインから叩ける道具)まで落とすのが楽です。

理想は「URLを渡したらJSONが返る」だけ。

1
2
# 例: rust-langブログの記事を構造化して取る(イメージ)
site-article https://blog.rust-lang.org/2026/03/05/some-post.html

この形にしておくと、エージェント側のプロンプト設計も単純化します。 「このコマンドを呼んで、返ってきたJSONのtitle/publishedだけ使う」みたいな指示ができます。

似た発想として、Webをコマンド化するweb2cliがあります。 あの思想(Every website is a Unix command)を、エージェント運用(再訪/トークン/ドリフト)に寄せると、だいたいこの方向になります。

実例: 自己学習型スキル self-learning-web-adapter を動かす

ここからは「スキル寄り」の話をします。 このスキルは、同じホストを何度も読むときに、サイト差分をアダプターへ閉じ込めて再利用します。

目的は次の通りです。

  • Why: 2回目以降は、スクレイピング探索を繰り返したくない
  • What: URL→構造化JSON(title/author/published + 健康診断情報)を返す
  • Prereq: Python 3.10+、ネットワーク到達
  • Verify: python3 skills/self-learning-web-adapter/scripts/web_adapter_cli.py run <url> がJSONを出す

1) セットアップ

スキルを使うだけなら、リポジトリをクローンしなくても大丈夫です。 スキル追加は、Node.js に付属するnpx(npm package runner: 一時的にCLIを実行する仕組み)で済みます。

1
npx skills add tumf/self-learning-web-adapter

依存は最小で、HTMLパース用にBeautiful Soup (HTMLパーサ)を入れます。 (Pythonの依存はnpxでは解決されないので、ここは別で入れます)

1
2
3
4
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install -U pip
python3 -m pip install beautifulsoup4

注意: 以降のコマンドは、skills/self-learning-web-adapter/ がカレントディレクトリ直下に追加されている想定です。 もし別の場所に入った場合は、パスだけ読み替えてください。

2) 学習サンプルを用意する(同一ホストで3本以上)

スキルのルールはシンプルで、学習(learn)は「同じホストのURLを3本以上」です。 ブログなら、同じ著者・同じカテゴリの3記事くらいを選ぶと外しにくいです。

3) learn → runで固定化する

同一ホストの代表ページを渡すと、アダプターが adapter_registry/<host>.json に保存されます。

1
python3 skills/self-learning-web-adapter/scripts/web_adapter_cli.py learn <url1> <url2> <url3>

学習できたら、別のURL(ホールドアウト: 学習に使っていない検証用ページ)で実行します。

1
python3 skills/self-learning-web-adapter/scripts/web_adapter_cli.py run <holdout-url>

出力には、抽出結果だけでなく signature_knownextraction_health.score のような診断情報が入ります。 このへんが「スキル寄り」にする理由で、壊れた時の扱いまで含めて“道具化”できます。

4) ドリフトチェックと再学習

checkrun と同じくJSONを返しますが、「再学習が必要そうか」を見る用途です。

1
python3 skills/self-learning-web-adapter/scripts/web_adapter_cli.py check <url>

needs_retrain: true が立ったら、再学習(retrain)に回します。

1
python3 skills/self-learning-web-adapter/scripts/web_adapter_cli.py retrain <host>

5) web2cli風コマンドにエクスポートする

スキルとしての“手触り”が出るのはここです。 学習済みアダプターを、web2cli風の1コマンドに落とします。

1
2
python3 skills/self-learning-web-adapter/scripts/web_adapter_cli.py export-command <host>
python3 skills/self-learning-web-adapter/scripts/web_adapter_cli.py commands

エクスポートされたコマンドは web2cli_commands/ に置かれ、web2cli_commands/index.json がレジストリ(registry: コマンド一覧)になります。 エージェント視点では「サイトがツールになった」状態です。

設計の勘所: うまくいくスキルに寄せるコツ

やってみて効くことが多いのはこのへんです。

  1. まずJSON-LDを疑う
  2. 次にOpen Graphと通常metaで拾う
  3. CSSセレクタは最後の逃げ道にする
  4. 失敗を「例外」ではなく「健康診断」にする(check)
  5. いきなり完璧を目指さず、欲しいフィールドを絞る(title/dateくらいから)

逆にアンチパターンはこう。

  • 1ページでたまたま動いたCSSセレクタを、根拠なく信じる
  • エージェント側のプロンプトに抽出ロジックを埋め込み、毎回実行する
  • 壊れた時に「その場しのぎ」で直して、学習物として残さない

まとめ: “Webを読む”を、道具の問題に変える

Web→Adapter→Tool→Agentの発想でやると、スクレイピングは「頑張って読む」から「使い回せる道具を作る」に変わります。 この変換が効くのは、特に「同じサイトを何度も読む」ワークロードです。

次のアクション案です。

  1. 自分がよく読むドメインを1つ選び、欲しいフィールドを3つに絞る(title/published/urlくらい)
  2. JSON-LD→meta→CSSの優先順位で、まずは動く抽出器を作る
  3. DOM fingerprintとcheckを入れて、壊れたら自動で学習を回す設計に寄せる

参考リンク