Scraping

Conserva la respuesta en bruto: archivado de páginas extraídas en WARC para su repetición

Los parsers mejoran y los extractores se rompen, pero una página que no has conservado no se puede volver a analizar. Cómo almacenar respuestas en bruto en WARC, con código probado y tamaños reales.

Matt Brown

Matt Brown

1 de octubre de 2026 · 10 min de lectura

Casi todos los pipelines de scraping desechan la página en cuanto han extraído algo de ella. El parser lee el HTML, escribe una fila, y la respuesta desaparece. Eso funciona hasta el día en que resulta que el extractor llevaba una semana fallando, o se necesita un nuevo campo para el trimestre pasado, o alguien pregunta qué decía realmente la página. Llegado ese punto, la única forma de volver atrás es descargarlo todo de nuevo, y mientras tanto las páginas han cambiado.

Conservar la respuesta en bruto resuelve los tres problemas, y existe un formato estándar creado para ello: WARC, el formato Web ARChive usado por los archivos web y por Common Crawl. Esta guía cubre qué almacenar, el código para almacenarlo y lo que costó en una prueba real.

Conclusiones clave

  • Almacena cada respuesta tal como se recibió, antes de analizarla. Volver a extraer datos de respuestas almacenadas es barato; recuperar el historial es imposible.
  • WARC es el contenedor estándar: un formato abierto con registros de request, response y metadata, legible por muchas herramientas ya existentes.
  • Solicita respuestas comprimidas y almacénalas todavía comprimidas. En nuestra prueba con 40 páginas, eso redujo 20,6 MB de HTML a 3,4 MB en tránsito y 3,5 MB en disco.
  • Reproducir las 40 páginas desde el archivo tardó menos de 0,2 segundos, frente a los 6 a 10 segundos que llevó descargarlas.
  • Registra cómo se recopiló cada archivo, incluyendo el país de salida, para poder comparar más adelante las páginas archivadas de forma justa.

Por qué conservar la respuesta en bruto

Tres situaciones aparecen en casi todo proyecto de recopilación de larga duración.

Los extractores fallan en silencio. Un sitio cambia su marcado, el parser sigue ejecutándose, y un campo se queda vacío o incorrecto sin que nadie se dé cuenta. La monitorización de la deriva del esquema lo detecta, pero solo después de que se hayan escrito algunas filas incorrectas. Con las respuestas en bruto guardadas en disco, arreglas el extractor y lo vuelves a ejecutar sobre los días afectados. Sin ellas, esos días se pierden.

Las preguntas cambian. Seis meses después, alguien necesita un campo que nadie extrajo: una nota de envío, la valoración de un vendedor, una insignia. Si las páginas están archivadas, es un trabajo por lotes. Si no, empieza hoy y no tiene historial.

La evidencia necesita el original. Cuando los datos recopilados respaldan una decisión, una reclamación o una disputa, la página tal como se sirvió tiene más peso que una fila derivada de ella, razón por la cual la evidencia lista para un tribunal empieza con capturas preservadas.

También cambia cómo se puede trabajar con la extracción. Probar un nuevo enfoque, como la extracción basada en modelos comparada con selectores, se convierte en un experimento sin conexión sobre páginas almacenadas en lugar de un nuevo rastreo.

Qué es WARC

WARC es un formato contenedor para capturas web, mantenido por el International Internet Preservation Consortium y estandarizado como ISO 28500. Un archivo WARC es una secuencia de registros, cada uno con un breve bloque de cabeceras de texto seguido del contenido. Los tipos de registro relevantes para el scraping son:

Tipo de registroQué contiene
warcinfoCómo se creó el archivo: software, operador, notas de recopilación
requestLa solicitud HTTP tal como se envió, incluyendo cabeceras como Accept-Language
responseLa respuesta HTTP tal como se recibió: línea de estado, cabeceras y cuerpo
metadataCualquier otra información sobre una captura, enlazada a ella por su ID de registro
revisitUn puntero a una captura idéntica anterior, usado para evitar almacenar duplicados

Cada registro lleva un resumen (digest) de su contenido, de modo que se puede comprobar si un archivo está corrupto. La especificación recomienda comprimir cada registro por separado con gzip, lo que mantiene los archivos pequeños a la vez que permite a un lector saltar directamente a un registro concreto. Common Crawl, por ejemplo, distribuye sus datos de rastreo como archivos WARC.

La ventaja práctica frente a un formato casero son las herramientas. Los archivos escritos conforme al estándar se pueden indexar, validar, reproducir y buscar con herramientas de código abierto ya existentes, por personas que nunca han visto tu código.

El código

La librería de Python warcio, del proyecto Webrecorder, lee y escribe WARC. La función siguiente descarga una página con requests, escribe la solicitud y la respuesta en un archivo WARC abierto, y conserva el cuerpo exactamente como lo envió el servidor:

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()

Y usándolo a través de un proxy, con un registro warcinfo que indica de dónde se recopilaron las páginas:

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))

