🎯 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

Claude Web Scraping: Transformar Tabelas de Estatísticas Nullable em JSON Limpo

Sophia Martinez
Sophia Martinez

Specialist in Anti-Bot Strategies

29-Jul-2026

TL;DR:

  • Claude lê dados anuláveis de forma limpa, e uma execução ao vivo comprova isso. Alimentado com uma tabela de estatísticas da temporada renderizada, anthropic/claude-haiku-4.5 retornou 25 registros de temporada para equipes em uma única chamada, escrevendo corretamente null para uma estatística que ainda não existia na temporada da NHL de 1990-91, em vez de adivinhar um valor.
  • A API é texto de entrada, texto de saída — nunca toca na rede. Renderização, sessões e desafios de acesso pertencem a uma camada de busca; a deste guia é um POST por página através da Scrapeless Universal Scraping API.
  • O modo JSON tem uma peculiaridade real que vale a pena conhecer antes que um erro de análise te custe. Claude via OpenRouter às vezes envolve a saída estruturada em uma cerca ```json mesmo com response_format definido — o script de extração abaixo a remove defensivamente.
  • Nomes atuais de nível barato em movimento. O catálogo atual do modelo OpenRouter coloca claude-haiku-4.5 bem acima da linha mais antiga claude-3-haiku em capacidade, com um modesto prêmio por token; este guia verificou o catálogo diretamente em vez de assumir um nome de memória.
  • Sem chave da Anthropic ainda? O pedido idêntico passa pelo OpenRouter. Este guia executou isso ao vivo em anthropic/claude-haiku-4.5 e mostra a saída capturada.
  • Gratuito para começar do lado da busca. Crie sua chave da Scrapeless API em app.scrapeless.com.

Claude consegue extrair dados de sites?

Claude analisa; ele não busca. Dê a ele o texto da página e um esquema e ele retorna registros limpos e tipados — incluindo o caso mais difícil de saber quando um campo está genuinamente ausente, em vez de inventar um zero que parece plausível. O que ele não pode fazer é recuperar uma URL específica, executar o JavaScript que constrói uma página, manter uma sessão, ou passar um desafio de acesso no volume que um trabalho de extração necessita. Cada configuração funcional de "extração web com Claude" combina o modelo com uma camada de busca que faz isso separadamente.

Um guia anterior neste site, Web Scraping with Claude AI, explora dez maneiras diferentes de combinar Claude com um scraper. Este guia é mais estreito e concreto: um alvo real, uma extração executada ao vivo, um esquema, saída capturada real. Se a pergunta mais ampla é qual modelo de linguagem usar para análise, o explicador de scraper LLM cobre a categoria.

Instalar

O SDK da Anthropic cobre o caminho nativo, openai cobre o caminho do OpenRouter, e requests cobre a camada de busca:

bash Copy
pip install "anthropic==0.120.0" "openai==2.48.0" requests

Configurar

bash Copy
export ANTHROPIC_API_KEY="sk-ant-your_key"
export SCRAPELESS_API_KEY="sk_your_scrapeless_key"

Buscar uma página com um campo que às vezes está ausente

O alvo da demonstração é um sandbox público de estatísticas esportivas: uma linha por temporada para cada equipe da NHL, incluindo uma estatística — derrotas no overtime — que a liga não rastreou até parte da década de 1990. Linhas anteriores a esse ponto deixam a célula em branco, o que faz da página um teste genuíno de se o extrator inventa dados ou reporta que está faltando. Um POST para a Universal Scraping API retorna a página com renderização e roteamento proxy tratados do lado do servidor:

python Copy
# fetch_teams.py — recuperar a página de estatísticas da equipe através da Scrapeless
import os

import requests

resp = requests.post(
    "https://api.scrapeless.com/api/v2/unlocker/request",
    headers={
        "Content-Type": "application/json",
        "x-api-token": os.environ["SCRAPELESS_API_KEY"],
    },
    json={
        "actor": "unlocker.webunlocker",
        "input": {"url": "https://scrapethissite.com/pages/forms/", "method": "GET", "js_render": True},
    },
    timeout=120,
)
resp.raise_for_status()
html = resp.json().get("data", "")
print(f"buscou {len(html):,} caracteres")
print("linhas da equipe:", html.count('<tr class="team">'))

with open("page.html", "w", encoding="utf-8") as f:
    f.write(html)

A execução imprime 25 linhas de equipe para a primeira página de resultados do sandbox — um alvo ao vivo mantido por um site público de prática de web scraping, distinto das caixas de citações e livros usadas nos outros guias de análise com LLM deste site.

Implementação básica: Claude como o extrator

A API nativa de Mensagens aceita um JSON Schema diretamente através de output_config, que a referência atual da API documenta como a substituição para os antigos truques JSON baseados em preenchimento prévio — veja o guia de saídas estruturadas da Anthropic. O nível mais barato atual do Claude que não é legado é o claude-haiku-4-5; note que o ID do modelo nativo usa hífens, distinto do slug claude-haiku-4.5 listado no OpenRouter.

Nota: Este bloco precisa de uma ANTHROPIC_API_KEY com crédito — o único pré-requisito que este guia não presume. A próxima seção executa a extração idêntica ao vivo através do OpenRouter, com a saída capturada.

python Copy
# extract_claude.py — extração nativa do Claude (requer ANTHROPIC_API_KEY)
import json

from anthropic import Anthropic

client = Anthropic()  # lê ANTHROPIC_API_KEY do ambiente

page_html = open("page.html", encoding="utf-8").read()

response = client.messages.create(
    model="claude-haiku-4-5",
    max_tokens=6000,
    system="Extraia cada linha equipe-temporada desta tabela. ot_losses é nulo quando a célula está em branco.",
    messages=[{"role": "user", "content": page_html}],
    output_config={
        "format": {
            "type": "json_schema",
            "schema": {
                "type": "object",
                "properties": {
                    "teams": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "name": {"type": "string"},
                                "year": {"type": "integer"},
                                "wins": {"type": "integer"},
                                "losses": {"type": "integer"},
                                "ot_losses": {"type": ["integer", "null"]},
                                "win_pct": {"type": "number"},
                                "goals_for": {"type": "integer"},
                                "goals_against": {"type": "integer"},
                            },
                            "required": ["name", "year", "wins", "losses", "ot_losses", "win_pct", "goals_for", "goals_against"],
                            "additionalProperties": False,
                        },
                    }
                },
                "required": ["teams"],
                "additionalProperties": False,
            },
        }
    },
)

text = next(b.text for b in response.content if b.type == "text")
data = json.loads(text)
print(f"extraído {len(data['teams'])} equipes")

output_config.format garante que a resposta seja analisada como JSON de acordo com o esquema — sem remoção de cercas, sem solução alternativa de preenchimento prévio.

Sem chave Anthropic? Execute pelo OpenRouter

O OpenRouter expõe o Claude por trás de uma interface de completações de chat compatível com o OpenAI. Esta é a versão que este guia executou de verdade, busca e extração em um único script autônomo:

python Copy
# extract_openrouter.py — a mesma extração, executada via OpenRouter
import json
import os

import requests
from openai import OpenAI

resp = requests.post(
    "https://api.scrapeless.com/api/v2/unlocker/request",
    headers={
        "Content-Type": "application/json",
        "x-api-token": os.environ["SCRAPELESS_API_KEY"],
    },
    json={
        "actor": "unlocker.webunlocker",
        "input": {"url": "https://scrapethissite.com/pages/forms/", "method": "GET", "js_render": True},
    },
    timeout=120,
)
resp.raise_for_status()
page_html = resp.json().get("data", "")

client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=os.environ["OPENROUTER_API_KEY"])

completion = client.chat.completions.create(
    model="anthropic/claude-haiku-4.5",
    temperature=0,
    max_tokens=6000,
    response_format={"type": "json_object"},
    messages=[
        {
            "role": "system",
            "content": 'Extraia cada linha equipe-temporada desta tabela. Responda SOMENTE com JSON: '
            '{"teams":[{"name":str,"year":int,"wins":int,"losses":int,"ot_losses":int|null,'
            '"win_pct":number,"goals_for":int,"goals_against":int}]}. '
            "ot_losses é nulo quando a célula está em branco.",
        },
        {"role": "user", "content": page_html},
    ],
)

raw = completion.choices[0].message.content.strip()
if raw.startswith("```"):
    raw = raw.split("```")[1]
    if raw.startswith("json"):
        raw = raw[4:]
