Extração de dados

Mantenha a Resposta Bruta: Arquivando Páginas Raspadas em WARC para Replay

Parsers melhoram e extratores quebram, mas uma página que você não guardou não pode ser analisada novamente. Como armazenar respostas brutas em WARC, com código testado e tamanhos reais.

Matt Brown

Matt Brown

1 de outubro de 2026 · 10 min de leitura

Na maioria dos pipelines de scraping, a página é descartada assim que os dados são extraídos dela. O parser lê o HTML, grava uma linha, e a resposta desaparece. Isso funciona até o dia em que se descobre que o extrator estava errado havia uma semana, ou que um novo campo é necessário para o trimestre passado, ou que alguém pergunta o que a página realmente dizia. Nesse ponto, o único caminho de volta é buscar tudo de novo, e as páginas já mudaram nesse meio tempo.

Guardar a resposta bruta resolve os três problemas, e existe um formato padrão construído para isso: WARC, o Web ARChive format usado por arquivos da web e pelo Common Crawl. Este guia cobre o que armazenar, o código para armazenar e o custo em um teste real.

Principais conclusões

  • Armazene cada resposta como recebida, antes de fazer o parsing. Reextrair de respostas armazenadas é barato; recuperar o histórico é impossível.
  • WARC é o container padrão: um formato aberto com registros de request, response e metadata, legível por muitas ferramentas já existentes.
  • Solicite respostas compactadas e as armazene ainda compactadas. No nosso teste com 40 páginas, isso manteve 20,6 MB de HTML em apenas 3,4 MB em trânsito e 3,5 MB em disco.
  • Reproduzir todas as 40 páginas a partir do arquivo levou menos de 0,2 segundos, contra 6 a 10 segundos para buscá-las.
  • Registre como cada arquivo foi coletado, incluindo o país de saída, para que páginas arquivadas possam ser comparadas de forma justa mais tarde.

Por que manter a resposta bruta

Três situações surgem em quase todo projeto de coleta de longa duração.

Extratores quebram silenciosamente. Um site muda sua marcação, o parser continua rodando, e um campo silenciosamente fica vazio ou errado. O monitoramento de schema drift detecta isso, mas só depois que algumas linhas ruins já foram gravadas. Com as respostas brutas em disco, você corrige o extrator e o executa novamente sobre os dias afetados. Sem elas, esses dias estão perdidos.

As perguntas mudam. Seis meses depois, alguém precisa de um campo que ninguém extraiu: uma observação de frete, uma avaliação de vendedor, um selo. Se as páginas estão arquivadas, é um job em lote. Se não, começa hoje e não tem histórico.

Evidências precisam do original. Quando os dados coletados sustentam uma decisão, uma reclamação ou uma disputa, a página como foi servida tem mais peso do que uma linha derivada dela, o que é a razão pela qual evidências prontas para uso judicial começam com capturas preservadas.

Isso também muda como você pode trabalhar com extração. Experimentar uma nova abordagem, como a extração baseada em modelo comparada com seletores, torna-se um experimento offline sobre páginas armazenadas, em vez de um novo crawl.

O que é o WARC

WARC é um formato de container para capturas web, mantido pelo International Internet Preservation Consortium e padronizado como ISO 28500. Um arquivo WARC é uma sequência de registros, cada um com um pequeno bloco de cabeçalhos de texto seguido de conteúdo. Os tipos de registro que importam para scraping são:

Tipo de registroO que contém
warcinfoComo o arquivo foi feito: software, operador, notas de coleta
requestA requisição HTTP como enviada, incluindo cabeçalhos como Accept-Language
responseA resposta HTTP como recebida: linha de status, cabeçalhos e corpo
metadataQualquer outra informação sobre uma captura, vinculada a ela pelo ID do registro
revisitUm ponteiro para uma captura idêntica anterior, usado para evitar armazenar duplicatas

Cada registro carrega um digest de seu conteúdo, de modo que um arquivo pode ser verificado quanto a corrupção. A especificação recomenda compactar cada registro separadamente com gzip, o que mantém os arquivos pequenos e ainda permite que um leitor pule diretamente para um registro. O Common Crawl, por exemplo, distribui seus dados de crawl como arquivos WARC.

A vantagem prática sobre um formato caseiro é o ferramental. Arquivos escritos conforme o padrão podem ser indexados, validados, reproduzidos e pesquisados com ferramentas open-source já existentes, por pessoas que nunca viram seu código.

O código

A biblioteca Python warcio, do projeto Webrecorder, lê e escreve WARC. A função abaixo busca uma página com requests, grava a requisição e a resposta em um arquivo WARC aberto, e mantém o corpo exatamente como o servidor o enviou:

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

E usando-a através de um proxy, com um registro warcinfo que indica de onde as páginas foram coletadas:

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

