Claude Code の WebFetch を 20件サンプリングして、同じページを今取り直し、ローカル qwen3.5:9b (Ollama 経由) に同じ指示で処理させた結果、「比べられた 11件のうち 8件 (○8 / △2 / ×1) が元の WebFetch 結果と一致」でした。残り 9件は、ページが取れない / 404 / 元の WebFetch 側が失敗 / 本文が別物、で比較できず脱落しています。対象は、事前に「ローカルで足りる」とラベルを貼っていた WebFetch です。
Claude Code の WebFetch は内部で小さいモデルを使って本文を要約・抜粋します。ドキュメントは "lossy by design" と自認していて、要約の過程で情報を落とすのを前提とした設計です。そうなると「毎回クラウドの Claude に投げる必要があるか」が気になります。ローカルの 9B クラスで同じ処理が成立するなら、クラウド 1 行あたりのコストと遅延を 1 桁下げられる可能性があります。これを 20件のサンプルで確かめました。
数字は、私の実験 iris-lab exp040 「『ローカルで足りる』とラベルした WebFetch を、実際にローカルで処理させる」(私の実験記録。リポジトリは非公開) で測ったものです。仕様は Claude Code の WebFetch 仕様、Ollama 公式の qwen3.5:9b モデル仕様、Trafilatura 本文抽出ライブラリ を、背景は拙著『ハーネスエンジニアリングガイド』を参照しました。
- 別軸との棲み分け: 姉妹記事「Claude Code Max $200/月×8ヶ月を私のタスク別に分解 — 4割はローカルLLMで代替できました」はタスク別の 4割ローカル化、「RTX 4070でローカルLLMにコードを書かせる2026年版 — Qwen3 Coderとgpt-oss:20bを実測」はコード生成、「ネットにもAPIにも繋がず、ローカルQwen 35Bでコーディングエージェントを動かす」はコーディングエージェント、「Qwen Code CLIをClaudeの隣に置いたら、2タスクで置き換わった」は Qwen Code CLI の使い分け、「Qwen Code CLIをローカルで動かしたら、4回中3回失敗した」は Qwen Code CLI のローカル動作、「RAGはもう古い? — Claude CodeのAgentic Searchを体験してわかった3つの違い」は Agentic Search、「Claude Codeの Agentic Search、rg に3割で負けた」は Agentic Search のベンチでした。違いは、「Claude Code の WebFetch 1機能」をローカル qwen3.5:9b で代替した場合の一致率と、どこで詰まるかの脱落構造を測った点です
-
数字の出どころ: 本記事の数字はすべて、私が行った iris-lab exp040 の 20件サンプルから取っています。元の WebFetch 結果は Claude Code の会話ログから拾ったもの (20/20 見つかりました)、ローカル側は trafilatura で本文を取り直し、Ollama 経由 qwen3.5:9b (think=false、num_ctx 32768、temperature 0) に元と同じ指示を与えた結果です。原文・ページ本文・両モデルの出力は iris-lab の
private/(git 管理外) にあり、公開するのは本記事の集計のみです
「ローカルで足りる」というラベルを、ちゃんと確かめたかった
iris-lab の別実験 (exp039) で、Claude Code の WebFetch 86件を人の目で見て「これはローカルで足りそう」とラベルを貼りました。基準は「決まった情報を抜く系の問い (数値・日付・価格・有無の判定)」です。ドキュメントの要約や、文脈を広く読む必要がある問いは「ローカルで足りない」側に分けました。この分類が実際に正しいかは、ラベルを貼った時点では確かめていません。
exp040 は、その「ローカルで足りる 86件」から seed 40 で 20件を選び、「本当にローカルで足りるか」を 1件ずつ比べた実験です。元の Claude Code の会話ログから WebFetch の出力を拾い、同じページを trafilatura で取り直し、ローカル qwen3.5:9b に同じ指示で処理させて、Iris が ○ / △ / × を付けました。
「○」は数値・日付・価格・有無の判定が元の結果と一致した件、「△」は惜しい誤り (結合セルの列取り違え、記事タイトルを「記載なし」とした等) 、「×」は実質的に間違えた件です。この基準で 11件中 ○8 / △2 / ×1 、残り 9件は比較そのものが成立しない構造的な脱落でした。
20件のうち 11件しか比較できていない原因は、元のサンプリング条件とローカル側の取得条件が揃わないことでした。ローカルで代替できるかどうか以前の問題です。詳しくは次節の内訳で分けます。モデルの能力の話と、取得パイプラインの話を混ぜない方が実測の読み方が正確になります。
20件の内訳 — どこで比較が脱落したか
| 区分 | 件数 | 内訳 |
|---|---|---|
| 比べられた | 11 | ○8 / △2 / ×1 |
| ページの本文が取れなかった (JS で描画) | 2 | Apple の購入ページ、EC の商品一覧 |
| ページが無くなっていた / 取れなかった | 3 | 404 × 2、タイムアウト × 1 |
| 元の WebFetch 側が失敗していた | 2 | 403 (bot よけ)、429 (レート制限) |
| 取り直した本文と元の本文が違い、比べられない | 2 | 元の結果が本文を丸ごと返していた、取り直した本文に該当箇所が無い |
| 合計 | 20 |
比較できた 11件が「qwen3.5:9b の実力の話」で、残り 9件は「WebFetch をローカルに回すなら、取得と本文抽出を自前で用意する必要がある」という話です。両者を分けて読みます。
脱落 9件のうち 5件は、ページ側の問題 (JS 描画 2件 + 404/タイムアウト 3件) です。これは Claude Code の WebFetch も同じく詰まる種類で、ブラウザレンダリング相当 (Playwright / Puppeteer) やリトライを自前で挟む必要があります。残り 4件 (元 WebFetch 失敗 2件 + 本文が別物 2件) は、Claude Code の WebFetch 側が bot よけで止められていたり、本文抽出のスコープが変わっていたりで、どちらのモデルの話でもありません。
比べられた 11件の内訳 — ○8 / △2 / ×1
11件のうち、8件は「数値・日付・価格・有無の判定が元の結果と一致」で ○ です。中身はベンチマーク表の数字、ハードウェアの価格改定、論文の実験条件、人物の経歴の年号、GitHub README の対応バージョン、ドキュメントのサポート言語一覧、といった「ページ内に明示されているファクト」の抜き出しです。これらは qwen3.5:9b で過不足なく拾えました。
△ の 2件は惜しい誤りです。1件は「結合セルのある仕様表 (コア数とキャッシュの割り当て) で列を取り違えた」です。Markdown にそのまま変換すると結合セルが潰れて列幅がずれるパターンで、人の目でも初見で間違えやすい表です。もう 1件は「記事タイトルを『記載なし』とした」で、trafilatura で本文を抽出するときに H1 が落ちた可能性が高いです (本文抽出のアルゴリズムは広告 / ナビゲーションを削るので、サイトによっては H1 まで巻き込みます)。原因は本文抽出層の側にあります。
× の 1件は、照合系の間違いです。設問が「ページに『2〜3ヶ月』と書かれているか」の形式で、qwen3.5:9b が根拠として「約 1〜3ヶ月」と引用しながら、「2〜3ヶ月と書かれている: はい」と答えました。根拠の引用は正しいのに、照合の最終判定を誤りました。「書かれているか」の確認 (照合) は、「情報を抽出せよ」より間違えやすいのが、この 1件に代表的に出ました。
「書く情報の抽出」のほうが、「書かれているかの照合」よりローカルで安定します。モデルに「このページから数値を抜け」と指示すると自然に動きますが、「このページに X と書かれているか」と二者択一を迫ると、根拠が正しくても判定を誤る挙動が時々出ます。実運用なら「抽出 → 文字列マッチ」の 2段に分けた方が、ローカル側のエラーが減ります。
ローカル処理の時間と、Claude Code との比較
qwen3.5:9b の処理時間は 1件あたり 6〜66秒、中央値約 18秒でした。Claude Code 側の WebFetch の処理時間は exp040 では測っていません。ローカル側は1件に1分以上かかることもあり、エージェントの中で何度も呼ぶと重くなります。長い本文 (24,000字で切ったもの) をコンテキストに入れると、推論時間がそのまま伸びます。
| 項目 | Claude Code WebFetch | ローカル qwen3.5:9b |
|---|---|---|
| 本文取得 | 内部 | trafilatura + 失敗時 curl -L |
| 本文抽出 | 内部 (ノイズ除去込み) | trafilatura (アルゴリズム依存) |
| 要約・抜粋 | Anthropic の内部モデル | qwen3.5:9b (think=false、num_ctx 32768、temperature 0) |
| 1件の処理時間 | 未計測 | 6〜66 秒 (中央値約 18 秒) |
| 失敗の分かりやすさ | lossy by design (明示あり) | モデル以前にページ取得で詰まる |
| コスト | Claude 1 API 呼び出し相当 | ローカル GPU の電気代のみ |
中央値 18秒はエージェントループに入れると結構重いです。1 PR のレビューに 20 件 WebFetch が入ると、ローカル側だけで 6分かかります。「どの問いをローカルに回すか」を事前に判定するラベリングが必要で、exp039 で貼ったラベルの意味もそこにあります。全部ローカルで、は現時点で非現実的です。
「ローカルで足りる」の分別は、こう進めると落ち着く
exp040 を通して見えてきた分別の基準は、次のようなものです。
- 抜く情報が確定していて、ファクトが本文に書いてあるタイプ: ローカルで足ります (○8 件の構造)。ベンチマーク表の数字、価格、実験条件、年号、対応バージョン、言語一覧など
- 本文抽出の精度に依存するタイプ: 結合セル・H1 の扱いなど、本文抽出層の性能が結果を左右します (△2 件の構造)。trafilatura を readability.js や custom selector に差し替える判断がここで必要になります
- 照合系 (『ページに X と書かれているか』) のタイプ: ローカル側で誤る回数が増えます (×1 件の構造)。「抽出 → 文字列マッチ」の 2段に分割して、照合の最終判定はコードに任せる構成が安定します
- 本文がブラウザ描画でしか出てこないタイプ: WebFetch 自体が機能しないので、Playwright / Puppeteer を前段に挟む必要があります (JS 描画 2件 の脱落構造)
- 元の WebFetch 側で失敗しているタイプ (403/429/404): 取得パイプラインとレート制御で対処する話です
「ローカルで足りる 86件」のうち 20件を実測した結果、取得と抽出のパイプラインを自前で揃えれば、○8/11 = 72% のケースで Claude Code の WebFetch と同等の抜き出しがローカルで成立する、という目安が立ちました。残り 28% のうち半分 (△2) は本文抽出層の問題、半分 (×1) は照合プロンプトの問題で、いずれもモデルを大きくしても解決しません。
まとめ
- Claude Code の WebFetch 20件サンプルを、同じ指示で qwen3.5:9b (Ollama、think=false、num_ctx 32768、temperature 0) に処理させました
- 比較できたのは 11件 (20件のうち 9件は JS 描画 / 404 / 元 WebFetch 失敗 / 本文不一致で脱落)
- 比較できた 11件のうち ○8 / △2 / ×1 で、「数値・日付・価格・有無の判定」系はローカルで一致しました
- △2 は結合セルの列取り違えと、H1 が本文抽出で落ちた件で、どちらも本文抽出層の問題でした
- ×1 は照合系の判定ミスで、「書かれているか」の確認は「情報を抽出」より間違えやすいです。実運用では 2段に分けると安定します
- 処理時間は 1件 6〜66秒 (中央値約 18秒) で、Claude Code より遅いです。全部をローカルに回すのは非現実的で、問いを選ぶラベリングが前提になります
- 取得と抽出のパイプラインを自前で用意すれば、「ファクト抜き出し」系の WebFetch はローカルで 72% 代替できる、という目安が立ちました
20件のサンプルが全体を代表するかは別の話で、今後 100件 / 1000件のオーダーで同じ検証を回したいところです。また、qwen3.5:9b 以外のローカルモデル (gemma4:e4b、llama3.3:70b など) で同じ 20件を回したときの差分も興味の対象です。exp041 / exp042 で測る予定です。
WebFetch のラベリング (「ローカルで足りるか」の分別) と、ハーネス運用でクラウド API を減らすための分担設計 (Observer / Strategist / Marketer のどのフェーズをローカルに置くか) は、拙著『ハーネスエンジニアリングガイド』でより詳しく扱っています。cron 周期の自律タスクで Claude 1本に頼らない運用を組む本です。
実測条件 (再掲)
- サンプル: iris-lab exp040 の
collect.pyが exp039 で local_ok とした 86件から seed 40 で 20件を抽出 - 元の結果: Claude Code の会話ログから 20/20 件分の WebFetch 出力を抽出
- 取得側: trafilatura で本文抽出、先頭 24,000 字で切る (
run_local.py)、失敗分はretry.pyで curl -L - ローカル LLM: Ollama 経由 qwen3.5:9b、think=false、num_ctx 32768、temperature 0
- 採点: Iris が「抜くべき情報が揃っているか・誤りがないか」の基準で ○ / △ / ×
- 原文・ページ本文・両モデルの出力は iris-lab の
private/(git 管理外)。本記事に公開するのは集計のみ - 本記事は上記 iris-lab exp040 の実測結果 + Claude Code / Ollama / trafilatura の公式仕様をもとにしています。数字は私の実験 (iris-lab) の結果で、この記事のための追加の測定はしていません
