Cómo Encontrar la API JSON Oculta Detrás de un Sitio Web con JavaScript
Descargas una página de producto con requests, imprimes el HTML y el precio no está. Tampoco el título, ni la valoración, ni el stock. La página se veía bien en tu navegador hace un segundo. Entonces, ¿a dónde fueron los datos?
Se fueron a una segunda petición. Los sitios modernos envían una carcasa HTML casi vacía, luego piden el contenido real en formato JSON a una API interna y lo pintan con JavaScript. Tu cliente HTTP se detiene tras el primer paso. El navegador sigue adelante.
La buena noticia: esa API interna suele ser accesible directamente. Si consigues encontrarla, te saltas todo el problema del renderizado. Sin navegador headless, sin esperar al DOM, sin analizar HTML frágil. Obtienes JSON limpio que a menudo está mejor estructurado que cualquier cosa de la página. Esta guía te muestra cómo encontrarla, cómo replicar la petición y qué hacer cuando el sitio se resiste.
Por Qué los Datos No Están en el HTML
La mayoría de los sitios interactivos son aplicaciones de una sola página construidas con React, Vue, Angular o un framework como Next.js o Nuxt. Cuando cargas la página, el servidor devuelve un esqueleto. Luego el código del lado del cliente se ejecuta en el navegador y llama a uno o varios endpoints del backend para obtener los datos reales.
Puedes confirmarlo en dos segundos. Haz clic derecho en la página y elige “Ver código fuente de la página” (no “Inspeccionar”). Eso muestra el HTML en bruto que recibiría tu cliente HTTP. Si ves un <div id="root"></div> vacío, un muro de etiquetas <script> o un mensaje pidiéndote que actives JavaScript, el contenido se carga después. Los datos que quieres viven detrás de una llamada a la API, no en este documento.
Hay tres lugares habituales de donde proceden esos datos:
- Llamadas XHR/Fetch a un endpoint JSON o GraphQL después de que la página cargue.
- JSON incrustado en el HTML inicial, a menudo en un bloque
<script id="__NEXT_DATA__" type="application/json">o en una asignaciónwindow.__INITIAL_STATE__ = {...}. - Una mezcla: algunos datos incrustados, el resto obtenido a medida que te desplazas o haces clic.
Tu trabajo es averiguar con cuál de ellos estás tratando y luego tomar los datos de la fuente en lugar de la página renderizada.
Paso 1: Abre la Pestaña Network y Filtra por Fetch/XHR
Abre la página en Chrome o Firefox, abre las DevTools (F12) y ve a la pestaña Network (Red). Recarga la página con la pestaña abierta para que capture todo desde el principio.
Verás decenas de entradas: imágenes, fuentes, CSS, píxeles de seguimiento. Ignóralas. Haz clic en el filtro Fetch/XHR para mostrar solo las peticiones que JavaScript hizo en segundo plano. Ahí es donde viven las llamadas a la API.
Ahora interactúa con la página como lo haría un usuario. Desplázate hacia abajo, haz clic en “cargar más”, abre un producto, cambia un filtro. Observa cómo aparecen nuevas filas en la pestaña Network mientras lo haces. Cada fila es una petición que el sitio hizo por ti. Una de ellas lleva tus datos.
Para encontrar la correcta, haz clic en una petición y mira la pestaña Response (Respuesta) o Preview (Vista previa). Estás buscando una respuesta que contenga los valores que viste en la página: el precio, la lista de artículos, el texto de las reseñas. Cuando veas JSON con tus campos dentro, has encontrado el endpoint.
Algunos consejos que ahorran tiempo:
- Ordena por tamaño de respuesta. Los endpoints de datos suelen ser más grandes que los pings y las llamadas de configuración.
- Escribe un valor conocido en el cuadro de filtro de Network (el nombre de un producto, un precio). Chrome puede buscar en los cuerpos de respuesta con el panel de búsqueda (Ctrl+Shift+F dentro de las DevTools).
- Fíjate en el nombre y la ruta de la petición. Endpoints como
/api/v2/products,/graphqlo/_next/data/...jsonson señales claras.
Paso 2: Lee la Petición Antes de Copiarla
Una vez que tengas el endpoint, haz clic en él y estudia la petición en sí, no solo la respuesta. Necesitas reproducirla con la exactitud suficiente para obtener la misma respuesta.
Revisa estas partes en la pestaña Headers (Cabeceras):
- Método y URL. ¿Es GET o POST? Anota cada parámetro de consulta. Parámetros como
page,limit,offset,cursorysortson tus controles para la paginación y el orden. - Cabeceras de la petición. Muchas API comprueban cabeceras que el navegador envía automáticamente. Las más comunes que importan:
Accept: application/json, unReferer, unX-Requested-Withy a veces una cabecera personalizada comoX-Api-KeyoX-Client-Version. - Cuerpo de la petición. Para llamadas POST y GraphQL, el cuerpo contiene la consulta y las variables. Cópialo tal cual para empezar.
- Cookies y tokens. Algunos endpoints necesitan una cookie de sesión o un token bearer que el sitio emitió al cargar la página.
La forma más rápida de conseguir una base que funcione es “Copiar como cURL”. Haz clic derecho en la petición, elige Copiar > Copiar como cURL y pégalo en tu terminal. Si devuelve el mismo JSON, tienes una reproducción fiel. A partir de ahí puedes reducirla.
Paso 3: Replícala en Código y Recorta lo Sobrante
Empieza con la petición completa que copiaste, confirma que funciona y luego elimina cabeceras una a una hasta que deje de funcionar. Lo que quede es el mínimo que realmente necesitas. Esto mantiene tu scraper simple y menos frágil.
Aquí tienes un ejemplo mínimo en Python llamando a un endpoint JSON típico:
import requests
url = "https://example.com/api/v2/products"
params = {"category": "shoes", "page": 1, "limit": 48}
headers = {
"Accept": "application/json",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
"Referer": "https://example.com/category/shoes",
}
resp = requests.get(url, params=params, headers=headers, timeout=20)
data = resp.json()
for item in data["products"]:
print(item["title"], item["price"])
Para un endpoint GraphQL, envía un POST con la consulta y las variables que copiaste:
import requests
payload = {
"query": "query Products($page:Int!){ products(page:$page){ title price } }",
"variables": {"page": 1},
}
resp = requests.post("https://example.com/graphql", json=payload, timeout=20)
print(resp.json()["data"]["products"])
En Node con fetch se ve casi idéntico:
const res = await fetch("https://example.com/api/v2/products?page=1&limit=48", {
headers: { Accept: "application/json", Referer: "https://example.com/category/shoes" },
});
const data = await res.json();
data.products.forEach((p) => console.log(p.title, p.price));
Paso 4: Gestiona la Paginación Desde la API, No Desde la Página
Aquí es donde el acceso directo a la API compensa. La página puede mostrar un botón de “Cargar más” o scroll infinito, pero la API casi siempre expone un control de paginación limpio. Fíjate en cómo cambian los parámetros entre la primera y la segunda petición en la pestaña Network.
Normalmente verás uno de estos tres patrones:
- Números de página:
?page=1,?page=2. Itera hasta obtener una lista vacía. - Offset y limit:
?offset=0&limit=48, luego?offset=48. Incrementa según el límite. - Basado en cursor: cada respuesta incluye un token
nextCursoroendCursorque pasas a la siguiente petición. Síguelo hasta que sea nulo.
La paginación por cursor es habitual en feeds grandes y es la más fiable de las tres. Además, suele esquivar los límites de paginación profunda que rompen los rastreadores basados en números de página en catálogos grandes.
Paso 5: Cuando el JSON Incrustado Es Suficiente
A veces ni siquiera necesitas una segunda petición. Los sitios con frameworks a menudo incrustan el conjunto de datos completo en la primera respuesta HTML. Mira el código fuente de la página y busca __NEXT_DATA__, __NUXT__, __INITIAL_STATE__ o application/ld+json. Si tus datos están ahí, analiza el HTML una vez, extrae el JSON de esa etiqueta script y listo. No hace falta replicar la API.
import json, re, requests
html = requests.get("https://example.com/product/123").text
match = re.search(r'<script id="__NEXT_DATA__"[^>]*>(.*?)</script>', html, re.S)
state = json.loads(match.group(1))
print(state["props"]["pageProps"]["product"]["price"])
Esto es rápido y estable porque estás leyendo los mismos datos que el framework usó para construir la página. Los datos estructurados en application/ld+json son especialmente útiles para productos, artículos y reseñas.
Cuando la API Se Resiste
No todos los endpoints entregan los datos a un script sin más. Las mismas capas anti-bot que protegen la página HTML suelen situarse también delante de la API. Señales de que has topado con una:
- Un
403o429aunque tu navegador cargue el endpoint sin problema. - Un cuerpo HTML con un desafío o CAPTCHA en lugar de JSON.
- Un token de corta duración, un parámetro firmado o una marca de tiempo que caduca en segundos.
- Una respuesta
200que devuelve una carga útil vacía o señuelo. Comprueba siempre la forma del JSON, no solo el código de estado.
Parte de esto lo puedes resolver tú mismo. Rota un User-Agent realista, envía las cabeceras que envía el navegador y reutiliza una cookie de sesión que capturaste una vez en lugar de calentar una nueva en cada llamada. Una librería como curl_cffi ayuda cuando el bloqueo se basa en tu handshake TLS y no en tus cabeceras, porque imita la huella de un navegador real.
Pero cuando un endpoint requiere un token firmado generado por JavaScript ofuscado del lado del cliente, o todo el dominio está detrás de un proveedor anti-bot agresivo, replicar la petición a mano se convierte en un juego perdido de ingeniería inversa que se rompe cada vez que el sitio publica una nueva versión. Ese es el momento en que una API de scraping gana su lugar: envías la URL objetivo, ella gestiona el renderizado del navegador, la huella digital y la rotación de proxies, y recibes la respuesta de vuelta. ScrapeUnblocker está hecho exactamente para ese caso, y sus endpoints analizados pueden entregarte JSON estructurado para que sigas ahorrándote el paso de analizar el HTML.
Preguntas Frecuentes
¿Cómo sé si un sitio usa una API oculta? Mira el código fuente en bruto de la página. Si faltan los valores que quieres y ves un contenedor vacío o un aviso de “activa JavaScript”, los datos se obtienen por separado. Confírmalo observando la pestaña Fetch/XHR en las DevTools mientras la página carga.
¿Es legal llamar a la API interna de un sitio? Estás llamando al mismo endpoint que llama tu navegador, así que es una petición pública. Dicho esto, respeta los términos de servicio del sitio, las indicaciones de robots y los límites de tasa, y recopila solo datos visibles públicamente. La base legal depende de la jurisdicción y del caso de uso, así que trata esto como una respuesta de ingeniería, no como asesoramiento legal.
¿Por qué usar la API en lugar de un navegador headless? Velocidad y estabilidad. Una llamada a la API tarda milisegundos y devuelve datos limpios y tipados. Un navegador headless levanta un renderizado completo de la página en cada petición y te da HTML que aún tienes que analizar. Usa la API cuando puedas y recurre al renderizado solo cuando sea imprescindible.
La API necesita un token que no puedo generar. ¿Y ahora qué? Si el token procede de JavaScript ofuscado, captura una carga de página nueva para leer el token actual, o renderiza la página una vez para obtenerlo y reutiliza la sesión. Si eso es demasiado frágil de mantener, enruta la petición a través de una API de scraping que renderice y te devuelva el resultado.
Para Terminar
La página renderizada es la superficie lenta y frágil de un sitio web. Debajo hay una tubería de datos limpia que el sitio construyó para su propio frontend. Aprender a leer la pestaña Network, replicar la petición y seguir la paginación convierte la mayoría de los sitios “cargados de JavaScript, imposibles de scrapear” en una simple llamada JSON.
Empieza con Ver código fuente y el filtro Fetch/XHR en tu próximo objetivo. Cuando topes con un endpoint bloqueado tras defensas anti-bot serias, ese es el momento de recurrir a una herramienta como ScrapeUnblocker en lugar de pelear con la huella digital a mano.
Prueba ScrapeUnblocker gratis
Tasa de éxito del 95%+ · desde 0,55 € por cada 1000 llamadas · 500 solicitudes gratis al registrarte.