🎯 Um navegador em nuvem personalizável e anti-detecção alimentado por Chromium desenvolvido internamente, projetado para rastreadores web e agentes de IA. 👉Experimente agora
De volta ao blog

Como Construir um Web Scraper Auto-Curativo

Emily Chen
Emily Chen

Advanced Data Extraction Specialist

05-Aug-2026

TL;DR:

  • Um seletor CSS que funciona hoje quebra logo pela manhã quando um site renomeia uma classe, e um scraper normal responde silenciosamente retornando nada.
  • Um scraper auto-reparável corrige o seletor em tempo de execução: ele busca a página, tenta o seletor salvo e, em caso de erro, pede um novo a um modelo de linguagem a partir do DOM ao vivo.
  • Este pipeline busca a página renderizada através da API de Raspagem Universal Scrapeless, então o modelo sempre se corrige em relação ao markup real e executado por script, em vez de uma casca vazia.
  • Uma execução ao vivo mostra que o seletor salvo .author-name não corresponde a nenhum elemento, então deepseek-v4-flash propõe small.author, que extrai todos os dez autores.
  • O seletor corrigido é validado contra o DOM antes de ser confiável — um palpite errado não corresponde a nada e é descartado, então uma sugestão ruim nunca corrompe silenciosamente seus dados.
  • Obtenha uma chave da API Scrapeless no plano gratuito e execute todo o loop do início ao fim.

Pipeline em um Relance

Um scraper quebra não porque o código está errado, mas porque a página se moveu. A solução é tratar o seletor como um dado que pode ser regenerado, não uma constante embutida na fonte. Este pipeline faz isso em cinco etapas:

buscar a página renderizada (Scrapeless) → tentar o seletor salvo → detectar a falha → corrigir com um modelo de linguagem → validar e extrair → persistir o seletor funcional

Cada etapa abaixo roda contra a página ao vivo. A busca e a correção são as duas chamadas que saem da sua máquina; todo o resto é parsing local que você pode inspecionar.

Etapa 1: Buscar a página renderizada com Scrapeless

A correção é tão boa quanto o HTML que você mostra ao modelo, então a busca deve retornar a página que um navegador construirá, não o contêiner vazio que um site renderizado pelo cliente envia primeiro. Essa busca renderizada é o trabalho da API de Raspagem Universal Scrapeless. A API de Raspagem Universal renderiza a página do lado do servidor com js_render e retorna o HTML final em seu campo data. Instale as duas bibliotecas que o pipeline usa:

bash Copy
pip install requests beautifulsoup4

Coloque ambas as chaves no ambiente — a chave Scrapeless para a busca, a chave do modelo para a correção:

bash Copy
export SCRAPELESS_API_KEY="sua_chave_api_scrapeless"
export OPENROUTER_API_KEY="sua_chave_api_openrouter"

Etapa 2: Tentar o seletor salvo e detectar a falha

Execute o scraper do mês passado na página de hoje e a falha é silenciosa: o seletor não corresponde a nada e a extração retorna uma lista vazia, então um trabalho ingênuo escreve zero linhas e reporta sucesso. A página de demonstração abaixo constrói suas citações com JavaScript; o seletor salvo .author-name é uma classe que o markup não usa mais, substituindo qualquer seletor que um redesign tenha renomeado:

python Copy
import os, requests
from bs4 import BeautifulSoup

def fetch(url):
    r = requests.post(
        "https://api.scrapeless.com/api/v2/unlocker/request",
        headers={"x-api-token": os.environ["SCRAPELESS_API_KEY"], "Content-Type": "application/json"},
        json={"actor": "unlocker.webunlocker", "input": {"url": url, "js_render": True}},
        timeout=120,
    )
    r.raise_for_status()
    return r.json()["data"]

soup = BeautifulSoup(fetch("https://quotes.toscrape.com/js/"), "html.parser")

SAVED_SELECTOR = ".author-name"          # funcionou mês passado; o site renomeou a classe
authors = [e.get_text(strip=True) for e in soup.select(SAVED_SELECTOR)]
print(f"seletor salvo {SAVED_SELECTOR!r} correspondeu: {len(authors)}",
      "-> QUEBRADO, extraiu nada" se não authors else "-> ok")

A execução torna a falha silenciosa barulhenta:

