Arquitetura de Scraping em Larga Escala: Um Guia de Campo
Algumas centenas de páginas é um script. Milhões é um sistema distribuído. Aqui está a arquitetura que te leva até lá — filas, trabalhadores assíncronos, camadas de proxy, tentativas, deduplicação e verificações de qualidade de dados, com código funcional.
Fazer scraping de algumas centenas de páginas é um script. Fazer scraping de milhões é um sistema distribuído. Quando seu alvo passa de "roda durante a noite no meu laptop" para "precisa terminar esta semana sem derreter", a parte difícil deixa de ser o parsing e passa a ser tudo ao redor — como você coloca o trabalho em fila, distribui, evita bloqueios, tenta novamente em caso de falhas, e armazena e verifica o resultado. Isso é scraping em larga escala como uma arquitetura, não um trecho de código, com código funcional em cada etapa.
Escala é concorrência, não um loop maior
Um número torna isso concreto. Pegue uma categoria com 20.000 páginas de listagem, 20 itens cada — 400.000 páginas para buscar. A 2,5 segundos por página, uma execução estritamente sequencial leva cerca de 1.000.000 segundos, aproximadamente 11,5 dias esperando o carregamento das páginas antes de analisar um único campo. Dirija 200 páginas em paralelo e esses 11,5 dias colapsam para cerca de uma hora de tempo de relógio. O tempo é a restrição em escala, e a concorrência é como você o recupera. Todo o resto na arquitetura existe para tornar essa concorrência viável.

A arquitetura em um relance
Um scraper que sobrevive a milhões de páginas é um pequeno sistema distribuído com algumas partes nomeadas, cada uma resolvendo um problema que só aparece em volume:
- Uma fila mantém as URLs ainda a serem buscadas e desacopla a descoberta do trabalho.
- Trabalhadores assíncronos ou distribuídos retiram da fila e buscam simultaneamente — é aqui que estão as economias de tempo de relógio.
- Uma camada de proxy e anti-bot rotaciona IPs e apresenta tráfego de navegador real para que nenhum endereço único ultrapasse um limite de taxa.
- Renderização, apenas quando necessário, porque um navegador sem cabeça é a coisa mais cara no pipeline.
- Tentativas com recuo exponencial, deduplicação, armazenamento e monitoramento com verificações de qualidade de dados completam o sistema.
Primeiro a fila: desacople a descoberta da busca
A decisão estrutural mais importante é colocar uma fila entre "o que buscar" e "fazer a busca". Um produtor enumera URLs; um conjunto de trabalhadores as esgota. Nenhum dos lados sabe quão rápido o outro opera, e você adiciona trabalhadores sem tocar no produtor. Em Python, isso é Celery ou RQ sobre Redis; em Node, BullMQ; em escala maior, RabbitMQ ou Kafka. O padrão em um arquivo:
import asyncio, aiohttp
CONCURRENCY = 50
queue = asyncio.Queue()
async def worker(session):
while True:
url = await queue.get()
try:
async with session.get(url, timeout=20) as resp:
await handle(url, await resp.text(), resp.status)
except Exception as err:
await on_failure(url, err)
finally:
queue.task_done()
async def run(urls):
for u in urls:
queue.put_nowait(u)
async with aiohttp.ClientSession() as session:
tasks = [asyncio.create_task(worker(session)) for _ in range(CONCURRENCY)]
await queue.join()
for t in tasks:
t.cancel()
O ajuste que importa é CONCURRENCY. Muito baixo desperdiça o paralelismo que torna a escala possível; muito alto sobrecarrega tanto o alvo quanto sua própria saída. Você encontra o valor certo observando as taxas de erro subirem — e é exatamente por isso que o monitoramento é uma parte de primeira classe do sistema, não um pensamento tardio.

