WARC (Web ARChive) ファイルフォーマット入門
Webアーカイブの標準ファイルフォーマットであるWARC (Web ARChive) の基礎を解説する。ISO 28500で規格化されたフォーマットの構造、レコード型、そして実際にWARCを生成・読み取るためのツールの使い方までを扱う。一方、大規模クローラーの設計やWARCを用いたリプレイシステムの実装は扱わない。
TL;DR
- WARCは複数のHTTPリクエスト/レスポンスを1ファイルにまとめて保存するための標準フォーマットで、2009年にISO 28500として規格化されている
- ファイルはレコードの連結として構成され、各レコードはHTTPライクなヘッダーとコンテンツブロックを持つ。
warcinfo/request/response/revisitなど複数のレコード型を使い分ける - Internet ArchiveやCommon Crawlといった主要なWebアーカイブプロジェクトが採用しており、
wget --warc-fileやPythonのwarcioを使えば手元でも簡単に生成・解析できる
背景: なぜWebアーカイブに専用フォーマットが必要か
Webページを保存したいだけなら、単純にHTMLをcurlでダウンロードしてディレクトリに並べればよさそうに見える。しかし実際のアーカイブには以下の要件がある。
- リクエスト/レスポンスのペアを保持する — あるコンテンツがどのURL・どのヘッダーで返されたかを後から検証できる必要がある
- HTTPヘッダーを含める —
Content-TypeやContent-Encoding、リダイレクトのLocationなど、再現には生のレスポンスヘッダーが必須 - メタデータを記録する — クロール日時、クローラーの識別子、ペイロードのハッシュ値といったアーカイブ固有の情報
- 大量のリソースを効率的に格納する — 1クロールで数百万〜数十億のリソースが生成されるため、ファイルシステムに散らばらせると扱いきれない
- 重複排除 — 同一のリソースが繰り返し取得された場合、2回目以降は参照だけ記録したい
WARCはこれらの要件を満たすために設計されている。
ARCフォーマットとの関係
WARCの直接の前身はInternet Archiveが1996年から使っていた ARC形式 にあたる。ARCはURL、日時、Content-Length、そしてレスポンス本体を連結しただけの素朴なフォーマットで、クローラーのログ出力に近い。ARCはメタデータの種類が限定的で、リクエストヘッダーや重複排除情報を表現できなかった。
WARCはARCを一般化し、任意の種類のレコードを同じファイルに混在させられるようにした点が最大の違い。ARCのレコードは実質的にレスポンスのみだが、WARCでは後述の通り複数のレコード型が定義されている。
WARCファイルの全体像
WARCファイルはレコードの連結として構成される。1つのファイルには、しばしば数千〜数百万のレコードが順に詰め込まれる。
実運用ではファイル全体をgzipで圧縮して.warc.gzとして配布するのが一般的。ここで重要なのは、gzipをレコード単位でかけるという点。ファイル全体を1つのgzipストリームにするのではなく、各レコードを個別に圧縮して連結する。gzip仕様はストリームの連結を許容しているため、通常のgzipデコーダで全体を展開できる。この仕組みのおかげで、ファイルの途中から特定のレコードだけを展開できる(ランダムアクセスが可能)。
CDXインデックス
WARCそのものには目次がない。特定URLのレコードを高速に引くには、別途CDXインデックスを作成する。CDXはURL、タイムスタンプ、WARCファイル名、そしてレコードのバイトオフセットと長さを記録したテキストファイルで、Wayback Machineのようなリプレイシステムはこのインデックスを引いて該当レコードにシークする。CDX自体はWARC規格の外にあるが、事実上の標準となっている。
レコードの構造
1つのレコードは3つの要素で構成される。
- バージョン行 —
WARC/1.1のようにフォーマットのバージョンを宣言する - WARCヘッダー — HTTPヘッダーに似た
Key: Value形式のメタデータ - コンテンツブロック — 実際のペイロード(HTTPリクエスト本体やレスポンス本体など)
ヘッダーとコンテンツブロックは空行(\r\n)で区切られ、各レコードの末尾には\r\n\r\nが付く。以下は最も単純なresponseレコードの例。
example.warc
ポイントは、レコードのContent-Typeがapplication/http;msgtype=responseとなっていること。つまりレコードのコンテンツブロックはHTTPメッセージそのものであり、ステータス行からレスポンスボディまで生の形で格納される。WARCはその上にもう1段ヘッダーを被せてメタデータを付けているだけ、と考えるとわかりやすい。
必須ヘッダー
仕様で全レコードに必須とされるヘッダーは以下の4つ。
| ヘッダー | 内容 |
|---|---|
WARC-Type | レコードの種類。warcinfo/response/requestなど |
WARC-Record-ID | レコードを一意に識別するURI。通常は<urn:uuid:...> |
WARC-Date | レコード作成日時。ISO 8601のUTC |
Content-Length | コンテンツブロックのバイト数 |
加えて、レコード型に応じて以下のようなヘッダーが使われる。
WARC-Target-URI— アーカイブ対象のURL(request/responseなど)WARC-Payload-Digest— ペイロード(HTTPボディ)のハッシュ値。重複排除の判定に使うWARC-Block-Digest— コンテンツブロック全体のハッシュ値WARC-Concurrent-To— 同じ取得イベントに属する別レコード(例えばresponseと対になるrequest)を指すWARC-Refers-To— 別のレコードを参照する(revisitで元のレコードを指す)
主要なレコード型
WARC/1.1では8種類のレコード型が定義されている。実務でよく目にするのは以下の通り。
warcinfo
WARCファイル自身のメタデータを記述するレコード。通常はファイルの先頭に1つ置く。コンテンツブロックはプレーンテキストで、クローラー名、バージョン、設定ファイルの内容などを自由形式で書く。
request / response
HTTPのリクエストとレスポンスをそのまま格納する。実際のクロールデータの大部分はこの2つで占められる。WARC-Concurrent-Toで対応するrequestとresponseを結びつける。
resource
HTTPレイヤーの情報が不要な、生のリソースだけを格納するレコード。例えばFTPから取得したファイルや、クローラーが生成した派生物(スクリーンショットなど)をresourceとして記録する。
revisit
重複排除のためのレコード。同じペイロードがすでにアーカイブされている場合、レスポンス本体を再度保存する代わりにrevisitを記録する。WARC-Refers-ToやWARC-Refers-To-Target-URIで元のレコードを指し、ペイロードのハッシュ値(WARC-Payload-Digest)で同一性を保証する。
大規模クロールでは同じ画像やCSSが何度も取得されるため、revisitの有無でアーカイブサイズが大きく変わる。Common Crawlの統計ではrevisitレコードが全体の相当部分を占めている。
metadata
関連付けたい補助情報を格納するレコード。例えば抽出したリンク一覧、文字コード検出結果、言語判定の結果など、クロール後の処理で生成した情報を元のレコードに紐づけて保存する用途で使う。
conversion / continuation
conversion— 元レコードを別の形式に変換した結果を格納する(例: HTMLからプレーンテキストへの変換結果)continuation— 大きなペイロードを複数のレコードに分割したい場合に使う。巨大ファイル向けだが、実運用ではあまり見ない
実例: wgetでWARCを作る
WARCを作る最も手軽な方法はwgetの--warc-fileオプションを使うこと。以下のコマンドはhttps://example.com/を取得し、example.warc.gzとして保存する。
オプションの意味は以下の通り。
--warc-file=NAME—NAME.warc.gzを出力する(拡張子は自動で付く)。gzip圧縮はデフォルトで有効。無効化したい場合は--no-warc-compressionを追加する--warc-cdx— 同時にCDXインデックスも作成する--delete-after— 取得したファイル本体は残さず、WARCだけを残す
実行するとexample.warc.gzとexample.cdxが生成される。未確認だが、Heritrixのような本格的なクローラーを使う場合も出力フォーマットはWARCなので、wgetで作ったファイルとそのまま同じツールで扱える。
実例: warcioで読む
Pythonのwarcioは、WARCの読み書きにもっとも広く使われているライブラリ。以下のスクリプトは先ほど生成したexample.warc.gzを読み、responseレコードだけを取り出してURLとステータスコードを表示する。
read_warc.py
インストールと実行は以下の通り。
出力例:
ArchiveIteratorはgzipストリームを透過的に扱い、レコード単位で反復する。各recordオブジェクトは2種類のヘッダーを持つ点に注意したい。
record.rec_headers— WARCレコードのヘッダー(WARC-Target-URIなど)record.http_headers— コンテンツブロック内のHTTPヘッダー(Content-Typeなど)
先の「レコードはHTTPメッセージをラップしている」という構造が、そのままAPIに反映されている。
ペイロードを取り出す
レスポンスボディを取得するにはrecord.content_stream()を使う。これはgzipなどContent-Encodingを自動でデコードしてくれる。
生のバイト列(デコード前)が欲しい場合はrecord.raw_streamを使う。
周辺ツールとエコシステム
WARCを扱うツールは用途別にいくつかの層に分かれる。
- クローラー — Heritrix (Internet Archive)、Browsertrix Crawler (Webrecorder、PlaywrightベースでJavaScriptレンダリングに対応)、wget
- 読み書きライブラリ — Pythonの
warcio、Javaのjwarc、Rustのwarcクレート - リプレイ(再生) — pywb — CDXインデックスを引いてWARCから該当レコードを返し、ブラウザ上で過去のWebサイトを閲覧できるようにする。Wayback Machineのオープンソース実装と位置付けられる
- 大規模データセット — Common Crawlは月次で数十億ページのクロールをWARC形式で公開している。S3上にファイルが置かれており、CDXインデックス経由で必要なレコードだけをダウンロードできる
実務でWARCに触れる場面としては、(1) 自分でクロールしてアーカイブを作る、(2) Common Crawlのような既存アーカイブを解析する、の2つが主。どちらもwarcioのような読み取りライブラリが出発点になる。
まとめ
WARCはWebアーカイブのための地味だが実用的なフォーマットで、HTTPメッセージにもう1段メタデータを被せて連結しただけと理解すると全体像がつかみやすい。レコード単位のgzipとCDXインデックスの組み合わせでランダムアクセスを実現している点、そしてrevisitによる重複排除で大規模クロールの現実的な運用を成り立たせている点が、フォーマットとしての巧さだと思う。
手元で試すにはwget --warc-fileとwarcioの組み合わせが最短ルート。大規模データが欲しければCommon Crawlを引いてみると、同じフォーマットでTB単位のアーカイブがどう構成されているかを直接観察できる。