Tres detalles de ese código surgieron al probarlo.

  • Leer el cuerpo sin descodificar. requests normalmente descomprime las respuestas por ti. Leer el flujo en bruto conserva los bytes tal como se enviaron, lo cual es a la vez más fiel y mucho más pequeño. warcio los vuelve a descomprimir al reproducirlos.
  • Descartar la cabecera de chunked. Una cuarta parte de nuestras respuestas llegaron con codificación de transferencia chunked, que la librería HTTP ya había desempaquetado. Almacenar la cabecera junto con el cuerpo ya desempaquetado deja un registro que se describe a sí mismo incorrectamente. warcio lo gestionó sin problemas; otros lectores podrían no hacerlo.
  • Enviar un User-Agent real. Nuestra primera ejecución del ejemplo fue rechazada con HTTP 403, porque el sitio rechaza el User-Agent predeterminado de la librería HTTP. Identifica tu recolector con honestidad.

Un detalle más, encontrado en el código fuente de la librería y no en las pruebas: si aceptas respuestas comprimidas con Brotli (br), instala el paquete brotli. Sin él, warcio no puede descodificar esos cuerpos al reproducirlos y devuelve los bytes todavía comprimidos sin lanzar ningún error.

Lo que costó

Archivamos 40 artículos de la Wikipedia en inglés el 1 de octubre de 2026 a través de una salida residencial en Alemania, dos veces: una aceptando respuestas comprimidas con gzip, y otra solicitando respuestas sin comprimir.

Respuestas comprimidasRespuestas sin comprimir
Páginas4040
Transferido3,43 MB20,6 MB
HTML una vez descodificado20,6 MB20,6 MB
Archivo WARC en disco3,54 MB3,51 MB
Tiempo de descarga6 a 10 s9 a 12 s
Tiempo de reproducir las 400,19 s0,18 s

Cada página reproducida fue idéntica byte a byte a la descargada, comprobado mediante hash SHA-256, y warcio check validó los resúmenes de los 81 registros del archivo.

Destacan dos cosas. Primero, el almacenamiento es barato: el archivo fue aproximadamente un 3% más grande que los bytes comprimidos transferidos, siendo la diferencia los registros de request y las cabeceras. Segundo, el almacenamiento fue el mismo en ambos casos, porque el archivo WARC se comprime de todas formas; lo que cambió fue el ancho de banda. Solicitar respuestas sin comprimir costó seis veces más tráfico para un archivo idéntico. En una recopilación facturada por ancho de banda, esa es la diferencia que aparece en la factura, como explica con más detalle cómo reducir los costes de ancho de banda de proxy.

Reglas prácticas

  • Archiva antes de analizar. Escribe primero el registro WARC y luego extrae. Si el extractor falla, la página sigue conservada.
  • Rota los archivos por tamaño o por tiempo. La especificación WARC recomienda 1 GB como tamaño objetivo práctico por archivo. Nombra los archivos con la fecha y la colección, y nunca añadas datos a un archivo que otro proceso está escribiendo.
  • Registra el punto de vista. Una página descargada desde Alemania y otra descargada desde Estados Unidos pueden diferir en idioma, precio y contenido. Incluye el país de salida y la configuración de recopilación en el registro warcinfo, como se argumenta en el caso a favor de registrar dónde se observaron los datos.
  • Indexa lo que conservas. Un pequeño índice de URL, fecha, archivo y desplazamiento te permite extraer una sola captura entre terabytes sin tener que leerlo todo. La herramienta de línea de comandos index de warcio genera uno.
  • Establece una política de retención. Las páginas en bruto pueden contener datos personales. Decide cuánto tiempo las conservas, restringe quién puede leerlas y elimínalas según un calendario.
  • Omite duplicados de forma deliberada. Cuando una página no ha cambiado desde la última captura, un registro revisit puede apuntar a la copia anterior en lugar de almacenarla de nuevo, lo cual combina bien con la detección de cambios.

La conclusión

El análisis (parsing) es la parte de un pipeline de scraping con más probabilidades de estar equivocada y más probabilidades de cambiar, así que no debería ser el único registro de lo que se recopiló. Almacena la respuesta en bruto en WARC antes de analizarla, solicita respuestas comprimidas y consérvalas comprimidas, y anota de dónde se recopiló cada archivo.

En nuestra prueba, eso costó aproximadamente 3,5 MB de disco para 40 páginas y convirtió un rastreo que tardaba segundos en una reproducción que tardaba una fracción de uno. La próxima vez que un extractor falle, o llegue una nueva pregunta sobre el mes pasado, la respuesta será un trabajo por lotes en lugar de una semana perdida.

Fuentes y referencias

  • International Internet Preservation Consortium, The WARC Format 1.1.
  • Webrecorder, warcio, versión 1.8.1, usada en el código y la prueba anteriores.
  • Common Crawl, Get started, sobre su uso del formato WARC.
  • Archivo de prueba de 40 páginas recopilado por Shifter el 1 de octubre de 2026, usando el código anterior.

¿Listo para empezar?

Prueba los proxies residenciales de Shifter, más de 205M IPs, más de 195 países, desde 0,75 $/GB.

Comenzar