Les pipelines de scraping jettent le plus souvent la page dès qu’ils en ont extrait les données. Le parseur lit le HTML, écrit une ligne, et la réponse disparaît. Cela fonctionne jusqu’au jour où l’on découvre que l’extracteur s’est trompé pendant une semaine, où un nouveau champ est nécessaire pour le trimestre dernier, ou où quelqu’un demande ce que la page disait réellement. À ce moment-là, le seul moyen de revenir en arrière est de tout récupérer à nouveau, et les pages ont changé entre-temps.
Conserver la réponse brute résout ces trois problèmes, et il existe un format standard conçu pour cela : WARC, le format Web ARChive utilisé par les archives web et par Common Crawl. Ce guide couvre ce qu’il faut stocker, le code pour le stocker, et ce que cela a coûté lors d’un test réel.
Points clés
- Stockez chaque réponse telle qu’elle a été reçue, avant tout traitement. Réextraire à partir de réponses stockées est peu coûteux ; recollecter l’historique est impossible.
- WARC est le conteneur standard : un format ouvert avec des enregistrements request, response et metadata, lisible par de nombreux outils existants.
- Demandez des réponses compressées et stockez-les telles quelles, encore compressées. Dans notre test sur 40 pages, cela a permis de réduire 20.6 MB de HTML à 3.4 MB sur le réseau et 3.5 MB sur disque.
- Rejouer les 40 pages depuis l’archive a pris moins de 0.2 seconde, contre 6 à 10 secondes pour les récupérer.
- Enregistrez comment chaque fichier a été collecté, y compris le pays de sortie, afin que les pages archivées puissent être comparées équitablement par la suite.
Pourquoi conserver la réponse brute
Trois situations se présentent dans presque tout projet de collecte de longue durée.
Les extracteurs tombent en panne silencieusement. Un site change son balisage, le parseur continue de tourner, et un champ devient silencieusement vide ou erroné. La surveillance de la dérive de schéma le détecte, mais seulement après que certaines lignes erronées ont été écrites. Avec les réponses brutes sur disque, vous corrigez l’extracteur et le relancez sur les jours concernés. Sans elles, ces jours sont perdus.
Les questions changent. Six mois plus tard, quelqu’un a besoin d’un champ que personne n’a extrait : une note de livraison, une évaluation de vendeur, un badge. Si les pages sont archivées, c’est un travail par lots. Sinon, cela commence aujourd’hui et n’a pas d’historique.
Les preuves ont besoin de l’original. Lorsque des données collectées viennent étayer une décision, une plainte ou un litige, la page telle qu’elle a été servie a plus de poids qu’une ligne qui en est dérivée, ce qui explique pourquoi les preuves recevables en justice commencent par des captures préservées.
Cela change aussi la façon dont on peut travailler avec l’extraction. Essayer une nouvelle approche, comme l’extraction basée sur un modèle comparée aux sélecteurs, devient une expérience hors ligne sur des pages stockées plutôt qu’une nouvelle collecte.
Qu’est-ce que WARC
WARC est un format de conteneur pour les captures web, maintenu par l’International Internet Preservation Consortium et normalisé sous la référence ISO 28500. Un fichier WARC est une séquence d’enregistrements, chacun comportant un court bloc d’en-têtes textuels suivi du contenu. Les types d’enregistrements qui comptent pour le scraping sont :
| Type d’enregistrement | Ce qu’il contient |
|---|---|
warcinfo | Comment le fichier a été créé : logiciel, opérateur, notes de collecte |
request | La requête HTTP telle qu’envoyée, y compris des en-têtes comme Accept-Language |
response | La réponse HTTP telle que reçue : ligne de statut, en-têtes et corps |
metadata | Tout autre élément relatif à une capture, lié à elle par son ID d’enregistrement |
revisit | Un pointeur vers une capture identique antérieure, utilisé pour éviter de stocker des doublons |
Chaque enregistrement comporte un condensé de son contenu, ce qui permet de vérifier qu’un fichier n’est pas corrompu. La spécification recommande de compresser chaque enregistrement séparément avec gzip, ce qui garde les fichiers petits tout en permettant à un lecteur d’accéder directement à un enregistrement donné. Common Crawl, par exemple, distribue ses données de collecte sous forme de fichiers WARC.
L’avantage pratique par rapport à un format maison, c’est l’outillage. Les fichiers écrits selon le standard peuvent être indexés, validés, rejoués et recherchés avec des outils open source existants, par des personnes qui n’ont jamais vu votre code.
Le code
La bibliothèque Python warcio, issue du projet Webrecorder, lit et écrit des fichiers WARC. La fonction ci-dessous récupère une page avec requests, écrit la requête et la réponse dans un fichier WARC ouvert, et conserve le corps exactement tel que le serveur l’a envoyé :
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):
"""Récupère url et écrit la requête et la réponse, octet pour octet telles que reçues, dans un fichier WARC."""
response = session.get(url, stream=True, **kwargs)
# Lit le corps non décodé, afin que les réponses gzip ou br soient stockées exactement comme le serveur les a envoyées.
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 a déjà supprimé tout découpage en chunks, donc on retire l'en-tête qui le décrit.
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):
"""Produit (url, status, corps décodé) pour chaque réponse d'un fichier WARC, sans aucun accès réseau."""
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()
Et en l’utilisant via un proxy, avec un enregistrement warcinfo qui indique d’où les pages ont été collectées :
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"
# Identifiez votre collecteur ; certains sites refusent le User-Agent par défaut de la bibliothèque.
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)
# Un enregistrement warcinfo par fichier indique comment et d'où les pages ont été collectées.
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)
# Des mois plus tard, sans accès réseau : relancez un nouvel extracteur sur les mêmes octets.
for url, status, body in replay("crawl-2026-10-01.warc.gz"):
print(status, url, len(body))
Trois détails dans ce code sont issus des tests que nous avons menés.
- Lire le corps non décodé. requests décompresse normalement les réponses pour vous. Lire le flux brut conserve les octets tels qu’envoyés, ce qui est à la fois plus fidèle et bien plus compact. warcio les décompresse à nouveau lors de la relecture.
- Retirer l’en-tête chunked. Un quart de nos réponses sont arrivées avec un encodage de transfert chunked, que la bibliothèque HTTP avait déjà déballé. Conserver l’en-tête avec le corps déballé laisse un enregistrement qui se décrit lui-même de façon erronée. warcio s’en est accommodé ; d’autres lecteurs pourraient ne pas le faire.
- Envoyer un véritable User-Agent. Notre premier essai de l’exemple a été refusé avec un HTTP 403, car le site rejette le User-Agent par défaut de la bibliothèque HTTP. Identifiez honnêtement votre collecteur.
Un dernier piège, découvert dans le code source de la bibliothèque plutôt que lors des tests : si vous acceptez les réponses compressées en Brotli (br), installez le paquet brotli. Sans lui, warcio ne peut pas décoder ces corps lors de la relecture et renvoie les octets encore compressés sans lever d’erreur.
Ce que cela a coûté
Nous avons archivé 40 articles de Wikipédia en anglais le 1 October 2026 via une sortie résidentielle en Allemagne, à deux reprises : une fois en acceptant des réponses compressées en gzip, et une fois en demandant des réponses non compressées.
| Réponses compressées | Réponses non compressées | |
|---|---|---|
| Pages | 40 | 40 |
| Transféré | 3.43 MB | 20.6 MB |
| HTML une fois décodé | 20.6 MB | 20.6 MB |
| Fichier WARC sur disque | 3.54 MB | 3.51 MB |
| Temps de récupération | 6 à 10 s | 9 à 12 s |
| Temps pour rejouer les 40 | 0.19 s | 0.18 s |
Chaque page rejouée était identique octet pour octet à ce qui avait été récupéré, vérifié par un hash SHA-256, et warcio check a validé les digests des 81 enregistrements du fichier.
Deux éléments ressortent. D’abord, le stockage est peu coûteux : l’archive était environ 3% plus grande que les octets compressés transférés, la différence provenant des enregistrements de requête et des en-têtes. Ensuite, le stockage était identique dans les deux cas, car le fichier WARC est compressé quoi qu’il arrive ; ce qui changeait, c’était la bande passante. Demander des réponses non compressées a coûté six fois plus de transfert pour une archive identique. Pour une collecte facturée à la bande passante, c’est la différence qui apparaît sur la facture, comme l’explique plus en détail la réduction des coûts de bande passante des proxys.
Règles pratiques
- Archivez avant d’analyser. Écrivez d’abord l’enregistrement WARC, puis extrayez. Si l’extracteur plante, la page est tout de même conservée.
- Faites tourner les fichiers par taille ou par durée. La spécification WARC recommande 1 GB comme taille cible pratique par fichier. Nommez les fichiers avec la date et la collecte, et n’ajoutez jamais à un fichier qu’un autre processus est en train d’écrire.
- Enregistrez le point d’observation. Une page récupérée depuis l’Allemagne et une récupérée depuis les États-Unis peuvent différer en langue, en prix et en contenu. Mettez le pays de sortie et les paramètres de collecte dans l’enregistrement
warcinfo, comme le plaide l’argumentaire en faveur de l’enregistrement du lieu d’observation des données. - Indexez ce que vous conservez. Un petit index d’URL, de date, de fichier et d’offset permet d’extraire une capture parmi des téraoctets sans tout lire. L’outil en ligne de commande
indexde warcio en produit un. - Définissez une politique de rétention. Les pages brutes peuvent contenir des données personnelles. Décidez combien de temps vous les conservez, restreignez qui peut les lire, et supprimez-les selon un calendrier.
- Évitez délibérément les doublons. Lorsqu’une page n’a pas changé depuis la dernière capture, un enregistrement
revisitpeut pointer vers la copie antérieure plutôt que de la stocker à nouveau, ce qui s’accorde bien avec la détection de changement.
En résumé
L’analyse est la partie d’un pipeline de scraping la plus susceptible d’être erronée et la plus susceptible de changer, elle ne devrait donc pas être le seul enregistrement de ce qui a été collecté. Stockez la réponse brute en WARC avant de l’analyser, demandez des réponses compressées et conservez-les compressées, et notez d’où chaque fichier a été collecté.
Dans notre test, cela a coûté environ 3.5 MB de disque pour 40 pages et a transformé une collecte qui prenait des secondes en une relecture qui prenait une fraction de seconde. La prochaine fois qu’un extracteur tombera en panne, ou qu’une nouvelle question se posera sur le mois dernier, la réponse sera un travail par lots plutôt qu’une semaine perdue.
Sources et références
- International Internet Preservation Consortium, The WARC Format 1.1.
- Webrecorder, warcio, version 1.8.1, utilisé pour le code et le test ci-dessus.
- Common Crawl, Pour commencer, à propos de son utilisation du format WARC.
- Archive de test de 40 pages collectées par Shifter le 1 October 2026, avec le code ci-dessus.