Três detalhes nesse código surgiram ao testá-lo.

  • Leia o corpo sem decodificar. O requests normalmente descompacta as respostas para você. Ler o stream bruto mantém os bytes como foram enviados, o que é ao mesmo tempo mais fiel e muito menor. O warcio os descompacta novamente na reprodução.
  • Descarte o cabeçalho de chunked. Um quarto das nossas respostas chegou com transfer encoding chunked, que a biblioteca HTTP já havia desempacotado. Armazenar o cabeçalho com o corpo já desempacotado deixa um registro que se descreve de forma errada. O warcio por acaso lidou bem com isso; outros leitores podem não lidar.
  • Envie um User-Agent real. Nossa primeira execução do exemplo foi recusada com HTTP 403, porque o site rejeita o User-Agent padrão da biblioteca HTTP. Identifique seu coletor com honestidade.

Mais um detalhe encontrado no código-fonte da biblioteca, e não durante os testes: se você aceita respostas compactadas com Brotli (br), instale o pacote brotli. Sem ele, o warcio não consegue decodificar esses corpos na reprodução e retorna os bytes ainda compactados sem gerar erro.

Qual foi o custo

Arquivamos 40 artigos da Wikipédia em inglês em 1 de outubro de 2026 através de uma saída residencial na Alemanha, duas vezes: uma aceitando respostas compactadas com gzip, e outra solicitando respostas não compactadas.

Respostas compactadasRespostas não compactadas
Páginas4040
Transferido3,43 MB20,6 MB
HTML após decodificação20,6 MB20,6 MB
Arquivo WARC em disco3,54 MB3,51 MB
Tempo para buscar6 a 10 s9 a 12 s
Tempo para reproduzir todas as 400,19 s0,18 s

Cada página reproduzida foi byte a byte idêntica à que foi buscada, verificado por hash SHA-256, e o warcio check validou os digests dos 81 registros do arquivo.

Duas coisas se destacam. Primeiro, o armazenamento é barato: o arquivo ficou cerca de 3% maior do que os bytes compactados transferidos, sendo a diferença os registros de request e os cabeçalhos. Segundo, o armazenamento foi o mesmo nos dois casos, porque o arquivo WARC é compactado de qualquer forma; o que mudou foi a largura de banda. Solicitar respostas não compactadas custou seis vezes mais transferência para um arquivo idêntico. Em coletas cobradas por largura de banda, essa é a diferença que aparece na fatura, como explica em mais detalhes como reduzir custos de largura de banda de proxy.

Regras práticas

  • Arquive antes de fazer o parsing. Grave o registro WARC primeiro, depois extraia. Se o extrator travar, a página ainda é mantida.
  • Gire os arquivos por tamanho ou tempo. A especificação WARC recomenda 1 GB como tamanho-alvo prático por arquivo. Nomeie os arquivos com a data e a coleta, e nunca anexe a um arquivo que outro processo esteja escrevendo.
  • Registre o ponto de vista. Uma página buscada da Alemanha e outra buscada dos Estados Unidos podem diferir em idioma, preço e conteúdo. Coloque o país de saída e as configurações de coleta no registro warcinfo, como argumentado em o caso para registrar onde os dados foram observados.
  • Indexe o que você guarda. Um pequeno índice de URL, data, arquivo e offset permite extrair uma captura de meio de terabytes sem ler tudo. A ferramenta de linha de comando index do warcio produz um.
  • Defina uma política de retenção. Páginas brutas podem conter dados pessoais. Decida por quanto tempo mantê-las, restrinja quem pode lê-las e exclua conforme um cronograma.
  • Ignore duplicatas de forma deliberada. Quando uma página não mudou desde a última captura, um registro revisit pode apontar para a cópia anterior em vez de armazená-la novamente, o que combina bem com a detecção de mudanças.

Conclusão

O parsing é a parte de um pipeline de scraping com maior chance de estar errada e maior chance de mudar, portanto não deveria ser o único registro do que foi coletado. Armazene a resposta bruta em WARC antes de fazer o parsing, solicite respostas compactadas e as mantenha compactadas, e registre de onde cada arquivo foi coletado.

No nosso teste, isso custou aproximadamente 3,5 MB de disco para 40 páginas e transformou um crawl que levava segundos em uma reprodução que levou uma fração de um segundo. Na próxima vez que um extrator quebrar, ou uma nova pergunta surgir sobre o mês passado, a resposta é um job em lote, e não uma semana perdida.

Fontes e referências

  • International Internet Preservation Consortium, The WARC Format 1.1.
  • Webrecorder, warcio, versão 1.8.1, usada no código e no teste acima.
  • Common Crawl, Get started, sobre seu uso do formato WARC.
  • Arquivo de teste com 40 páginas coletadas pela Shifter em 1 de outubro de 2026, usando o código acima.

Pronto para começar?

Experimente os proxies residenciais da Shifter, mais de 205M IPs, mais de 195 países, a partir de $ 0,75/GB.

Começar