text Copy
seletor salvo '.author-name' correspondeu: 0 -> QUEBRADO, extraiu nada

Etapa 3: Corrigir com um modelo de linguagem

Quando o seletor salvo não corresponde a nada, entregue ao modelo o DOM ao vivo e peça um substituto. O modelo lê a estrutura real — as tags, classes e aninhamentos definidos pela especificação de Seletores CSS — e retorna um seletor moldado para a página à sua frente, não para uma página que viu durante o treinamento. Constranger a resposta a um objeto JSON mantém a resposta uma única string de seletor em vez de uma explicação:

python Copy
import os, json, requests

def propose_selector(html_fragment, target):
    prompt = (f'Um seletor CSS de raspagem da web quebrou. A partir deste fragmento HTML, retorne JSON '
              f'{{"selector": "<css>"}} para o elemento que contém o {target}. Retorne SOMENTE JSON.\n\n'
              f"HTML:\n{html_fragment}")
    r = requests.post(
        "https://openrouter.ai/api/v1/chat/completions",
        headers={"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}", "Content-Type": "application/json"},
        json={"model": "deepseek/deepseek-v4-flash",
              "messages": [{"role": "user", "content": prompt}],
              "response_format": {"type": "json_object"}, "temperature": 0},
        timeout=120,
    )
    r.raise_for_status()
    return json.loads(r.json()["choices"][0]["message"]["content"])["selector"]

Enviar um bloco .quote renderizado é contexto suficiente e mantém a contagem de tokens — e o custo — baixo.

Obtenha sua chave de API no plano gratuito: app.scrapeless.com

Etapa 4: Validar e extrair

Um seletor proposto é uma sugestão até que o DOM a confirme. Execute o seletor curado contra a mesma sopa e conte as correspondências: uma correção real retorna linhas, e um palpite ruim retorna zero e é descartado antes que possa gravar dados vazios. O loop completo une as três chamadas:

python Copy
import os, json, requests
from bs4 import BeautifulSoup

def fetch(url):
    r = requests.post(
        "https://api.scrapeless.com/api/v2/unlocker/request",
        headers={"x-api-token": os.environ["SCRAPELESS_API_KEY"], "Content-Type": "application/json"},
        json={"actor": "unlocker.webunlocker", "input": {"url": url, "js_render": True}},
        timeout=120,
    )
    r.raise_for_status()
    return r.json()["data"]

def propose_selector(html_fragment, target):
    prompt = (f'Um seletor CSS de web scraping quebrou. A partir deste fragmento HTML, retorne JSON '
              f'{{"selector": "<css>"}} para o elemento que contém o {target}. Retorne SOMENTE JSON.\n\n'
              f"HTML:\n{html_fragment}")
    r = requests.post(
        "https://openrouter.ai/api/v1/chat/completions",
        headers={"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}", "Content-Type": "application/json"},
        json={"model": "deepseek/deepseek-v4-flash",
              "messages": [{"role": "user", "content": prompt}],
              "response_format": {"type": "json_object"}, "temperature": 0},
        timeout=120,
    )
    r.raise_for_status()
    return json.loads(r.json()["choices"][0]["message"]["content"])["selector"]

def extract(soup, selector):
    return [e.get_text(strip=True) for e in soup.select(selector)]

soup = BeautifulSoup(fetch("https://quotes.toscrape.com/js/"), "html.parser")
authors = extract(soup, ".author-name")

if not authors:
    print("seletor salvo '.author-name' correspondente: 0 -> cura")
    sample = str(soup.select_one(".quote") or soup.body)[:1500]
    healed = propose_selector(sample, "nome do autor da citação")
    print("modelo propôs seletor:", repr(healed))
    authors = extract(soup, healed)
    print("seletor curado correspondente:", len(authors))

print("autores:", ", ".join(authors[:3]), "...")

O seletor quebrado se cura e a extração preenche novamente:

text Copy
seletor salvo '.author-name' correspondente: 0 -> cura
modelo propôs seletor: 'small.author'
seletor curado correspondente: 10
autores: Albert Einstein, J.K. Rowling, Jane Austen ...

Como o seletor curado é verificado contra o mecanismo de seletor CSS que seu parser já usa, a validação é exata: ou corresponde a elementos ou não, e apenas uma correspondência é aceita.

