Como Construir um Web Scraper Auto-Curativo
Advanced Data Extraction Specialist
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-namenão corresponde a nenhum elemento, entãodeepseek-v4-flashpropõesmall.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
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
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
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
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
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
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
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
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.