data = json.loads(raw)
print(f"extraído {len(data['teams'])} equipes")
print(json.dumps(data["teams"][0], ensure_ascii=False))

A execução ao vivo extraiu todas as 25 equipes-temporadas, primeiro registro:

text Copy
extraído 25 equipes
{"name": "Boston Bruins", "year": 1990, "wins": 44, "losses": 24, "ot_losses": null, "win_pct": 0.55, "goals_for": 299, "goals_against": 264}

ot_losses retornou null para a temporada de 1990-91 — o ano precede o rastreamento dessa estatística pela NHL — e cada uma das 25 linhas foi analisada para os tipos declarados sem conversão posterior. A remoção de cercas é importante aqui: sem isso, json.loads falha completamente na saída do OpenRouter deste modelo, mesmo com response_format definido. A chamada inteira: 13.365 tokens.

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

Padrões Avançados

  • Defenda-se contra a saída cercada no caminho do agregador. O caminho nativo output_config.format impõe JSON verdadeiro ao esquema sem cercas; o caminho de chat-completions do OpenRouter não oferece essa mesma garantia de modelo para modelo, portanto, remova um bloco inicial de ``` antes de analisar, independentemente do modelo para o qual você está direcionando.
  • Nomeie a nulidade explicitamente. O esquema sozinho ("ot_losses": {"type": ["integer", "null"]}) mais uma frase no prompt do sistema foi o que produziu nulos corretos em vez de um modelo inventando 0. Deixe qualquer um de fora e teste novamente antes de confiar no campo.
  • Mantenha uma página por chamada. Os limites de página são limites naturais de registro; agrupar várias páginas em um único prompt confunde onde terminam as estatísticas de uma equipe e começam as da próxima.
  • Meça os tokens antes de escalar. A execução acima custou 13.365 tokens para uma página de 25 linhas — multiplique pelos números reais de páginas e campos antes de se comprometer com um volume de página, não depois que a conta chegar.

Solução de Problemas

  • json.loads falha em uma resposta do OpenRouter mesmo com o modo JSON definido. Verifique se há uma cerca inicial de ```json e remova-a antes de analisar — a execução ao vivo deste guia precisava exatamente dessa correção.
  • Um campo que deveria estar ausente às vezes sempre retorna como 0 ou um espaço reservado. Declare explicitamente no esquema e no prompt quando um campo é anulável e quais condições o tornam nulo; os modelos preenchem automaticamente as lacunas, a menos que informados para não fazê-lo.
  • Zero ou poucas linhas de uma página que tem dezenas. Verifique primeiro a contagem HTML obtida, da maneira que o script de busca acima faz — uma busca superficial é um problema de renderização ou acesso, não algo que o prompt de extração pode corrigir.
  • A saída varia entre execuções idênticas. Defina temperature=0 no caminho do OpenRouter; a extração é uma tarefa de transcrição, e qualquer temperatura acima de zero convida à paráfrase.

