Pular para o conteúdo principal
Voltar ao blog
Web Scraping Dados Testing DevOps

Scraping em produção: smoke tests, alertas e fontes que mudam sem avisar

Como vigiamos mais de 35 fontes de scraping: testes contra o site real a cada três dias, filas com retentativas, alertas e a falha dos zero resultados.

JM
Javier Manzano
CEO & Co-founder • 19 de outubro de 2026
Scraping em produção: smoke tests, alertas e fontes que mudam sem avisar

Um scraper parte-se por motivos que não estão no seu código: o site muda de design, move um URL, ativa uma firewall ou deixa de publicar o feed. Nenhuma destas mudanças chega com aviso e, se ninguém a detetar, o primeiro sintoma é um utilizador a perguntar porque faltam dados.

Mantemos uma plataforma de agregação com mais de 35 fontes públicas. Isto é o que temos montado para darmos pelo problema antes do utilizador, ordenado pelo que mais nos ajudou.

Três níveis de teste, cada um com o seu trabalho

NívelContra quêQuando correO que deteta
UnitárioUma captura guardada da resposta realEm cada PRQue o nosso parser continua a entender o formato
IntegraçãoBase de dados de testeEm cada PRQue normalizar e guardar não duplica nem perde campos
SmokeO site realA cada três dias em CIQue a fonte continua lá e com a estrutura que esperamos

A distinção conta porque os dois primeiros nunca deveriam falhar por culpa de terceiros. Se o teste unitário chamasse o site real, um redesign da fonte bloquearia qualquer PR, mesmo que não tivesse nada a ver com scraping.

O teste unitário usa uma resposta real guardada

Quando acrescentamos uma fonte, guardamos uma resposta real como fixture: o JSON da API, o XML do feed ou o HTML da listagem. O teste processa esse ficheiro e verifica campo a campo o que sai. Cada formato estranho que aparece depois (um preço vazio, uma data sem ano, uma categoria com outro nome) é acrescentado ao fixture como caso novo.

O smoke test chama o site a sério

Há um por fonte e é curto. Para uma fonte de HTML:

describe('Fonte X — smoke', () => {
  let html: string;

  beforeAll(async () => {
    const res = await axios.get(URL, { timeout: 30_000, headers: DEFAULT_HEADERS });
    html = res.data;
  });

  it('devolve HTML', () => expect(html).toContain('<html'));

  it('tem cartões na listagem', () => {
    const $ = cheerio.load(html);
    expect($('.views-row').length).toBeGreaterThanOrEqual(1);
  });

  it('o primeiro cartão tem título', () => {
    const $ = cheerio.load(html);
    expect($('.views-row').first().find('h2 a, h3 a').text().trim()).not.toBe('');
  });
});

Não verifica quantos elementos há, porque isso muda todos os dias. Verifica que a estrutura de que o scraper depende continua a existir. E no cabeçalho de cada ficheiro deixamos escrito o que ver se falhar: o URL, o CMS do site e onde estão os seletores na configuração.

Correm no GitHub Actions a cada três dias (cron: '0 8 */3 * *') e também podem ser lançados à mão. Três dias é o nosso compromisso entre não martelar os sites de origem e não demorar uma semana a dar por um redesign.

Cada execução deixa rasto

Cada fonte é um trabalho numa fila BullMQ sobre Redis. Um planeador verifica de hora a hora que fontes têm de ser executadas consoante a sua frequência e coloca-as na fila. Se uma execução falhar, é repetida três vezes com espera exponencial a começar em 30 segundos. Se a lançarmos à mão a partir do painel, uma só tentativa: quando alguém a lança à mão está a olhar e quer ver o erro logo.

Cada execução guarda um registo:

  • estado (RUNNING, SUCCESS, FAILED)
  • elementos encontrados e elementos novos
  • mensagem de erro, se houver
  • início e fim

Com isso responde-se depressa a «desde quando é que não entra nada desta fonte?», que é a primeira pergunta quando algo falha.

A falha que não lança nenhum erro: zero resultados

Este foi o caso que mais nos custou ver. Um site muda a classe CSS dos seus cartões, o seletor não encontra nada e o scraper termina com sucesso: zero elementos, nenhum erro, estado SUCCESS.

Por isso o relatório diário não se limita a separar sucessos de falhas. Agrupa as fontes em quatro listas:

  1. Com sucesso, com elementos encontrados e novos.
  2. Falhadas, com o erro.
  3. Com zero resultados, mesmo que tenham terminado bem.
  4. Não executadas no período, o que costuma indicar que a fonte está desativada ou que o planeador não a colocou na fila.

