Saltar al contenido
Extracción estructurada2 credits

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

POST/api/v1/tools/extract_embedded_state
Auth Required
1 req/s en el plan Free
2 credits

Parameters

NameTypeRequiredDefaultDescription
url
stringRequired-
Página de la que se leerá el estado incrustado.
Example: https://example.com/products/widget
path
stringOptional-
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
stringOptional-
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
booleanOptionaltrue
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
numberOptional20000
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
booleanOptionalfalse
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
stringOptional-
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
booleanOptionalfalse
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
booleanOptionalfalse
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
stringOptionalplaywright
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
numberOptional-
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
numberOptional40000
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 de next_f que 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 con path: "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 de kit.start de SvelteKit, una entrada por nodo de ruta, empezando por el layout raíz.
  • yt_initial_data y yt_initial_player_response - ytInitialData y ytInitialPlayerResponse de 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__) y global_data (__data__) - variables globales de window de Apollo, de estilo Redux y otras.
  • inertia_page - el JSON data-page de Inertia.
  • shopify - ShopifyAnalytics.meta y los scripts JSON de producto de Shopify. Los precios de meta son enteros en unidades menores: 10500 es 105.00 en meta.currency.
  • json_scripts - bloques <script type="application/json"> y <script type="text/json">, cada uno { id, data }.
  • json_attributes - atributos data-* 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.

terminalBash
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.

response.jsonJson
{
  "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

terminalBash
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

extractEmbeddedState.tsTypescript
// 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

extract_embedded_state.pyPython
# 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

200 OK980ms
{
"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
}
Field Descriptions
data.foundTodas las fuentes de estado de la página, con el elemento del que se leyeron y su tamaño serializado
data.pathLa ruta aplicada, o null cuando se devolvió toda la carga
data.bytesTamaño serializado de lo que se devuelve, ya acotado cuando se indicó una ruta
data.dataEl estado en sí, indexado por nombre de fuente
data.warningsFuentes presentes pero no analizables como JSON, y la indicación de tamaño que sugiere usar una ruta

Gestió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