extract_ embedded_ state
Los sitios modernos serializan los datos que muestra su interfaz directamente en el documento. Esta herramienta los recupera: una sola descarga, valores exactos y ningún modelo en la ruta de extracción.
Casos de uso
Precios exactos de páginas renderizadas con JavaScript
Los precios, las existencias y los identificadores proceden del propio estado del sitio y no de un modelo que lee el texto renderizado, así que no hay nada que inventar.
Listados que un scraping normal no ve
Los resultados de búsqueda y las cuadrículas de productos que se renderizan en el cliente suelen estar ya presentes en __NEXT_DATA__ o en una carga RSC en la primera respuesta.
Más barato que una extracción con LLM
2 credits frente a 3 de extract_with_llm o extract_structured, y sin ningún paso de inferencia que esperar.
Auditar lo que un sitio publica sobre sí mismo
found informa de todas las fuentes de estado de la página y de su tamaño, que a menudo es más de lo que muestra la interfaz visible.
Endpoint
/api/v1/tools/extract_embedded_stateParameters
Una llamada cuesta 2 credits. Con escalate: true la proyección es el máximo de 7 credits; los 5 credits de la etapa de escalada solo se cobran cuando la descarga simple quedó bloqueada y la etapa se ejecutó de verdad. keys_only y find cuestan los mismos 2 credits.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
url | string | Required | - | Página de la que se leerá el estado incrustado. Example: https://example.com/products/widget |
path | string | Optional | - | Devuelve un solo subárbol en lugar de toda la carga. Únicamente claves separadas por puntos e índices de array: esto no es JSONPath, así que no hay comodines, filtros, segmentos ni descenso recursivo. Una ruta que no se resuelve devuelve un 400 que nombra las claves disponibles en el punto donde se detuvo, y no consume credits. Example: next_data.props.pageProps |
user_agent | string | Optional | - | Sustituye el User-Agent enviado al destino. La solicitud sigue firmada como CrawlForge; la firma cubre la autoridad, no esta cabecera. Example: MyCompanyBot/1.0 |
respect_robots | boolean | Optional | true | Respeta el robots.txt del sitio de destino. Con el valor `true`, una ruta no permitida para `CrawlForge` se rechaza con 403 antes de descargar nada y no se cobran credits. Establézcalo en `false` solo para un destino con el que tenga su propio acuerdo: la respuesta incluirá entonces una entrada `warnings` y la anulación queda registrada en su API key. Example: true |
timeout | number | Optional | 20000 | Tiempo de espera de la descarga en milisegundos, entre 1000 y 60000. Las cargas de estado suelen ocupar megabytes, por lo que el valor predeterminado es mayor que en las herramientas de extracción más ligeras. Example: 20000 |
keys_only | boolean | Optional | false | Devuelve `keys` en lugar de `data`: la forma de los datos seleccionados (después de `path`, si se indica) hasta dos niveles de profundidad, con cada hoja etiquetada como `object`, `array(<n>)`, `string`, `number`, `boolean` o `null`, y un array seleccionado mostrado como `{ length, first }` con la forma de su primer elemento. `found` y `bytes` no cambian. Una llamada económica le indica qué `path` pedir antes de descargar una carga de varios megabytes; cuesta los mismos 2 credits. Example: true |
find | string | Optional | - | Devuelve `matches` en lugar de `data`: cada propiedad con este nombre de clave (sin distinguir mayúsculas y minúsculas) en cualquier lugar de los datos seleccionados (después de `path`), en orden de documento, cada una como {path, preview} con los primeros 200 caracteres de su valor; como máximo 50, con `matches_total` y `matches_truncated`. Cada ruta de coincidencia ya empieza por `path`, así que puedes pasarla de vuelta tal cual como `path`. Descubre dónde está un campo sin descargar la carga. No se puede combinar con keys_only Example: specialty |
raw | boolean | Optional | false | Conserva además el array devalue sin decodificar de __NUXT_DATA__ en json_scripts, junto al nuxt_data decodificado. Valor predeterminado: false Example: true |
escalate | boolean | Optional | false | Active un único reintento automático a través del navegador stealth cuando la descarga simple encuentra un muro en lugar de la página: una página de desafío de Cloudflare, Amazon, DataDome, PerimeterX, Akamai o Vercel, un 403, 429 o 444 sin más, o un armazón vacío que no contiene estado. La descarga simple siempre se ejecuta primero; un 404 o un 5xx nunca escalan. El HTML renderizado pasa por el mismo analizador, de modo que la respuesta tiene la misma forma, más `escalated` (siempre que se indique este parámetro) y `stealth: { engine, vendor_detected }` cuando el renderizado se ejecutó. La escalada reutiliza la misma ruta stealth que [stealth_mode](/docs/api-reference/tools/stealth-mode) y la misma comprobación de robots.txt, y no añade ningún tratamiento nuevo de una defensa antibots; un muro en el que también se rechaza el renderizado sigue devolviéndose como página bloqueada, sin coste. Añade 5 credits, cobrados solo cuando el renderizado se ejecuta de verdad. La lectura de variables globales de `window` (`window_state`) solo está disponible en el servidor MCP: el renderizado de esta API devuelve HTML. Example: true |
escalate_engine | string | Optional | playwright | Motor de navegador para la etapa de escalada: "playwright" (por defecto) o "camoufox" (basado en Firefox, con mayor resistencia a la detección; disponible solo donde esté instalado en el backend). Se ignora salvo que `escalate` sea `true` y la descarga simple haya quedado bloqueada. Example: camoufox |
wait_for | number | Optional | - | Espera adicional tras la carga de la página en el renderizado escalado, en milisegundos (0-30000), para el estado que una página escribe después de DOMContentLoaded. Se pasa al renderizado stealth; se ignora salvo que `escalate` sea `true` y la descarga simple haya quedado bloqueada. Example: 2000 |
max_inline_chars | number | Optional | 40000 | Umbral de tamaño en línea, en caracteres del JSON del resultado (de 1.000 a 10.000.000). Por encima de él, el resultado completo se almacena durante 1 hora y la respuesta conserva `url`, `path`, `bytes`, `found` y `data_keys` (las claves de primer nivel de `data`, o `array(<n>)`), además de `preview` (el inicio del JSON con formato, cortado en un límite de línea para que ningún valor quede partido), `result_handle`, `total_chars` y `truncated: true`. La respuesta completa no supera este tamaño. Lea cualquier campo de la copia almacenada con `json_path` de [read_result](/docs/api-reference/tools/read-result), p. ej. `data.next_data.props`, por 1 credit por llamada. Example: 40000 |
Qué lee
Cada fuente que contiene la página aparece en found y se indexa en data con su nombre. Una variable global que no es JSON simple se lee como un literal de JavaScript evaluado de forma estática (objetos, arrays, cadenas, números y la función de retorno único de Nuxt 2) y nunca se ejecuta código de la página; lo que llama a código o lee otras variables se indica en warnings.
next_data-__NEXT_DATA__de Next.js.next_f- filas flight de React Server Components (self.__next_f), indexadas por id de fila, con las referencias$<id>entre filas resueltas en su sitio (como mucho duplica la carga).data_rows- un índice de las filas denext_fque llevan datos en lugar de marcado, de mayor a menor, cada una{ id, bytes, keys, length }. Los datos de productos y listados suelen estar en estas filas; lee una conpath: "next_f.<id>".nuxt-__NUXT__de Nuxt 2;nuxt_data-__NUXT_DATA__de Nuxt 3, decodificado de su array de índices a objetos simples.sveltekit_data- los datos dekit.startde SvelteKit, una entrada por nodo de ruta, empezando por el layout raíz.yt_initial_datayyt_initial_player_response-ytInitialDatayytInitialPlayerResponsede YouTube.apollo_state,apollo_ssr_transport,initial_state,preloaded_state,preloaded_data,remix_context(__remixContext),tgt_data(__TGT_DATA__),pws_data(__PWS_DATA__),server_data(__SERVER_DATA__),app_state(__APP_STATE__),state(__STATE__) yglobal_data(__data__) - variables globales dewindowde Apollo, de estilo Redux y otras.inertia_page- el JSONdata-pagede Inertia.shopify-ShopifyAnalytics.metay los scripts JSON de producto de Shopify. Los precios de meta son enteros en unidades menores: 10500 es 105.00 enmeta.currency.json_scripts- bloques<script type="application/json">y<script type="text/json">, cada uno{ id, data }.json_attributes- atributosdata-*que contienen un objeto JSON de 2 KB o más, cada uno{ tag, attribute, id, data }.
Encontrar una ruta
En una carga de varios megabytes, pide la clave que quieres en lugar de los datos. find busca en toda la carga, o solo dentro de path si lo envías, y devuelve dónde aparece la clave con el comienzo de cada valor.
curl -X POST https://crawlforge.dev/api/v1/tools/extract_embedded_state \
-H "X-API-Key: cf_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"path": "yt_initial_player_response",
"find": "viewCount"
}'Respuesta de find
matches sustituye a data. Cada path ya empieza por el path que enviaste, así que pásalo tal cual como path en la siguiente llamada para obtener el valor completo. matches_total cuenta todas las coincidencias; matches_truncated es true cuando se encontraron más de las 50 devueltas. Una coincidencia marcada addressable: false está bajo una clave que contiene . o [, algo que una ruta no puede expresar.
{
"success": true,
"data": {
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"found": [
{ "name": "yt_initial_data", "variable": "ytInitialData", "bytes": 21239 },
{ "name": "yt_initial_player_response", "variable": "ytInitialPlayerResponse", "bytes": 8076 }
],
"path": "yt_initial_player_response",
"bytes": 8076,
"matches": [
{
"path": "yt_initial_player_response.videoDetails.viewCount",
"preview": "\"1822707973\""
},
{
"path": "yt_initial_player_response.microformat.playerMicroformatRenderer.viewCount",
"preview": "\"1822707973\""
}
],
"matches_total": 2,
"matches_truncated": false,
"warnings": []
},
"credits_used": 2,
"credits_remaining": 996,
"processing_time": 640
}Ejemplos de solicitud
cURL - Leer todo el estado
curl -X POST https://crawlforge.dev/api/v1/tools/extract_embedded_state \
-H "X-API-Key: cf_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/products/widget"
}'TypeScript - Acotarlo con una ruta
// npm install crawlforge-sdk
import { CrawlForge, ValidationError } from 'crawlforge-sdk';
const client = new CrawlForge({ apiKey: process.env.CRAWLFORGE_API_KEY });
try {
const result = await client.extractEmbeddedState({
url: 'https://example.com/products/widget',
// Dotted keys and array indexes. Not JSONPath: no wildcards or filters.
path: 'next_data.props.pageProps',
});
// result.data is untyped in crawlforge-sdk 0.1 — its shape is the Response Example below.
const { found, data } = result.data as {
found: { name: string; variable: string; bytes: number }[];
data: { product: { price: number } };
};
// Every state source on the page, largest first, whether or not it was scoped.
for (const source of found) {
console.log(source.name, source.variable, source.bytes);
}
// The values are the site's own, so they can be used as they are.
console.log(data.product.price);
} catch (err) {
// A path that does not resolve is a 400 naming the keys that were available.
if (err instanceof ValidationError) {
console.error(err.message);
} else {
throw err;
}
}Python - Descubrir y luego acotar
# pip install crawlforge
from crawlforge import CrawlForge
client = CrawlForge() # reads CRAWLFORGE_API_KEY
# 1. Discover what the page carries. Nothing is truncated, so this can be big.
discovery = client.extract_embedded_state(
url='https://example.com/products/widget',
)
# discovery.data is a plain dict — its shape is the Response Example below.
for source in discovery.data['found']:
print(source['name'], source['variable'], source['bytes'])
# This tool's warnings live inside data, next to the state itself.
for warning in discovery.data['warnings']:
print('warning:', warning)
# 2. Ask again for just the branch you want.
scoped = client.extract_embedded_state(
url='https://example.com/products/widget',
path='next_data.props.pageProps.product',
)
print(scoped.data['bytes'], 'bytes')
print(scoped.data['data'])Ejemplo de respuesta
{ "success": true, "data": { "url": "https://example.com/products/widget", "found": [ { "name": "next_data", "variable": "__NEXT_DATA__", "bytes": 125 } ], "path": null, "bytes": 139, "data": { "next_data": { "buildId": "KfC_3GF1zuM", "props": { "pageProps": { "product": { "sku": "WID-9001", "price": 149.99, "currency": "USD", "inStock": true } } } } }, "warnings": [] }, "credits_used": 2, "credits_remaining": 998, "processing_time": 980}data.foundTodas las fuentes de estado de la página, con el elemento del que se leyeron y su tamaño serializadodata.pathLa ruta aplicada, o null cuando se devolvió toda la cargadata.bytesTamaño serializado de lo que se devuelve, ya acotado cuando se indicó una rutadata.dataEl estado en sí, indexado por nombre de fuentedata.warningsFuentes presentes pero no analizables como JSON, y la indicación de tamaño que sugiere usar una rutaGestión de errores
Parámetros no válidos (400 Bad Request)
VALIDATION_ERROR, con el problema en details: por ejemplo, find enviado junto con keys_only (cada uno sustituye data por su propia respuesta, así que envía uno por llamada) o un find vacío o de más de 100 caracteres. No se descarga nada y no se cobran credits.
La ruta no se resolvió (400 Bad Request)
El valor de path no existe en el estado extraído. El mensaje nombra el punto donde se detuvo y las claves disponibles allí, de modo que un error tipográfico se puede corregir. No se cobran credits. Ejecute la llamada una vez sin path para ver qué contiene realmente la página.
Bloqueado por robots.txt (403 Forbidden)
El robots.txt del sitio de destino no permite esta ruta a CrawlForge. Establezca respect_robots: false para anularlo si tiene su propio acuerdo con el destino: la anulación queda registrada en su API key. La anulación no alcanza a un host incluido en la lista de exclusión permanente de CrawlForge, que se rechaza sea cual sea el valor de respect_robots.
Respuesta demasiado grande (413 Payload Too Large)
El HTML de la página supera el límite de 25MB de cuerpo. El límite se aplica a la página tal como se sirve, no al estado extraído de ella. No se cobra nada.
El destino no respondió (504 Gateway Timeout)
El sitio no respondió dentro de timeout. Las páginas con mucho estado son grandes; suba timeout hacia su techo de 60000 antes de considerarlo un fallo. No se cobra nada.
Página bloqueada (502 Bad Gateway)
La descarga simple encontró una página de desafío antibots (BLOCKED, con el proveedor en blocked) o un armazón vacío sin estado (UNUSABLE_DOCUMENT). No se cobra nada, y el mensaje indica el siguiente paso: repita la llamada con escalate: true. Si escalate: true ya estaba activado, el mismo error incluye escalated: true (el renderizado stealth también fue rechazado) y no se cobra nada.
Backend de escalada no configurado (503 TOOL_NOT_AVAILABLE)
escalate: true ejecuta su etapa stealth en el backend de ejecución de CrawlForge. Cuando ese backend no está configurado, la etapa devuelve 503 y no se cobra ningún credit.
Herramientas relacionadas
¿Listo para leer los propios datos de una página? Regístrese gratis y obtenga 1,000 credits.