A terceira lista é a que mais problemas nos revelou. Uma fonte que traz sempre entre 10 e 20 elementos e um dia traz zero está partida, mesmo que o estado diga o contrário.

Um alerta que diz o que verificar

Quando uma execução falha de verdade, chega um email à equipa. Fomos ampliando-o sempre que um alerta nos obrigava a abrir cinco separadores para o perceber. Hoje inclui:

  • fonte, estratégia e configuração
  • código HTTP e os primeiros caracteres da resposta
  • stack trace do erro
  • as últimas cinco execuções dessa fonte
  • uma sugestão consoante o tipo de erro

As sugestões são regras simples sobre o código HTTP, a mensagem de erro e a estratégia da fonte:

SinalSugestão
403O site bloqueia pedidos automáticos: rever o User-Agent, passar para navegador headless ou para uma saída com desbloqueio
404O URL mudou
429Limitação de pedidos: baixar a frequência ou acrescentar esperas
500, 502, 503O site de origem está em baixo; verificar à mão antes de investigar mais
timeout, ECONNABORTEDSite lento ou inacessível; rever o timeout
ENOTFOUND, ECONNREFUSEDDomínio alterado ou site em baixo
Erro de leitura em HTMLA estrutura mudou: rever os seletores
Erro de JSON em APIO formato mudou: rever o mapeamento de campos

Com essa tabela, a maioria dos alertas resolve-se sem abrir o código. Uma pessoa que não escreveu o scraper sabe por onde começar.

Um documento por fonte

Cada fonte tem um ficheiro Markdown com a sua configuração, um exemplo do formato de dados, os problemas conhecidos e o que está pendente. Exigimo-lo tal como o teste: sem documento, a fonte não é adicionada.

O documento existe porque cada site tem as suas particularidades, e na segunda vez que alguém as investiga já se perde tempo. Exemplos reais da nossa tabela de problemas conhecidos:

  • o preço chega como string vazia e tem de ser tratado como «sem preço», não como 0
  • o campo «gratuito» vem como número 0 ou 1
  • o URL da API redireciona para outro domínio e é preciso seguir o redirecionamento
  • o site responde 403 embora o seu robots.txt permita o acesso
  • o feed mistura tipos de conteúdo e é preciso filtrá-los pelo prefixo do guid

O que se parte e não é a fonte

Nem todas as falhas vêm de fora. Uma das nossas fontes com navegador headless foi abaixo porque um ambiente foi publicado sem a variável que indica onde está o binário do Chromium. O erro dizia Executable doesn't exist at /root/.cache/ms-playwright/... e o smoke test, que usa um cliente HTTP, continuava verde porque o site estava bem.

Desde então tratamo-lo como parte do deploy: a imagem Docker instala o Chromium, fixa o caminho por variável de ambiente e documentamo-lo na página da estratégia. Se o ambiente não puder levar um navegador, a alternativa é ligar-se a um remoto; é o que fazemos com o Scraping Browser da Bright Data nas fontes que além disso bloqueiam por IP.

Checklist de operação

  • Teste unitário com uma resposta real guardada, em cada PR.
  • Smoke test contra o site real, programado e separado dos PR.
  • Fila com novas tentativas e espera exponencial; uma só tentativa nas execuções manuais.
  • Registo por execução com encontrados, novos e erro.
  • Relatório diário que separe as fontes com zero resultados.
  • Alerta com código HTTP, resposta, histórico e sugestão.
  • Um documento por fonte com os seus problemas conhecidos.

Antes disto, explicámos como escolhemos a estratégia de cada fonte. E se tem scrapers que se partem sem que ninguém dê por isso, no nosso serviço de web scraping começamos por montar esta vigilância.

Não perca nada

JM

Javier Manzano

CEO & Co-founder na Soamee

Apaixonado por tecnologia e desenvolvimento de software. Compartilhando conhecimentos e experiências para ajudar outros desenvolvedores a crescer.

Gostou deste artigo?

Se você precisa de ajuda com seu projeto de desenvolvimento, estamos aqui para você.

Scraping em produção: smoke tests e alertas

Conte-nos seu desafio. Propomos uma solução.

Sem compromisso. Em menos de 24 horas, você recebe uma proposta com escopo, cronograma e orçamento. Sem letras miúdas.

Agende uma call gratuita →