使用场景
从 JavaScript 渲染的页面取得精确价格
价格、库存和 ID 来自站点自身的状态,而不是由模型阅读渲染后的文本得出,因此无从编造。
普通抓取看不到的列表
客户端渲染的搜索结果和商品网格,通常在首次响应的 __NEXT_DATA__ 或 RSC 负载中就已经存在。
比 LLM 提取更省
2 credits,而 extract_with_llm 或 extract_structured 为 3 credits,而且不必等待推理步骤。
审查站点对自身发布了什么
found 会报告页面上的每一个状态来源及其大小,往往比可见界面所展示的更多。
Endpoint
/api/v1/tools/extract_embedded_stateParameters
每次调用 2 credits。设置 escalate: true 时,预估上限为 7 credits;只有当普通抓取被拦下且升级阶段真正执行时,才会收取升级阶段的 5 credits。keys_only 和 find 同样是 2 credits。
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
url | string | Required | - | 要读取嵌入状态的页面。 Example: https://example.com/products/widget |
path | string | Optional | - | 只返回一个子树,而不是整个负载。仅支持点号分隔的键和数组索引——这不是 JSONPath,因此没有通配符、过滤器、切片或递归下降。无法解析的路径会返回 400,并列出中断处可用的键,且不消耗 credits。 Example: next_data.props.pageProps |
user_agent | string | Optional | - | 覆盖发送给目标站点的 User-Agent。请求仍以 CrawlForge 的身份签名;签名覆盖的是 authority,而不是该请求头。 Example: MyCompanyBot/1.0 |
respect_robots | boolean | Optional | true | 遵守目标站点的 robots.txt。保持为 `true` 时,对 `CrawlForge` 禁止的路径会在抓取之前以 403 拒绝,并且不扣除 credits。只有在您与目标站点另有协议时才设为 `false`——此时响应会带有 `warnings` 条目,并且该覆盖会记录在您的 API key 上。 Example: true |
timeout | number | Optional | 20000 | 抓取超时(毫秒),取值 1000 到 60000。状态负载常常有数兆字节,因此默认值高于更轻量的提取工具。 Example: 20000 |
keys_only | boolean | Optional | false | 返回 `keys` 而不是 `data`:所选数据(如提供了 `path`,则为其选中的部分)两层深的结构,每个叶子标注为 `object`、`array(<n>)`、`string`、`number`、`boolean` 或 `null`;被选中的数组显示为 `{ length, first }`,其中给出第一个元素的结构。`found` 和 `bytes` 保持不变。先用一次低成本调用看清该请求哪个 `path`,再拉取数兆字节的负载;费用同样是 2 credits。 Example: true |
find | string | Optional | - | 返回 `matches` 而不是 `data`:所选数据(`path` 选中之后)中任意位置键名为此名称(不区分大小写)的每一个属性,按文档顺序排列,每条为 {path, preview},附带其值的前 200 个字符——最多 50 条,并带有 `matches_total` 和 `matches_truncated`。每条匹配路径都已以 `path` 开头,因此可以直接作为 `path` 传回。无需下载负载即可发现某个字段所在的位置。不能与 keys_only 同时使用 Example: specialty |
raw | boolean | Optional | false | 同时在 json_scripts 下保留未解码的 __NUXT_DATA__ devalue 数组,与解码后的 nuxt_data 并列。默认值:false Example: true |
escalate | boolean | Optional | false | 选择加入:当普通抓取遇到的是拦截墙而不是页面时——Cloudflare、Amazon、DataDome、PerimeterX、Akamai 或 Vercel 的挑战页、单纯的 403、429 或 444,或不含任何状态的空壳页面——自动通过 stealth 浏览器重试一次。普通抓取始终先执行;404 或 5xx 永远不会触发升级。渲染出的 HTML 交给同一个解析器处理,因此响应结构不变,另外只要设置了此参数就会带上 `escalated`,渲染实际执行时还会带上 `stealth: { engine, vendor_detected }`。升级复用与 [stealth_mode](/docs/api-reference/tools/stealth-mode) 相同的 stealth 路径和同一道 robots.txt 校验,不会新增任何针对反爬防护的处理手段;若渲染同样被拒,结果仍作为被拦截的页面返回,且不计费。仅在渲染真正执行时加收 5 credits。读取 `window` 全局变量(`window_state`)仅在 MCP 服务器上可用:本 API 的渲染只返回 HTML。 Example: true |
escalate_engine | string | Optional | playwright | 升级阶段使用的浏览器引擎:"playwright"(默认)或 "camoufox"(基于 Firefox,抗检测能力更强;仅在后端已安装时可用)。除非 `escalate` 为 `true` 且普通抓取被拦下,否则忽略此参数。 Example: camoufox |
wait_for | number | Optional | - | 升级渲染时页面加载后的额外等待时间(毫秒,0-30000),用于页面在 DOMContentLoaded 之后才写入的状态。该值传给 stealth 渲染;除非 `escalate` 为 `true` 且普通抓取被拦下,否则忽略此参数。 Example: 2000 |
max_inline_chars | number | Optional | 40000 | 内联大小阈值,以结果 JSON 的字符数计(1,000-10,000,000)。超过此值时,完整结果会存储 1 小时,响应保留 `url`、`path`、`bytes`、`found` 和 `data_keys`(`data` 的顶层键,或 `array(<n>)`),并附带 `preview`(格式化 JSON 的开头部分,在行边界处截断,因此任何值都不会被截成两半)、`result_handle`、`total_chars` 和 `truncated: true`。整个响应不会超过此大小。可用 [read_result](/docs/api-reference/tools/read-result) 的 `json_path` 读取已存储副本中的任意字段,例如 `data.next_data.props`,每次调用 1 credit。 Example: 40000 |
读取的内容
页面携带的每个来源都会列在 found 中,并在 data 中以其名称为键。不是纯 JSON 的全局变量会作为 JavaScript 字面量静态求值(对象、数组、字符串、数字以及 Nuxt 2 只含一条 return 的函数),绝不会运行页面代码;凡是调用代码或读取其他变量的值,都会在 warnings 中注明。
next_data- Next.js 的__NEXT_DATA__。next_f- React Server Component 的 flight 行(self.__next_f),以行 id 为键,行与行之间的$<id>引用已就地解析(负载最多翻倍)。data_rows-next_f中承载数据而非标记的行的索引,按大小从大到小排列,每条为{ id, bytes, keys, length }。商品和列表数据通常就在这些行中;用path: "next_f.<id>"读取其中一行。nuxt- Nuxt 2 的__NUXT__;nuxt_data- Nuxt 3 的__NUXT_DATA__,已从索引数组解码为普通对象。sveltekit_data- SvelteKit 的kit.start数据,每个路由节点一条,根布局在前。yt_initial_data和yt_initial_player_response- YouTube 的ytInitialData和ytInitialPlayerResponse。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__)和global_data(__data__) - Apollo、Redux 风格及其他window全局变量。inertia_page- Inertia 的data-pageJSON。shopify-ShopifyAnalytics.meta和 Shopify 的商品 JSON 脚本。meta 中的价格是以最小货币单位表示的整数:10500 即meta.currency中的 105.00。json_scripts-<script type="application/json">和<script type="text/json">块,每个为{ id, data }。json_attributes- 含有 2 KB 及以上 JSON 对象的data-*属性,每个为{ tag, attribute, id, data }。
查找路径
面对数兆字节的负载时,请求您想要的键,而不是数据本身。find 会搜索整个负载,或在您发送 path 时只搜索其内部,并返回该键出现的位置以及每个值的开头部分。
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"
}'find 响应
matches 取代 data。每个 path 都已以您发送的 path 开头,因此可以在下一次调用中直接作为 path 传回,以获取完整的值。matches_total 统计全部匹配;当找到的匹配超过返回的 50 条时,matches_truncated 为 true。标记为 addressable: false 的匹配位于含有 . 或 [ 的键之下,路径无法表达。
{
"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
}请求示例
cURL - 读取完整状态
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 - 用 path 缩小范围
// 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 - 先发现,再缩小范围
# 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'])响应示例
{ "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.found页面上的每一个状态来源,包含读取自的原始对象及其序列化大小data.path所应用的路径;返回完整负载时为 nulldata.bytes所返回内容的序列化大小——给出 path 时即为缩小后的大小data.data状态本身,按来源名称索引data.warnings存在但无法按 JSON 解析的来源,以及建议使用 path 的大小提示错误处理
参数无效 (400 Bad Request)
VALIDATION_ERROR,问题写在 details 中:例如 find 与 keys_only 同时发送——两者都会用各自的结果替代 data,因此每次调用只发送其中一个——或 find 为空或超过 100 个字符。不会抓取任何内容,也不扣除 credits。
路径无法解析 (400 Bad Request)
path 在所提取的状态中不存在。消息会指出中断的位置以及该处可用的键,因此拼写错误可以直接修正。不扣除 credits。先不带 path 调用一次,即可看到页面实际携带的内容。
被 robots.txt 阻止 (403 Forbidden)
目标站点的 robots.txt 禁止 CrawlForge 访问此路径。如果您与目标站点另有协议,可设置 respect_robots: false 覆盖——该覆盖会记录在您的 API key 上。此覆盖对 CrawlForge 永久排除名单上的主机无效,无论 respect_robots 取何值都会被拒绝。
响应过大 (413 Payload Too Large)
页面 HTML 超过 25MB 的响应体上限。该上限针对服务端返回的页面本身,而非从中提取的状态。不收取任何费用。
目标未响应 (504 Gateway Timeout)
站点未在 timeout 内响应。状态密集的页面体积很大;在判定为失败之前,请先把 timeout 调高至 60000 的上限。不收取任何费用。
页面被拦截 (502 Bad Gateway)
普通抓取遇到了反爬挑战页(BLOCKED,blocked 中给出厂商)或不含状态的空壳页面(UNUSABLE_DOCUMENT)。不扣除任何费用,消息会给出下一步:使用 escalate: true 重新调用。若已设置 escalate: true,同一错误会带上 escalated: true——stealth 渲染同样被拒——且不扣除任何费用。
升级后端未配置 (503 TOOL_NOT_AVAILABLE)
escalate: true 的 stealth 阶段运行在 CrawlForge 执行后端上。该后端未配置时,升级阶段返回 503,且不扣除任何 credits。
相关工具
准备好读取页面自己的数据了吗?免费注册,即可获得 1,000 credits。