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
| 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 |
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
const response = await fetch('https://crawlforge.dev/api/v1/tools/extract_embedded_state', {
method: 'POST',
headers: {
'X-API-Key': process.env.CRAWLFORGE_API_KEY!,
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: 'https://example.com/products/widget',
// Dotted keys and array indexes. Not JSONPath: no wildcards or filters.
path: 'next_data.props.pageProps',
}),
});
const payload = await response.json();
if (!response.ok) {
// A path that does not resolve is a 400 naming the keys that were available.
throw new Error(payload.error.code + ': ' + payload.error.message);
}
const result = payload.data;
// Every state source on the page, largest first, whether or not it was scoped.
for (const source of result.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(result.data.product.price);Python - Descubrir y luego acotar
import os
import requests
URL = 'https://crawlforge.dev/api/v1/tools/extract_embedded_state'
HEADERS = {
'X-API-Key': os.environ['CRAWLFORGE_API_KEY'],
'Content-Type': 'application/json',
}
# 1. Discover what the page carries. Nothing is truncated, so this can be big.
discovery = requests.post(
URL,
headers=HEADERS,
json={'url': 'https://example.com/products/widget'},
).json()
for source in discovery['data']['found']:
print(source['name'], source['variable'], source['bytes'])
for warning in discovery['data']['warnings']:
print('warning:', warning)
# 2. Ask again for just the branch you want.
scoped = requests.post(
URL,
headers=HEADERS,
json={
'url': 'https://example.com/products/widget',
'path': 'next_data.props.pageProps.product',
},
)
payload = scoped.json()
if not scoped.ok:
error = payload['error']
raise RuntimeError(f"{error['code']}: {error['message']}")
print(payload['data']['bytes'], 'bytes')
print(payload['data']['data'])Ejemplo de respuesta
{ "success": true, "data": { "url": "https://example.com/products/widget", "found": [ { "name": "next_data", "variable": "__NEXT_DATA__", "bytes": 412880 } ], "path": null, "bytes": 412893, "data": { "next_data": { "buildId": "KfC_3GF1zuM", "props": { "pageProps": { "product": { "sku": "WID-9001", "price": 149.99, "currency": "USD", "inStock": true } } } } }, "warnings": [ "Result is 412893 bytes; \"next_data\" alone is 412880. Re-run with path to scope it, e.g. path:\"next_data.props\"." ] }, "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
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.