Conclusão

O caminho de saída estruturada do Claude elimina as suposições sobre como obter JSON tipificado de volta — output_config.format na API nativa ignora completamente truques de preenchimento, e até mesmo a superfície de chat-completions do OpenRouter, mais permissiva, obteve todas as 25 linhas de equipe-temporada de uma página real com campos corretamente anulados, uma vez que a resposta foi defendida contra cercas de markdown. O que o Claude não fornece é a própria página: uma postagem do lado do servidor por página, através de uma camada de busca construída para renderização e acesso, é o que faz os registros existirem para serem extraídos em primeiro lugar.

Pronto para Alimentar o Claude com Páginas Reais?

A camada de busca aqui é a API de Extração Universal — planos e volumes de requisição estão na página de preços, com cada parâmetro unlocker.webunlocker na documentação do desenvolvedor. Crie uma chave no plano gratuito em app.scrapeless.com e ambos os scripts funcionam como escrito.

Perguntas Frequentes

P: O Claude pode extrair dados de websites sozinho?

Não. A API do Claude é um ponto final de modelo de linguagem: ela extrai de texto que você fornece e não pode emitir solicitações HTTP, renderizar JavaScript ou manter uma sessão. Cada configuração funcional emparelha-a com uma camada de busca que retorna conteúdo fiel da página.

P: Qual modelo do Claude devo usar para extração de web-scraping?

claude-haiku-4-5 na API nativa (claude-haiku-4.5 no OpenRouter) — a execução ao vivo neste guia usou isso e retornou todos os 25 registros com campos corretamente anulados. É o nível atual mais barato do Claude não legado; o nome anterior claude-3-haiku é de uma geração anterior que ainda está listada, mas não é a recomendação atual.

P: Por que minha saída JSON do Claude falha ao analisar mesmo com o modo JSON ativado?

No caminho do agregador do OpenRouter, o Claude pode envolver sua saída em uma cerca de ```json de markdown mesmo quando response_format está definido como json_object. Remova uma cerca inicial antes de chamar json.loads, ou use o output_config.format da API nativa, que impõe JSON verdadeiro ao esquema sem esse modo de falha.
P: Como Claude lida com um campo que às vezes está ausente da página de origem?

Corretamente, quando o esquema e o prompt dizem assim. O esquema deste guia marcou ot_losses como ["integer", "null"] e o prompt do sistema declarou a condição de nulo; a execução ao vivo retornou null para uma temporada que precede a estatística em vez de fabricar um valor.

P: É legal fazer scraping com Claude?

A camada de extração não altera as regras de coleta. Busque apenas páginas públicas, respeite os termos do site e as diretrizes dos robôs padronizadas pelo Protocolo de Exclusão de Robôs, mantenha os volumes limitados e trate quaisquer dados pessoais de acordo com a legislação aplicável - além das políticas de uso da Anthropic do lado do modelo.

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