データチームに「Webデータ収集のコストは?」と尋ねれば、ギガバイト、リクエスト数、クレジット、成功率といった答えが返ってくる。財務部門に何を知りたいか尋ねれば、答えはもっと単純だ。使える1レコードあたりのコストはいくらで、それは上がっているのか下がっているのか。この2つの会話が噛み合うことはめったにない。エンジニアが追跡する数値はパイプラインを説明するものであり、そのパイプラインが何を生み出しているかを説明するものではないからだ。
「クリーンレコードあたりのコスト(cost per clean record)」はこのギャップを埋める。これは、収集ジョブの総コストを、検証を通過したレコード数、つまりレポートやモデル、顧客向け製品に実際に使うレコード数で割ったものだ。本ガイドではこれを正しく定義し、実際にお金がどこへ流れているかを示し、具体例を通して計算し、コストを動かすレバーを重要度順に並べる。
要点
- クリーンレコードあたりのコストを測定する。これはリクエスト数やHTTP成功数ではなく、コンテンツ検証を通過したレコード数で総コストを割ったものである。
- 監視ジョブについては、「有効な変化1件あたりのコスト(cost per useful change)」を追加する。ほとんどの再取得は「何も変わっていない」ことを確認するだけなので、変化1件のコストはレコード1件のコストの何倍にもなり得る。
- 課金モデルによって、どの無駄が痛手になるかが決まる。帯域幅課金は重いページに厳しく、成功課金は不要な取得に厳しい。
- 中央値のホームページの重さは約2.5MBで、そのうちHTMLはわずか22KBである。パースする部分だけを取得することは、帯域幅課金の収集において最大級の節約になることが多い。
- このメトリクスは月次で、ジョブごとに、その構成要素とともに報告する。財務が追跡できる数値こそが、予算がつく数値である。
メトリクスを正確に定義する
3つの定義がほとんどの仕事をこなす。
**総コスト(Total cost)**は、そのジョブが消費したすべてを指す。プロキシの帯域幅またはAPIクレジット、コンピュート、ストレージ、そして正直に言えば、そのジョブを稼働させ続けるために費やしたエンジニアリング時間も含まれる。
**クリーンレコード(A clean record)**とは、検証を通過したレコードのことだ。必須フィールドが存在し、値が妥当な範囲にあり、ブロックページやチャレンジ、空のシェルではなく、実際に要求した通りのページであったこと。HTTP 200のレスポンスは、これらのチェックを通過するまではクリーンレコードとは言えない。この両者の間のギャップこそが、サイレント失敗率(silent failure rate)の主題である。
**有効な変化(A useful change)**は監視において重要である。ある価格を毎日再取得し、月に2回変化するとしよう。残りの28日間に支払ったレコードは、何も新しいことを確認していない。有効な変化1件あたりのコストは、監視が本来何のためにあるのかを捉えている。
課金方式を把握する
同じパイプラインでも、課金モデルによって高くも安くもなる。それぞれのモデルが数えるものが異なるからだ。
| 課金モデル | 何に対して支払うか | 何が高くつくか |
|---|---|---|
| レジデンシャルプロキシの帯域幅 | ゲートウェイを通過するバイト数 | 重いページ、レンダリング、データを転送するリトライ |
| スクレイピングAPIクレジット | 成功したレスポンス | 不要だった取得 |
Shifterのレジデンシャルゲートウェイでは、送受信されたすべてのバイトがヘッダーも含めてカウントされ、エラー前にバイトが転送されていれば失敗したリクエストもカウントされる。Web Scraping APIでは、JavaScriptレンダリングが有効かどうかにかかわらず、成功したリクエストは1クレジットのコストとなり、失敗したリクエストとターゲット側のエラーは無料で、1回の呼び出し内での自動リトライはその1回の呼び出しとしてカウントされる。したがって帯域幅課金では、すべての画像やスクリプトを読み込んだレンダリング済みページはHTMLだけよりもはるかに高くつく可能性があるが、成功課金では同じコストになる。
このサイズの差は大きい。HTTP Archiveの2025年版Web Almanacによると、中央値のホームページの重さはデスクトップで2.86MB、モバイルで2.56MBだったのに対し、中央値のHTMLはどちらも22KBだった。HTMLだけ、あるいはその背後にあるJSONエンドポイントだけをパースするのであれば、完全にレンダリングされたページのバイトのほとんどは何の役にも立たない。
計算例
1ヶ月間の監視ジョブを考えてみよう。以下の数値は説明のための例示であり、$3/GBというレートも計算例のための概算の数字であって、見積もりではない。
| 入力項目 | 値 |
|---|---|
| 送信されたリクエスト数(リトライ含む) | 120,000 |
| リクエストあたりの平均バイト数 | 450 KB |
| HTTPレベルの成功数 | 108,000 |
| 検証を通過したレコード数 | 97,000 |
| 前回のコピーと異なっていた有効なレコード数 | 6,800 |
| 帯域幅レート | $3.00 per GB |
| コンピュート | $40 |
以下の計算式にかけると、次の結果が得られる。
| メトリクス | 値 |
|---|---|
| 総コスト | $202.00 |
| リクエストあたりのコスト | $0.0017 |
| クリーンレコードあたりのコスト | $0.0021 |
| 有効な変化1件あたりのコスト | $0.0297 |
| サイレント失敗率 | 10.2% |
| クリーンレコードあたりのバイト数 | 約557 KB |
ここで際立つ点が2つある。有効な変化1件あたりのコストは、クリーンレコード1件あたりのコストの約14倍になる。ほとんどの取得が「何も動いていない」ことを確認するだけだからだ。そして、HTTPレベルで成功したものの10件に1件は、使えるものを何も生み出していない。
ここで1つだけ変更してみる。フルページのレンダリングをやめ、パーサーが必要とするHTMLまたはJSONだけを取得することで、リクエストあたりの平均を450KBから60KBに削減する。他はすべて同じままとする。総コストは$61.60に、クリーンレコードあたりのコストは$0.0006に、有効な変化1件あたりのコストは$0.0091にまで下がる。ページの取得方法をひとつ変えただけで、3分の2以上の削減となる。
計算式は数行で書ける。
from dataclasses import dataclass
@dataclass
class Run:
requests: int # every request sent, including retries
bytes_transferred: int # everything through the proxy, failures included
responses_ok: int # HTTP-level successes
records_valid: int # records that passed content validation
records_changed: int # valid records that differed from the last copy
price_per_gb: float # your plan's rate
compute_cost: float = 0.0
people_cost: float = 0.0 # engineering time spent on this job, if you count it
def unit_economics(run):
bandwidth = run.bytes_transferred / 1e9 * run.price_per_gb
total = bandwidth + run.compute_cost + run.people_cost
return {
"total_cost": round(total, 2),
"cost_per_request": round(total / max(1, run.requests), 5),
"cost_per_clean_record": round(total / max(1, run.records_valid), 4),
"cost_per_useful_change": round(total / max(1, run.records_changed), 4),
"silent_failure_rate": round(1 - run.records_valid / max(1, run.responses_ok), 3),
"bytes_per_clean_record": int(run.bytes_transferred / max(1, run.records_valid)),
}
クレジット課金のジョブの場合は、帯域幅の行を「成功したレスポンス数×クレジット単価」に置き換えればよい。それ以外の計算は同じだ。
レバー:影響の大きい順
1. 変化していないものの取得をやめる。 監視においては、変化していない再取得が最大のコスト項目になることが多い。各ページが実際にどれくらいの頻度で変化するかに基づいて訪問スケジュールを組み、サイトが対応していれば条件付きリクエストを使うことで、変化を見逃すことなく取得数を減らせる。この手法はコストを意識したクロールスケジューリング(cost-aware crawl scheduling)で解説されており、実際の変化とノイズを見分ける方法は大規模な変化検出(change detection at scale)で扱われている。
2. ページごとの取得量を減らす。 帯域幅課金の場合、データに必要でなければレンダリングを避け、レンダリングが必須の場合は画像・フォント・メディアをブロックし、可能ならJSONエンドポイントを優先し、圧縮を有効にしておく。詳細なリストはプロキシ帯域幅コストの削減(cutting proxy bandwidth costs)にある。
3. サイレント失敗を修正する。 成功として通ってしまうブロックページやチャレンジ、空のシェルはすべて、良質なレコードと同じコストがかかりながら何も生み出さない。コンテンツを検証し、ターゲットごとに失敗数を数え、失敗を生むターゲットを修正するか取得ペースを落とす。
4. 安定したソースから抽出する。 メンテナンスは実質的なコストだ。サイトのリデザインのたびに壊れるセレクタはエンジニアリング時間を消費するため、構造化データは1回の実行だけを見たときよりも、1年を通してみると安くつく。詳しくはHTMLパースをやめる(stop parsing HTML)を参照。
5. 課金モデルをターゲットに合わせる。 レンダリングが必須の重いページは成功課金の方が安くつくことがあり、軽量なHTMLやJSONのターゲットは通常帯域幅課金の方が安い。複数種のターゲットを扱う場合は両方を併用することが多い。この広いトレードオフについてはWebスクレイピングインフラの内製か外部委託か(build vs buy for web scraping infrastructure)で扱っている。
財務部門への報告
メトリクスは一貫して報告されて初めて意味を持つ。月に一度、ジョブごとに以下を公開する。
- 総コスト。帯域幅またはクレジット、コンピュート、そして数えるのであれば人件費に分けたもの。
- クリーンレコード数、およびクリーンレコードあたりのコスト。
- 監視ジョブについては、有効な変化数、および有効な変化1件あたりのコスト。
- サイレント失敗率とクリーンレコードあたりのバイト数を、先行指標として2つ示す。
- 前月からの主な変化と、その理由。
これを四半期にわたって続ければ、Webデータは不透明なインフラの項目から、予算化でき、他所からデータを購入する場合と比較でき、正当化できる単位コストへと変わる。
結論
ギガバイトやリクエスト数は労力を説明する。財務部門が気にするのはアウトプットだ。使えるレコード1件のコストはいくらか、そして監視においては、実際の変化1件のコストはいくらか。クリーンレコードはステータスコードではなく検証によって定義し、そのジョブが発生させるあらゆるコストを数え、結果を月ごとにジョブ単位で報告する。
レバーはめったに特別なものではない。何も変わっていないところでの取得頻度を減らし、ページごとの取得量を減らし、成功しているように見えて実はそうでないページへの支払いをやめ、壊れないソースから抽出する。それぞれが、その場にいる誰もが読み取れる1つの数値に直接現れる。
出典と参考文献
- HTTP Archive, Web Almanac 2025: Page Weight。中央値のページおよびHTMLの重さ、2025年7月のクロールデータ。
- Shifter, Residential Proxies bandwidth and billing および Web Scraping API errors and limits のドキュメント。