Abre una página web moderna, observa la actividad de red y verás a menudo que los datos que buscas llegan como JSON antes de que la página los dibuje. Los precios, listados o resultados que un scraper extraería laboriosamente del HTML están ahí, en una respuesta limpia y estructurada que la propia página ha obtenido para sí misma.
Recopilar datos desde esa respuesta en lugar de la página renderizada puede ser más pequeño, más estable y mucho más fácil de analizar. Pero no es tan simple como copiar una URL, y hay dos cosas que sorprenden a la mayoría de los equipos: qué poco del JSON de una página son realmente datos, y con qué frecuencia el endpoint de datos se niega a funcionar fuera de la página que lo llamó. Esta guía muestra cómo encontrar el endpoint correcto, qué medimos en páginas reales, y cómo recopilar datos desde él sin sobrepasar los límites.
Conclusiones clave
- La mayor parte del JSON que carga una página no son datos. En la página de búsqueda de vuelos de una aerolínea, las tarifas llegaron en una única respuesta de 44,9 KB, aproximadamente el 8% del JSON de la página y aproximadamente el 1,2% de su transferencia total de 3,6 MB.
- Algunas páginas no tienen ninguna API de datos. En la página de previsión de un servicio meteorológico nacional, todas las respuestas JSON pertenecían a la herramienta de consentimiento de cookies, y la previsión estaba en el HTML.
- Encuentra el endpoint buscando en los cuerpos de las respuestas un valor que puedas ver en la página, no adivinando a partir de las URL.
- Las API de página suelen estar vinculadas a la sesión de la página. El endpoint de tarifas de la aerolínea funcionaba dentro de la página y devolvía un HTTP 409 cuando se llamaba directamente.
- Usa solo los endpoints públicos que la página llama para visitantes anónimos, al ritmo al que navegaría una persona, y prefiere una API oficial siempre que exista.
Qué medimos
Cargamos dos páginas públicas en un navegador real el 30 de septiembre de 2026 y registramos todas las respuestas.
| Búsqueda de vuelos de aerolínea | Previsión meteorológica | |
|---|---|---|
| Solicitudes | 61 | 46 |
| Total transferido | 3,62 MB | 2,57 MB |
| Respuestas JSON | 15, 534 KB | 3, 1,04 MB |
| Respuestas JSON que contenían los datos | 1, 44,9 KB | 0 |
| Dónde estaban los datos | Un endpoint de tarifas | El HTML renderizado en el servidor |
En la página de la aerolínea, las respuestas JSON más grandes no eran tarifas en absoluto. Un servicio de feature-flags de terceros devolvió la misma respuesta de 97 KB cuatro veces, y un paquete de traducción añadió otros 92 KB. Las tarifas llegaron en una única respuesta de la propia API de reservas de la aerolínea.
En la página del tiempo, las tres respuestas JSON procedían de la herramienta de gestión de consentimiento, una de ellas una lista de proveedores de 860 KB, mientras que la propia previsión se renderizaba en el HTML en el servidor. “Recopilar del JSON” no habría recopilado nada útil.
Encontrar el endpoint que contiene los datos
El método fiable es buscar, no adivinar. Elige un valor que puedas ver en la página, como un precio, un nombre de producto o un identificador, carga la página en un navegador real y encuentra qué respuesta JSON lo contiene.
A mano, esto se hace con las herramientas de desarrollador del navegador: abre la pestaña Network, filtra por Fetch/XHR, recarga y busca el valor en los cuerpos de las respuestas. De forma automatizada, es un script corto:
import { chromium } from 'playwright';
// Load a page once and report which JSON responses contain a value you can see on it.
export async function findEndpoint(url, needle, waitMs = 20000) {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
const hits = [];
page.on('response', async (response) => {
const type = response.request().resourceType();
const contentType = response.headers()['content-type'] || '';
if (!['xhr', 'fetch'].includes(type) || !contentType.includes('json')) return;
try {
const body = await response.text();
if (body.includes(needle)) {
hits.push({
method: response.request().method(),
status: response.status(),
bytes: Buffer.byteLength(body),
url: response.url(),
});
}
} catch {
// Some responses (redirects, aborted requests) have no readable body.
}
});
// Analytics beacons keep many pages from ever going network-idle,
// so wait for the DOM, then until a match appears or the time runs out.
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 90000 });
for (let waited = 0; waited < waitMs && hits.length === 0; waited += 500) {
await page.waitForTimeout(500);
}
await page.waitForTimeout(1000);
await browser.close();
return hits;
}
Dada la página de búsqueda de vuelos de la aerolínea y una tarifa visible en ella, el script devolvió exactamente una coincidencia de 61 solicitudes: la respuesta de disponibilidad de la API de reservas. Fíjate en la estrategia de espera. Nuestra primera versión esperaba a que la página llegara a network-idle, cosa que nunca ocurría, porque las balizas de analítica seguían disparándose; agotó el tiempo tras 90 segundos. Esperar al DOM y luego a una coincidencia es más fiable.
¿Se puede llamar directamente?
A menudo no. Cuando solicitamos el mismo endpoint de disponibilidad de la aerolínea directamente, sin la página, devolvió HTTP 409 para solicitudes desde seis países distintos por igual, mientras que la propia página lo cargaba sin problemas. Las API de página suelen depender de cosas que la página establece primero: cookies de sesión, tokens incrustados en el HTML, cabeceras de solicitud o una secuencia de llamadas previas.
Eso deja dos enfoques:
- Dejar que la página haga la llamada. Carga la página en un navegador y lee la respuesta JSON según llega, como hace el script anterior. Obtienes los datos limpios sin reconstruir la sesión, a costa de ejecutar un navegador.
- Reproducir solo endpoints simples y públicos. Algunos endpoints no necesitan nada más que una URL. Esa misma aerolínea también publica un endpoint público de tarifas que responde a solicitudes sencillas, que usamos en una prueba aparte sobre si los precios cambian según el país de salida. Cuando un endpoint funciona con esa sencillez, es con diferencia la opción más económica.
Lo que no hay que hacer es sortear la sesión: recolectar tokens, falsificar cabeceras o reproducir llamadas autenticadas para acceder a datos que el sitio no puso a disposición de visitantes anónimos. Ahí es donde la recopilación deja de ser observación.
Por qué merece la pena el JSON cuando está disponible
Cuando los datos sí llegan como JSON, las ventajas son reales:
- Tamaño. La respuesta de tarifas pesaba 44,9 KB frente a 3,6 MB de la página completa. En una recopilación facturada por ancho de banda, esa diferencia es la mayor parte de la factura, como muestra el coste por registro limpio.
- Estructura. Los campos llegan nombrados y tipados, sin selectores que escribir ni mantener.
- Completitud. Las respuestas a menudo contienen más de lo que la página muestra, como identificadores, todas las clases de tarifa o indicadores de existencias.
Las contrapartidas son igual de reales. Los endpoints no documentados cambian sin previo aviso, y una respuesta que cambia de forma rompe tu analizador en silencio, que es exactamente el problema para el que existe la monitorización de schema drift. Un número de versión en la ruta, como el v4 en la API de reservas de la aerolínea, es una señal leve de estabilidad, no una promesa.
Dónde encaja esto entre las alternativas
| Fuente | Estabilidad | Esfuerzo | Cuándo usarla |
|---|---|---|---|
| API oficial y documentada | Máxima | Mínimo | Comprobar siempre primero |
| JSON-LD o estado de página incrustado | Alta | Bajo | La página lo publica, ver deja de analizar HTML |
| Los endpoints JSON propios de la página | Media | Medio | Los datos se cargan después de la página, y el endpoint es público |
| HTML renderizado y selectores | Mínima | Máximo | Nada más contiene los datos |
| Un modelo que lee la página | Variable | Bajo por sitio, alto por página | Muchas plantillas, como en extracción con LLM frente a selectores |
Recopilar de forma responsable
Los endpoints que llama una página forman parte del sitio, y las normas del sitio siguen siendo aplicables. Usa solo los endpoints que la página llama para visitantes anónimos, marca el ritmo de las solicitudes como lo haría una persona navegando, respeta el robots.txt y los términos del sitio, y deja en paz cualquier cosa que esté tras un inicio de sesión o un token a menos que tengas permiso. Cuando un sitio ofrezca una API oficial, úsala; es más estable, y es lo que el sitio ha aceptado respaldar. Los principios más generales están en robots.txt, exclusiones de IA y señales de reserva.
En resumen
Los datos detrás de una página moderna a menudo llegan como JSON, y cuando es así, recopilarlos de ahí es más pequeño, más limpio y más fácil de mantener que analizar HTML. Pero encontrarlo requiere una búsqueda, no una suposición: en las páginas que medimos, el JSON útil era una respuesta entre muchas, y en una página directamente no existía.
Busca en los cuerpos de las respuestas un valor que puedas ver, comprueba si el endpoint funciona por sí solo, deja que la página haga la llamada cuando no funcione, y mantente dentro de lo que el sitio ofrece a cualquier visitante anónimo.
Fuentes y referencias
- Páginas cargadas y medidas por Shifter el 30 de septiembre de 2026, usando el código anterior.
- Playwright, documentación de eventos de red.