本記事はcogley.jpの完全版の要約版です。コードの完全な解説、フォールバック戦略の詳細、計測ハーネスの全コードは完全版をご覧ください。
AIクローラー(Gemini、GPT、Claude、Perplexityなど)は絶えずサイトを読みにきており、HTMLよりMarkdownのほうを好む。Markdownのほうが文脈がクリーンで、トークン数が少なく、推論コストも安い。
コンテンツが すでにMarkdown の場合は、Accept: text/markdownで交渉するだけで済む。コンテンツが HTML の場合(プロキシ、ドキュメントミラー、リーダーモード、LLMサマライザーへの供給、あるいは単に静的サイトの配信など)は、WorkerのなかでMarkdownに変換することになる。無料プランでは 10 msのCPU と 1 MBの圧縮バンドル のなかで処理することになり、どの戦略が生き残るかを実測した結果をまとめる。
「有料」は二つある
Cloudflareで「有料」という言葉は紛らわしい。本記事で指すのは以下のどちらか、はっきりさせておきたい。
- Workers Paid:月額5ドル+従量課金。CPU予算は10 ms→30 s、圧縮バンドル上限は1 MB→10 MB。このプランが変換の前提条件を変える。
- Cloudflare Pro:ドメインあたり月額20ドル。WAFや画像最適化が追加されるが、Worker制限は 変わらない。
本記事の「有料」はWorkers Paidを指す。
無料プランの制限
| 制限 | 無料 | 有料 |
|---|---|---|
| CPU時間(リクエスト毎) | 10 ms | 30 s(Standard) |
| 圧縮Workerバンドル | 1 MB | 10 MB |
10 msはルーティングやJSON変換には十分だが、HTML→Markdownは違う。DOMを解析して全ノードを歩くCPU集約的な処理であり、自前のDOM実装を含むライブラリはバンドル予算も食い潰しがちだ。
結論:HTMLRewriter
HTMLRewriterはworkerdに組み込まれた、npm依存ゼロのストリーミングSAXスタイル のHTMLパーサーだ。バイト到着時に<h1> / text / </h1>のイベントを発火し、メモリ上にツリーを構築しない。
サンプルの34 KB HTML記事に対する実測値:
- バンドル:10.52 KiB 非圧縮 / 3.74 KiB gzip(1 MB予算の0.4%)
- CPU:50回実行の中央値 2 ms(最小2、最大8)。10 ms予算の20%。
CPU予算に5倍、バンドル予算に250倍の余裕がある。wrangler devのローカルworkerdなので、エッジでは1.5〜2倍を見込んで3〜4 msあたりを想定するとよい。
なぜ他は収まらないか
アーキテクチャ上の違いが効いている。turndown / Readability / cheerio系はドキュメント全体をバッファしてDOMを構築してから歩く。この構築パスがMarkdownを1文字も生成する前に払うCPU税であり、自前のDOM実装(数百KBのバンドル)を同梱する理由でもある。
| 戦略 | バンドル目安 | 推定CPU | 無料で使えるか |
|---|---|---|---|
| HTMLRewriter | ~11 KB | 2 ms | ✅ 圧倒的な余裕 |
| node-html-parser | ~40 KB | 速い | ✅ フォールバックとして |
| cheerio + 自作エミッター | 100〜150 KB | 中程度 | △ 余裕は狭い |
| turndown + domino | ~320 KB | 15〜30 ms | ❌ CPU予算を超える |
| Readability + turndown | ~400 KB | 20〜40 ms | ❌ 論外 |
| jsdom | ~2 MB | - | ❌ バンドル予算の2倍 |
HTMLRewriterの使い方(要点)
マッチした要素のそばにMarkdown句読点をテキストとして差し込み、残りのタグは*キャッチオールで剥がす。
const rewriter = new HTMLRewriter()
.on('head, nav, aside, footer, script, style, figure', {
element(el) {
el.remove();
},
})
.on('h1', {
element(el) {
el.before('\n\n# ', { html: false });
el.after('\n\n', { html: false });
},
})
.on('h2', {
element(el) {
el.before('\n\n## ', { html: false });
el.after('\n\n', { html: false });
},
})
.on('li', {
element(el) {
el.before('\n- ', { html: false });
},
})
.on('a', {
element(el) {
const href = (el.getAttribute('href') || '').replace(/\s+/g, '');
el.before('[', { html: false });
el.after(`](${href})`, { html: false });
},
})
.on('*', {
element(el) {
el.removeAndKeepContent();
},
});
const raw = await rewriter
.transform(new Response(html, { headers: { 'content-type': 'text/html' } }))
.text();
完全な後処理(HTMLエンティティのデコード、連続改行の整形など)、既知の制約(順序付きリストが- になる、<pre>内インラインコードのフェンスが落ちる、など)は完全版参照。
計測リグ
数値を生んだ計測リグは独立した公開リポジトリとして公開している:cf-workers-html-to-markdown-harness。
git clone https://github.com/RickCogley/cf-workers-html-to-markdown-harness
cd cf-workers-html-to-markdown-harness
npm install --ignore-scripts
npm run dev
curl 'http://127.0.0.1:8791/bench?strategy=htmlrewriter&runs=50'
新しい戦略はsrc/handlers/にハンドラファイル1つと、src/index.tsにマップ1行を足すだけで登録できる(詳細はADD_A_STRATEGY.md)。
あきらめて有料に切り替えるべきとき
次が必要なら、無料プランと戦うのをやめる。
- 往復可能なMarkdown:turndown
- 記事抽出(ナビ/サイドバー除外):Readability
- HTMLテーブル → Markdownテーブル:turndownかcheerio
- 入力が50 KB超または形状が多様:有料の30 s予算
Workers Paidは月額5ドル+従量課金で、Cloudflare Proドメインプランとは別物。本格的なコンバーターが必要なら、無料プラン予算と格闘する1日分のエンジニアリングコストより安い。
この記事は cogley.jp に掲載されたものです。
Rick Cogley(コグレー・リック)は株式会社イソリアのCEO兼創業者。東京で日英バイリンガルITアウトソーシングとインフラサービスを提供中。