Die meisten Scraping-Pipelines werfen die Seite weg, sobald sie daraus extrahiert haben. Der Parser liest das HTML, schreibt eine Zeile, und die Antwort ist weg. Das funktioniert, bis sich eines Tages herausstellt, dass der Extraktor eine Woche lang falsch lag, oder ein neues Feld für das letzte Quartal benötigt wird, oder jemand fragt, was auf der Seite tatsächlich stand. An diesem Punkt führt der einzige Weg zurück darüber, alles erneut abzurufen, und die Seiten haben sich in der Zwischenzeit verändert.
Die Rohantwort aufzubewahren löst alle drei Probleme, und es gibt dafür ein standardisiertes Format: WARC, das Web ARChive-Format, das von Webarchiven und von Common Crawl verwendet wird. Dieser Leitfaden behandelt, was gespeichert werden sollte, den Code dafür und was es bei einem realen Test gekostet hat.
Die wichtigsten Erkenntnisse
- Speichern Sie jede Antwort so, wie sie empfangen wurde, bevor Sie sie parsen. Die erneute Extraktion aus gespeicherten Antworten ist günstig; die nachträgliche Erfassung von Verlaufsdaten ist unmöglich.
- WARC ist der Standardcontainer: ein offenes Format mit Request-, Response- und Metadaten-Datensätzen, lesbar mit vielen vorhandenen Tools.
- Fordern Sie komprimierte Antworten an und speichern Sie sie weiterhin komprimiert. Bei unserem Test mit 40 Seiten hielt dies 20,6 MB HTML auf 3,4 MB bei der Übertragung und 3,5 MB auf der Festplatte.
- Das Wiedergeben aller 40 Seiten aus dem Archiv dauerte unter 0,2 Sekunden, gegenüber 6 bis 10 Sekunden für den Abruf.
- Zeichnen Sie auf, wie jede Datei erfasst wurde, einschließlich des Austrittslandes, damit archivierte Seiten später fair verglichen werden können.
Warum die Rohantwort aufbewahren
Drei Situationen treten in nahezu jedem langfristigen Erfassungsprojekt auf.
Extraktoren brechen lautlos. Eine Website ändert ihr Markup, der Parser läuft weiter, und ein Feld wird still leer oder falsch. Schema-Drift-Monitoring erkennt dies, aber erst, nachdem einige fehlerhafte Zeilen geschrieben wurden. Mit Rohantworten auf der Festplatte reparieren Sie den Extraktor und lassen ihn über die betroffenen Tage erneut laufen. Ohne sie sind diese Tage verloren.
Fragen ändern sich. Sechs Monate später benötigt jemand ein Feld, das niemand extrahiert hat: eine Versandnotiz, eine Verkäuferbewertung, ein Badge. Wenn die Seiten archiviert sind, ist das ein Batch-Job. Wenn nicht, beginnt es heute und hat keine Vorgeschichte.
Belege benötigen das Original. Wenn erfasste Daten eine Entscheidung, eine Beschwerde oder einen Streitfall stützen, trägt die Seite, so wie sie ausgeliefert wurde, mehr Gewicht als eine daraus abgeleitete Zeile, weshalb gerichtsfeste Beweise mit erhaltenen Erfassungen beginnen.
Es verändert auch, wie Sie mit der Extraktion arbeiten können. Einen neuen Ansatz auszuprobieren, etwa den modellbasierten Vergleich mit Selektoren, wird zu einem Offline-Experiment mit gespeicherten Seiten statt zu einem neuen Crawl.
Was WARC ist
WARC ist ein Containerformat für Webaufnahmen, das vom International Internet Preservation Consortium gepflegt und als ISO 28500 standardisiert wird. Eine WARC-Datei ist eine Abfolge von Datensätzen, jeder mit einem kurzen Textkopf gefolgt vom Inhalt. Die Datensatztypen, die für Scraping relevant sind, sind:
| Datensatztyp | Was er enthält |
|---|---|
warcinfo | Wie die Datei erstellt wurde: Software, Betreiber, Erfassungshinweise |
request | Die HTTP-Anfrage wie gesendet, einschließlich Headern wie Accept-Language |
response | Die HTTP-Antwort wie empfangen: Statuszeile, Header und Body |
metadata | Alles andere zu einer Erfassung, verknüpft über die Datensatz-ID |
revisit | Ein Verweis auf eine frühere identische Erfassung, verwendet, um Duplikate zu vermeiden |
Jeder Datensatz trägt einen Digest seines Inhalts, sodass eine Datei auf Beschädigung geprüft werden kann. Die Spezifikation empfiehlt, jeden Datensatz separat mit gzip zu komprimieren, was Dateien klein hält und gleichzeitig erlaubt, dass ein Reader direkt zu einem Datensatz springt. Common Crawl zum Beispiel verteilt seine Crawl-Daten als WARC-Dateien.
Der praktische Vorteil gegenüber einem selbstgebauten Format ist das Tooling. Dateien, die nach dem Standard geschrieben wurden, können indexiert, validiert, wiedergegeben und durchsucht werden, mit vorhandenen Open-Source-Tools, von Leuten, die Ihren Code nie gesehen haben.
Der Code
Die Python-Bibliothek warcio, vom Webrecorder-Projekt, liest und schreibt WARC. Die folgende Funktion ruft eine Seite mit requests ab, schreibt die Anfrage und die Antwort in eine offene WARC-Datei und behält den Body genau so, wie der Server ihn gesendet hat:
from io import BytesIO
from urllib.parse import urlsplit
import requests
from warcio.archiveiterator import ArchiveIterator
from warcio.statusandheaders import StatusAndHeaders
from warcio.warcwriter import WARCWriter
def fetch_and_archive(session, url, writer, **kwargs):
"""Fetch url and write the request and the response, byte for byte as received, to a WARC file."""
response = session.get(url, stream=True, **kwargs)
# Read the body undecoded, so gzip or br responses are stored exactly as the server sent them.
raw = response.raw.read(decode_content=False)
sent = response.request
path = urlsplit(sent.url)
request_line = f"{sent.method} {path.path or '/'}{'?' + path.query if path.query else ''} HTTP/1.1"
request_headers = [("Host", path.netloc)] + list(sent.headers.items())
request = writer.create_warc_record(
sent.url, "request", payload=BytesIO(b""),
http_headers=StatusAndHeaders(request_line, request_headers, is_http_request=True))
# urllib3 has already removed any chunked framing, so drop the header that describes it.
headers = [(k, v) for k, v in response.raw.headers.items() if k.lower() != "transfer-encoding"]
status = f"{response.status_code} {response.reason}"
record = writer.create_warc_record(
response.url, "response", payload=BytesIO(raw),
http_headers=StatusAndHeaders(status, headers, protocol="HTTP/1.1"))
request.rec_headers.add_header("WARC-Concurrent-To", record.rec_headers.get_header("WARC-Record-ID"))
writer.write_record(request)
writer.write_record(record)
return response.status_code, raw
def replay(path):
"""Yield (url, status, decoded body) for every response in a WARC file, with no network access."""
with open(path, "rb") as stream:
for record in ArchiveIterator(stream):
if record.rec_type == "response":
status = int(record.http_headers.get_statuscode())
yield record.rec_headers.get_header("WARC-Target-URI"), status, record.content_stream().read()
Und die Verwendung über einen Proxy, mit einem warcinfo-Datensatz, der angibt, woher die Seiten erfasst wurden:
import os
import requests
from warcio.warcwriter import WARCWriter
from archive import fetch_and_archive, replay
proxy = f"http://{os.environ['SHIFTER_PROXY_USER']}-country-de:{os.environ['SHIFTER_PROXY_PASS']}@p.shifter.io:443"
session = requests.Session()
session.proxies = {"http": proxy, "https": proxy}
session.headers["Accept-Encoding"] = "gzip"
# Identify your collector; some sites refuse the library's default User-Agent.
session.headers["User-Agent"] = "ExampleArchiver/1.0 (+https://example.com/bot)"
urls = ["https://en.wikipedia.org/wiki/Web_archiving", "https://en.wikipedia.org/wiki/Web_crawler"]
with open("crawl-2026-10-01.warc.gz", "wb") as output:
writer = WARCWriter(output, gzip=True)
# One warcinfo record per file says how and from where the pages were collected.
writer.write_record(writer.create_warcinfo_record(
"crawl-2026-10-01.warc.gz", {"software": "warcio", "description": "exit country: de"}))
for url in urls:
fetch_and_archive(session, url, writer, timeout=30)
# Months later, with no network access: re-run a new extractor over the same bytes.
for url, status, body in replay("crawl-2026-10-01.warc.gz"):
print(status, url, len(body))
Drei Details in diesem Code ergaben sich aus dem Testen.
- Den Body undekodiert lesen. requests dekomprimiert Antworten normalerweise automatisch für Sie. Das Lesen des rohen Streams behält die Bytes so, wie sie gesendet wurden, was sowohl originalgetreuer als auch deutlich kleiner ist. warcio dekomprimiert sie bei der Wiedergabe erneut.
- Den Chunked-Header entfernen. Ein Viertel unserer Antworten kam mit Chunked-Transfer-Encoding an, das die HTTP-Bibliothek bereits aufgelöst hatte. Den Header mit dem aufgelösten Body zu speichern, hinterlässt einen Datensatz, der sich selbst falsch beschreibt. warcio kam damit zufällig zurecht; andere Reader möglicherweise nicht.
- Einen echten User-Agent senden. Unser erster Durchlauf des Beispiels wurde mit HTTP 403 abgelehnt, weil die Website den Standard-User-Agent der HTTP-Bibliothek ablehnt. Identifizieren Sie Ihren Collector ehrlich.
Noch eine Falle, gefunden im Quellcode der Bibliothek statt beim Testen: Wenn Sie Brotli-komprimierte Antworten (br) akzeptieren, installieren Sie das brotli-Paket. Ohne es kann warcio diese Bodies bei der Wiedergabe nicht dekodieren und gibt die weiterhin komprimierten Bytes zurück, ohne einen Fehler auszulösen.
Was es gekostet hat
Wir haben 40 englische Wikipedia-Artikel am 1. Oktober 2026 über einen residentiellen Exit in Deutschland archiviert, zweimal: einmal mit Annahme von gzip-komprimierten Antworten, und einmal mit Anforderung unkomprimierter Antworten.
| Komprimierte Antworten | Unkomprimierte Antworten | |
|---|---|---|
| Seiten | 40 | 40 |
| Übertragen | 3,43 MB | 20,6 MB |
| HTML nach Dekodierung | 20,6 MB | 20,6 MB |
| WARC-Datei auf Festplatte | 3,54 MB | 3,51 MB |
| Zeit zum Abrufen | 6 bis 10 s | 9 bis 12 s |
| Zeit zur Wiedergabe aller 40 | 0,19 s | 0,18 s |
Jede wiedergegebene Seite war byteidentisch mit der abgerufenen, geprüft per SHA-256-Hash, und warcio check validierte die Digests aller 81 Datensätze in der Datei.
Zwei Dinge stechen hervor. Erstens: Speicherung ist günstig: Das Archiv war etwa 3% größer als die übertragenen komprimierten Bytes, wobei der Unterschied aus den Request-Datensätzen und Headern stammt. Zweitens: Die Speicherung war in beiden Fällen gleich, da die WARC-Datei ohnehin komprimiert wird; was sich änderte, war die Bandbreite. Die Anforderung unkomprimierter Antworten kostete sechsmal so viel Übertragung für ein identisches Archiv. Bei nach Bandbreite abgerechneter Erfassung ist das der Unterschied, der auf der Rechnung erscheint, wie die Senkung von Proxy-Bandbreitenkosten ausführlicher erklärt.
Praktische Regeln
- Archivieren Sie vor dem Parsen. Schreiben Sie zuerst den WARC-Datensatz, dann extrahieren Sie. Wenn der Extraktor abstürzt, bleibt die Seite erhalten.
- Dateien nach Größe oder Zeit rotieren. Die WARC-Spezifikation empfiehlt 1 GB als praktische Zielgröße pro Datei. Benennen Sie Dateien mit Datum und Sammlung, und hängen Sie nie an eine Datei an, die ein anderer Prozess gerade schreibt.
- Den Beobachtungsstandpunkt aufzeichnen. Eine Seite, die aus Deutschland abgerufen wurde, und eine, die aus den Vereinigten Staaten abgerufen wurde, können sich in Sprache, Preis und Inhalt unterscheiden. Vermerken Sie das Austrittsland und die Erfassungseinstellungen im
warcinfo-Datensatz, wie es in dem Argument für die Aufzeichnung, wo Daten beobachtet wurden dargelegt wird. - Indexieren Sie, was Sie behalten. Ein kleiner Index aus URL, Datum, Datei und Offset erlaubt es Ihnen, eine einzelne Erfassung aus Terabytes herauszuziehen, ohne alles zu lesen. Das Kommandozeilen-Tool
indexvon warcio erzeugt einen solchen. - Legen Sie eine Aufbewahrungsrichtlinie fest. Rohseiten können personenbezogene Daten enthalten. Entscheiden Sie, wie lange Sie sie behalten, beschränken Sie, wer sie lesen darf, und löschen Sie nach Plan.
- Duplikate bewusst überspringen. Wenn sich eine Seite seit der letzten Erfassung nicht geändert hat, kann ein
revisit-Datensatz auf die frühere Kopie verweisen, statt sie erneut zu speichern, was gut mit Änderungserkennung zusammenpasst.
Fazit
Das Parsen ist der Teil einer Scraping-Pipeline, der am wahrscheinlichsten falsch ist und sich am wahrscheinlichsten ändert, daher sollte es nicht die einzige Aufzeichnung dessen sein, was erfasst wurde. Speichern Sie die Rohantwort in WARC, bevor Sie sie parsen, fordern Sie komprimierte Antworten an und behalten Sie sie komprimiert, und notieren Sie, woher jede Datei erfasst wurde.
Bei unserem Test kostete das etwa 3,5 MB Festplattenspeicher für 40 Seiten und verwandelte einen Crawl, der Sekunden dauerte, in eine Wiedergabe, die einen Bruchteil davon dauerte. Wenn beim nächsten Mal ein Extraktor ausfällt oder eine neue Frage zum letzten Monat auftaucht, lautet die Antwort Batch-Job statt verlorener Woche.
Quellen und Referenzen
- International Internet Preservation Consortium, The WARC Format 1.1.
- Webrecorder, warcio, Version 1.8.1, verwendet für den obigen Code und Test.
- Common Crawl, Get started, zu seiner Verwendung des WARC-Formats.
- Testarchiv von 40 Seiten, erfasst von Shifter am 1. Oktober 2026, mit dem obigen Code.