Claude Web Scraping: Convierte tablas de estadísticas anulables en JSON limpio
Specialist in Anti-Bot Strategies
Resumen:
- Claude lee datos anulables de manera limpia, y una ejecución en vivo lo demuestra. Al recibir una tabla de estadísticas de temporada renderizada,
anthropic/claude-haiku-4.5devolvió 25 registros de temporadas de equipo en una sola llamada, escribiendo correctamentenullpara una estadística que aún no existía en la temporada 1990-91 de la NHL en lugar de adivinar un valor. - La API es entrada de texto y salida de texto — nunca toca la red. La renderización, sesiones y desafíos de acceso pertenecen a una capa de recuperación; la de esta guía es un POST por página a través de la API Universal de Raspado Sin Scrapeless.
- El modo JSON tiene una peculiaridad real que vale la pena conocer antes de que te cueste un error de análisis. Claude a través de OpenRouter a veces envuelve la salida estructurada en una cerca de
```jsonincluso conresponse_formatconfigurado; el script de extracción a continuación se encarga de eliminarlo defensivamente. - Movimientos de nombramiento en la categoría económica actual. El catálogo actual del modelo OpenRouter coloca a
claude-haiku-4.5muy por encima de la línea más antiguaclaude-3-haikuen capacidad con un modesto premium por token; esta guía verificó el catálogo directamente en lugar de asumir un nombre de memoria. - ¿Aún no tienes una clave de Anthropic? La solicitud idéntica se ejecuta a través de OpenRouter. Esta guía lo ejecutó en vivo en
anthropic/claude-haiku-4.5y muestra la salida capturada. - Gratis para comenzar del lado de la recuperación. Crea tu clave API de Scrapeless en app.scrapeless.com.
¿Puede Claude raspar sitios web?
Claude analiza; no recupera. Dale texto de página y un esquema y devolverá registros limpios y tipados, incluyendo el caso más complicado de saber cuándo un campo está genuinamente ausente en lugar de inventar un cero que parece plausible. Lo que no puede hacer es recuperar una URL específica, ejecutar el JavaScript que construye una página, mantener una sesión o superar un desafío de acceso al volumen que requiere un trabajo de raspado. Cada configuración funcional de "raspado web con Claude" empareja el modelo con una capa de recuperación que lo hace por separado.
Una guía anterior en este sitio, Raspado Web con Claude AI, repasa diez maneras diferentes de combinar Claude con un raspador. Esta guía es más estrecha y concreta: un objetivo real, una extracción ejecutada en vivo, un esquema, salida capturada real. Si la pregunta más amplia es qué modelo de lenguaje usar para el análisis en general, la explicación sobre raspadores LLM cubre la categoría.
Instalación
El SDK de Anthropic cubre el camino nativo, openai cubre el camino de OpenRouter, y requests cubre la capa de recuperación:
bash
pip install "anthropic==0.120.0" "openai==2.48.0" requests
Configurar
bash
export ANTHROPIC_API_KEY="sk-ant-your_key"
export SCRAPELESS_API_KEY="sk_your_scrapeless_key"
Recuperar una página con un campo que a veces falta
El objetivo de demostración es un sandbox público de estadísticas deportivas: una fila por temporada por cada equipo de la NHL, incluyendo una estadística — derrotas en tiempo extra — que la liga no rastreó hasta parte de la década de 1990. Las filas de antes de ese punto dejan la celda en blanco, lo que convierte a la página en una prueba genuina de si un extractor inventa datos o informa que faltan. Un POST a la API Universal de Raspado devuelve la página con la renderización y el enrutamiento proxy manejados del lado del servidor:
python
# fetch_teams.py — recuperar la página de estadísticas del equipo a través de 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"recuperados {len(html):,} caracteres")
print("filas de equipo:", html.count('<tr class="team">'))
with open("page.html", "w", encoding="utf-8") as f:
f.write(html)
La ejecución imprime 25 filas de equipo para la primera página de resultados del sandbox — un objetivo en vivo mantenido por un sitio público de práctica de raspado web, distinto de los sandbox de citas y libros utilizados en las otras guías de análisis LLM de este sitio.
Implementación básica: Claude como el extractor
La API de Mensajes nativa toma un Esquema JSON directamente a través de output_config, que la referencia actual de la API documenta como el reemplazo para los viejos trucos de JSON basados en prefills — consulta la guía de salidas estructuradas de Anthropic. El nivel de Claude no legado más barato actualmente es claude-haiku-4-5; nota que el ID del modelo nativo utiliza guiones, a diferencia de la etiqueta claude-haiku-4.5 que lista OpenRouter.
Nota: Este bloque necesita una
ANTHROPIC_API_KEYcon crédito — el único requisito previo que esta guía no asume. La siguiente sección ejecuta la misma extracción en vivo a través de OpenRouter, con la salida capturada.
python
# extract_claude.py — extracción nativa de Claude (requiere ANTHROPIC_API_KEY)
import json
from anthropic import Anthropic
client = Anthropic() # lee ANTHROPIC_API_KEY del entorno
page_html = open("page.html", encoding="utf-8").read()
response = client.messages.create(
model="claude-haiku-4-5",
max_tokens=6000,
system="Extraer cada fila de equipo-temporada de esta tabla. ot_losses es nulo cuando la celda está en blanco.",
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'])} equipos")
output_config.format garantiza que la respuesta se analice como JSON contra el esquema — sin eliminación de cercas, sin solución alternativa de prefills.
¿Sin clave de Anthropic? Ejecútalo a través de OpenRouter
OpenRouter expone a Claude detrás de una superficie de chat-completions compatible con OpenAI. Esta es la versión que esta guía ejecutó de verdad, obteniendo y extrayendo en un solo script autocontenido:
python
# extract_openrouter.py — la misma extracción, ejecutada a través de 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": 'Extraer cada fila de equipo-temporada de esta tabla. Responde SOLO con 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 es nulo cuando la celda está en blanco.",
},
{"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'])} equipos")
print(json.dumps(data["teams"][0], ensure_ascii=False))
La ejecución en vivo extrajo todos los 25 equipos-temporadas, primer registro:
text
extraído 25 equipos
{"name": "Boston Bruins", "year": 1990, "wins": 44, "losses": 24, "ot_losses": null, "win_pct": 0.55, "goals_for": 299, "goals_against": 264}
ot_losses volvió a ser null para la temporada 1990-91: el año precede el seguimiento de esa estadística por parte de la NHL — y cada una de las 25 filas se analizó a los tipos declarados sin ninguna conversión posterior. El despojo de cercas importa aquí: sin ello, json.loads falla de manera rotunda en la salida de OpenRouter de este modelo, incluso con response_format establecido. La llamada completa: 13,365 tokens.
Obtén tu clave API en el plan gratuito: app.scrapeless.com
Patrones Avanzados
- Defenderse contra la salida cercada en la ruta de agregador. La ruta nativa de
output_config.formataplica JSON verdadero al esquema sin cercas; la ruta de completaciones de chat de OpenRouter no garantiza esa misma condición de modelo a modelo, así que quita un bloque líder de```antes de analizar, independientemente de qué modelo estés usando. - Nombrar la nulidad explícitamente. Solo el esquema (
"ot_losses": {"type": ["integer", "null"]}) más una oración en el aviso del sistema es lo que produjo nulos correctos en lugar de un modelo que inventa0. Deja cualquiera de los dos fuera y vuelve a probar antes de confiar en el campo. - Mantén una página por llamada. Los límites de página son límites de registro naturales; agrupar varias páginas en un solo aviso difumina dónde terminan las estadísticas de un equipo y comienzan las del siguiente.
- Mide los tokens antes de escalar. La ejecución anterior costó 13,365 tokens por una página de 25 filas: multiplica por los recuentos reales de páginas y campos antes de comprometerte con un volumen de páginas, no después de que llegue la factura.
Solución de Problemas
json.loadsfalla en una respuesta de OpenRouter incluso con el modo JSON establecido. Busca un cercado líder```jsony quítalo antes de analizar: la ejecución en vivo de esta guía necesitó exactamente esa solución.- Un campo que debería estar ausente a veces siempre vuelve como
0o un marcador de posición. Indica explícitamente en el esquema y en el aviso cuándo un campo es nullable y qué condición lo hace nulo; los modelos por defecto completan los espacios en blanco a menos que se les indique no hacerlo. - Cero o unas pocas filas de una página que tiene docenas. Primero verifica la cuenta de HTML obtenido, como lo hace el script de obtención anterior: una obtención delgada es un problema de renderizado o acceso, no algo que el aviso de extracción puede solucionar.
- La salida varía entre ejecuciones idénticas. Establece
temperature=0en la ruta de OpenRouter; la extracción es una tarea de transcripción, y cualquier temperatura por encima de cero invita a la paráfrasis.
Conclusión
El camino de salida estructurada de Claude elimina la conjetura de obtener JSON tipado de vuelta: output_config.format en la API nativa omite completamente los trucos de prellenado, e incluso la superficie de completaciones de chat de OpenRouter más permisiva logró obtener las 25 filas de equipo-temporada de una página real con campos correctamente nulos, una vez que la respuesta se defendió contra cercas de markdown. Lo que Claude no proporciona es la página en sí: un POST del lado del servidor por página, a través de una capa de obtención construida para el renderizado y el acceso, es lo que hace que los registros existan para extraer en primer lugar.
¿Listo para Alimentar a Claude con Páginas Reales?
La capa de obtención aquí es la API de Extracción Universal — los planes y volúmenes de solicitud están en la página de precios, con cada parámetro de unlocker.webunlocker en la documentación para desarrolladores. Crea una clave en el plan gratuito en app.scrapeless.com y ambos scripts se ejecutarán como están escritos.
FAQ
P: ¿Puede Claude raspar sitios web por sí mismo?
No. La API de Claude es un punto final de modelo de lenguaje: extrae de texto que proporcionas y no puede emitir solicitudes HTTP, renderizar JavaScript o mantener una sesión. Cada configuración de trabajo lo empareja con una capa de obtención que devuelve contenido de página fiel.
P: ¿Qué modelo de Claude debo usar para extracción de raspado web?
claude-haiku-4-5 en la API nativa (claude-haiku-4.5 en OpenRouter) — la ejecución en vivo en esta guía lo utilizó y devolvió todos los 25 registros con campos correctamente nulos. Es la capa más barata de Claude no legada; el nombre más antiguo claude-3-haiku es de una generación anterior aún listado pero no es la recomendación actual.
P: ¿Por qué mi salida JSON de Claude falla al analizar incluso con el modo JSON activado?
En la ruta de agregador de OpenRouter, Claude puede envolver su salida en un cercado de markdown ```json incluso cuando response_format está configurado en json_object. Quita un cercado líder antes de llamar a json.loads, o usa output_config.format de la API nativa, que aplica JSON verdadero al esquema sin ese modo de fallo.
P: ¿Cómo maneja Claude un campo que a veces falta en la página de origen?
Correctamente, cuando el esquema y el aviso así lo indican. El esquema de esta guía marcó ot_losses como ["entero", "nulo"] y el aviso del sistema declaró la condición nula; la ejecución en vivo devolvió nulo para una temporada anterior a la estadística en lugar de fabricar un valor.
P: ¿Es legal realizar scraping con Claude?
La capa de extracción no cambia las reglas de colección. Solo extraiga páginas públicas, respete los términos del sitio y las directrices de robots estandarizadas por el Protocolo de Exclusión de Robots, mantenga los volúmenes limitados y maneje cualquier dato personal de acuerdo con la legislación aplicable, además de las políticas de uso de Anthropic en el lado del modelo.
En Scrapeless, solo accedemos a datos disponibles públicamente y cumplimos estrictamente con las leyes, regulaciones y políticas de privacidad del sitio web aplicables. El contenido de este blog es sólo para fines de demostración y no implica ninguna actividad ilegal o infractora. No ofrecemos garantías y renunciamos a toda responsabilidad por el uso de la información de este blog o enlaces de terceros. Antes de realizar cualquier actividad de scraping, consulte a su asesor legal y revise los términos de servicio del sitio web de destino u obtenga los permisos necesarios.