A camada de proxy quebra primeiro
Em baixo volume, você mal percebe as defesas anti-bot; em escala, elas quebram a execução primeiro. Envie algumas centenas de milhares de requisições de um IP e você é limitado por taxa, depois desafiado, depois bloqueado. A solução é a rotação entre muitos endereços. Estruture: IPs de datacenter baratos para alvos e APIs lenientes, IPs residenciais rotativos para alvos comerciais difíceis que esperam tráfego de usuários reais. Mas a rotação sozinha não é suficiente — defesas modernas também leem impressões digitais TLS e a ordem dos cabeçalhos, então o tráfego precisa parecer um navegador, não apenas vir de um IP novo.
Tentativas: falha é o estado constante
Com um milhão de requisições, uma taxa de falha transitória de 1% são 10.000 páginas falhadas. Falha não é um caso extremo nesse volume — é rotina, e o pipeline precisa tratar uma busca falhada como normal em vez de fatal. Tente novamente com recuo exponencial e um limite, depois mova a URL para uma fila de mensagens mortas em vez de bloquear a execução. Leia por que falhou: um tempo limite ou 503 vale a pena tentar novamente, um 404 definitivo não.
import asyncio, random
async def fetch_with_retry(session, url, tries=4):
for attempt in range(tries):
try:
async with session.get(url, timeout=20) as r:
if r.status == 404:
return None # don't retry a hard 404
if r.status < 400:
return await r.text()
except Exception:
pass
await asyncio.sleep(2 ** attempt + random.random()) # backoff + jitter
await dead_letter(url) # give up after the cap
return None
Deduplicação: não rastreie a mesma página duas vezes
A descoberta em escala produz duplicatas constantemente — o mesmo produto acessível por três caminhos, parâmetros de rastreamento que fazem uma página parecer dez. Normalize URLs antes de entrarem na fila, depois mantenha um conjunto de vistos (um conjunto Redis, ou um filtro de Bloom quando o conjunto atinge centenas de milhões):
from urllib.parse import urlsplit, urlunsplit, parse_qsl, urlencode
seen = set()
def normalize(url):
s = urlsplit(url.lower())
q = [(k, v) for k, v in parse_qsl(s.query) if not k.startswith("utm_")]
return urlunsplit((s.scheme, s.netloc, s.path.rstrip("/"), urlencode(sorted(q)), ""))
def enqueue(url):
key = normalize(url)
if key not in seen:
seen.add(key)
queue.put_nowait(key)
Armazenamento e qualidade de dados
Dois hábitos específicos de escala: escreva em lotes para que o armazenamento não seja seu gargalo, e separe bruto de analisado para que você possa reanalisar sem rastrear novamente quando os seletores mudarem. Depois adicione a metade do monitoramento que a maioria das equipes ignora — verificações de qualidade de dados. Uma execução pode relatar 100% de sucesso HTTP e ainda produzir lixo se o layout mudou e seus seletores agora não correspondem a nada. Assegure-se de que os campos obrigatórios não estão vazios e os valores são sensatos:
def validate(row):
assert row.get("title"), "empty title — selector may have drifted"
price = row.get("price")
assert isinstance(price, (int, float)) and 0 < price < 1_000_000, "bad price"
return row
# fail loud on page 5,000, not silently after 5,000,000 empty rows
Descarregue proxies, anti-bot e renderização para uma API
Renderize apenas quando necessário
Um navegador sem cabeça é a operação mais cara no pipeline — CPU, memória e segundos por página, que dominam tudo em um milhão de páginas. Muitos sites ainda enviam dados no HTML inicial ou em um endpoint JSON; uma busca simples mais um analisador é uma ordem de magnitude mais barata. Tente o caminho barato primeiro, confirme que os campos estão presentes, e escale para renderização apenas para as páginas que precisam. Nossa análise de custo de renderização versus HTTP coloca números reais nessa diferença.
Construir versus comprar as camadas difíceis
Tudo acima é construível, então a pergunta honesta é quais partes merecem seu tempo de engenharia. O modelo de dados, a lógica de parsing, as verificações de qualidade e o esquema de armazenamento são específicos para o seu projeto — só você pode construí-los bem. O pool de proxies, o manuseio anti-bot, a frota de renderização sem cabeça e a fila de tentativas e entregas são infraestrutura genérica que é cara de construir e um trabalho árduo de manter saudável à medida que os alvos evoluem. É aí que uma Scraper API gerenciada se posiciona: alugue as partes que são as mesmas para todos. Nossa análise de construir versus comprar trabalha o imposto de manutenção em detalhe, e executar scrapers como software de produção cobre como manter tudo observável.
Perguntas frequentes
Como você faz scraping de milhões de páginas?
Com concorrência, não um loop maior. Coloque uma fila entre a descoberta de URLs e a busca, esgote-a com um conjunto de trabalhadores assíncronos ou distribuídos, rotacione IPs para evitar bloqueios, tente novamente falhas transitórias com recuo, deduplique URLs e escreva em lotes para o armazenamento. Uma execução sequencial de um milhão de páginas leva dias; o mesmo trabalho através de um conjunto de trabalhadores concorrentes termina em horas.
Qual é a melhor arquitetura para scraping em larga escala?
Um pipeline de fila e trabalhadores: um produtor enumera URLs em uma fila (Redis, RabbitMQ ou Kafka), trabalhadores buscam simultaneamente através de uma camada de proxy rotativa, renderizam apenas as páginas que precisam de JavaScript, tentam novamente falhas em uma fila de mensagens mortas, deduplicam com um conjunto de vistos e armazenam dados brutos e analisados separadamente. Envolva tudo em monitoramento com verificações de qualidade de dados para que desvios apareçam cedo.
Quantas requisições você pode executar em paralelo?
Depende do alvo e da sua saída, não de um número fixo. Scraping é limitado por IO, então uma máquina modesta pode manter muitas centenas de requisições em andamento. Comece com uma concorrência de cerca de 50, observe a taxa de erro e aumente até que as falhas subam — esse é o seu limite. Além dos limites de uma máquina, adicione trabalhadores distribuídos em vez de forçar um único nó.
Como você lida com falhas em escala?
Assuma a falha — com um milhão de requisições, mesmo uma taxa de erro de 1% são 10.000 páginas mortas. Tente novamente erros transitórios (tempos limite, 503s) com recuo exponencial e jitter, limite as tentativas e mova falhas persistentes para uma fila de mensagens mortas em vez de bloquear a execução. Não tente novamente 404s definitivos. Randomizar a ordem de coleta também espalha falhas para que você não falhe nas mesmas páginas em toda execução.
Escala é principalmente as partes que não são divertidas de construir: rotação de IPs, anti-bot, renderização sem cabeça, filas e tentativas. Possua as peças específicas para seus dados — o modelo, os parsers, as verificações de qualidade — e alugue a infraestrutura genérica que é o mesmo trabalho árduo para todos. Acerte a fila e a concorrência primeiro; todo o resto é fazer essa concorrência sobreviver ao contato com um milhão de páginas reais.