Etapa 5: Persistir o seletor funcional

Curar uma vez é consertar; curar a cada execução é desperdício. Depois que um seletor é validado, escreva-o de volta para um pequeno cache indexado por campo, para que a próxima execução leia o seletor funcional diretamente do disco e só chame o modelo quando aquele também quebrar:

python Copy
import json
from pathlib import Path

CACHE = Path("selectors.json")

def remember(field, selector):
    cache = json.loads(CACHE.read_text()) if CACHE.exists() else {}
    cache[field] = selector
    CACHE.write_text(json.dumps(cache, indent=2))

remember("quote_author", "small.author")   # próxima execução começa a partir do seletor curado

O scraper agora se degrada graciosamente: ele se apoia em seletores em cache enquanto funcionam e os repara no momento em que uma página muda, em vez de falhar até que alguém perceba e edite o código.

Conclusão

Um scraper auto-reparador transforma um seletor quebrado de uma falha em um evento de tempo de execução. A API Universal de Scraping Scrapeless fornece o HTML renderizado que o modelo precisa considerar, o modelo lê esse DOM ao vivo e propõe um seletor, e uma contagem de correspondências contra o analisador decide se a correção é real antes que qualquer dado seja gravado. A execução acima — .author-name correspondendo a zero, small.author correspondendo a dez — é o loop em miniatura: buscar, detectar, curar, validar, persistir. Dimensione o volume de requisições que você espera em relação à página de preços antes de escalar, e leia o artigo sobre como um navegador analisa HTML em um DOM se você quiser entender por que a busca renderizada é importante. Para uma visão mais ampla dos modelos de linguagem na extração, o explicador sobre o que é um scraper LLM contextualiza o assunto.

Junte-se à nossa comunidade para aproveitar um plano gratuito e trocar experiências com outros desenvolvedores que estão construindo scrapers resilientes: Discord · Telegram.

FAQ

Q: O que realmente quebra quando um scraper "quebra"?

Normalmente é o seletor, não a lógica. Um site lança um redesign que renomeia uma classe ou reestrutura sua marcação, o seletor CSS salvo para de corresponder e a extração retorna uma lista vazia. A requisição ainda é bem-sucedida, razão pela qual a falha é fácil de ser ignorada.

Q: Por que buscar através da API Universal de Scraping Scrapeless em vez de uma requisição simples?

Porque o modelo só pode se curar com base no HTML que é mostrado. Uma página renderizada pelo cliente retorna um container vazio para um cliente HTTP simples, então o modelo raciocinaria sobre uma marcação que não possui dados. A API Universal de Scraping renderiza a página do lado do servidor e retorna o DOM finalizado, oferecendo ao modelo a estrutura real com a qual trabalhar.

Q: Como você impede que o modelo invente um seletor que não corresponda a nada?

Você valida antes de confiar. O seletor curado é testado contra o DOM analisado e as correspondências são contadas; um seletor que retorna zero elementos é descartado em vez de usado. O modelo propõe, mas a contagem de correspondências decide.

Q: Isso chama o modelo em cada execução?

Não. Uma vez que um seletor é validado, ele é armazenado em disco indexado por campo, e execuções posteriores leem o seletor ativo diretamente. O modelo é chamado apenas quando um seletor em cache para de corresponder, o que mantém tanto a latência quanto o custo atrelados a falhas reais, em vez de a cada extração.

Q: É legal raspar dessa maneira?

O loop de cura não muda o que você está autorizado a coletar. Raspe páginas públicas, respeite os termos do site e suas diretrizes para robôs, evite dados pessoais que você não tem base para processar e mantenha as taxas de requisição modestas.

Na Scorretless, acessamos apenas dados disponíveis ao público, enquanto cumprem estritamente as leis, regulamentos e políticas de privacidade do site aplicáveis. O conteúdo deste blog é apenas para fins de demonstração e não envolve atividades ilegais ou infratoras. Não temos garantias e negamos toda a responsabilidade pelo uso de informações deste blog ou links de terceiros. Antes de se envolver em qualquer atividade de raspagem, consulte seu consultor jurídico e revise os termos de serviço do site de destino ou obtenha as permissões necessárias.

Artigos mais populares

Catálogo