跳到正文
结构化提取2 credits

extract_embedded_state

现代站点会把界面所渲染的数据直接序列化进文档。本工具把它读回来:一次抓取、数值精确,提取链路中没有任何模型。

使用场景

从 JavaScript 渲染的页面取得精确价格

价格、库存和 ID 来自站点自身的状态,而不是由模型阅读渲染后的文本得出,因此无从编造。

普通抓取看不到的列表

客户端渲染的搜索结果和商品网格,通常在首次响应的 __NEXT_DATA__ 或 RSC 负载中就已经存在。

比 LLM 提取更省

2 credits,而 extract_with_llm 或 extract_structured 为 3 credits,而且不必等待推理步骤。

审查站点对自身发布了什么

found 会报告页面上的每一个状态来源及其大小,往往比可见界面所展示的更多。

Endpoint

POST/api/v1/tools/extract_embedded_state
Auth Required
Free 套餐 1 req/s
2 credits

Parameters

NameTypeRequiredDefaultDescription
url
stringRequired-
要读取嵌入状态的页面。
Example: https://example.com/products/widget
path
stringOptional-
只返回一个子树,而不是整个负载。仅支持点号分隔的键和数组索引——这不是 JSONPath,因此没有通配符、过滤器、切片或递归下降。无法解析的路径会返回 400,并列出中断处可用的键,且不消耗 credits。
Example: next_data.props.pageProps
user_agent
stringOptional-
覆盖发送给目标站点的 User-Agent。请求仍以 CrawlForge 的身份签名;签名覆盖的是 authority,而不是该请求头。
Example: MyCompanyBot/1.0
respect_robots
booleanOptionaltrue
遵守目标站点的 robots.txt。保持为 `true` 时,对 `CrawlForge` 禁止的路径会在抓取之前以 403 拒绝,并且不扣除 credits。只有在您与目标站点另有协议时才设为 `false`——此时响应会带有 `warnings` 条目,并且该覆盖会记录在您的 API key 上。
Example: true
timeout
numberOptional20000
抓取超时(毫秒),取值 1000 到 60000。状态负载常常有数兆字节,因此默认值高于更轻量的提取工具。
Example: 20000
keys_only
booleanOptionalfalse
返回 `keys` 而不是 `data`:所选数据(如提供了 `path`,则为其选中的部分)两层深的结构,每个叶子标注为 `object`、`array(<n>)`、`string`、`number`、`boolean` 或 `null`;被选中的数组显示为 `{ length, first }`,其中给出第一个元素的结构。`found` 和 `bytes` 保持不变。先用一次低成本调用看清该请求哪个 `path`,再拉取数兆字节的负载;费用同样是 2 credits。
Example: true
find
stringOptional-
返回 `matches` 而不是 `data`:所选数据(`path` 选中之后)中任意位置键名为此名称(不区分大小写)的每一个属性,按文档顺序排列,每条为 {path, preview},附带其值的前 200 个字符——最多 50 条,并带有 `matches_total` 和 `matches_truncated`。每条匹配路径都已以 `path` 开头,因此可以直接作为 `path` 传回。无需下载负载即可发现某个字段所在的位置。不能与 keys_only 同时使用
Example: specialty
raw
booleanOptionalfalse
同时在 json_scripts 下保留未解码的 __NUXT_DATA__ devalue 数组,与解码后的 nuxt_data 并列。默认值:false
Example: true
escalate
booleanOptionalfalse
选择加入:当普通抓取遇到的是拦截墙而不是页面时——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
stringOptionalplaywright
升级阶段使用的浏览器引擎:"playwright"(默认)或 "camoufox"(基于 Firefox,抗检测能力更强;仅在后端已安装时可用)。除非 `escalate` 为 `true` 且普通抓取被拦下,否则忽略此参数。
Example: camoufox
wait_for
numberOptional-
升级渲染时页面加载后的额外等待时间(毫秒,0-30000),用于页面在 DOMContentLoaded 之后才写入的状态。该值传给 stealth 渲染;除非 `escalate` 为 `true` 且普通抓取被拦下,否则忽略此参数。
Example: 2000
max_inline_chars
numberOptional40000
内联大小阈值,以结果 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-page JSON。
  • 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 时只搜索其内部,并返回该键出现的位置以及每个值的开头部分。

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"
  }'

find 响应

matches 取代 data。每个 path 都已以您发送的 path 开头,因此可以在下一次调用中直接作为 path 传回,以获取完整的值。matches_total 统计全部匹配;当找到的匹配超过返回的 50 条时,matches_truncated 为 true。标记为 addressable: false 的匹配位于含有 . 或 [ 的键之下,路径无法表达。

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
}

请求示例

cURL - 读取完整状态

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 - 用 path 缩小范围

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 - 先发现,再缩小范围

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'])

响应示例

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.found页面上的每一个状态来源,包含读取自的原始对象及其序列化大小
data.path所应用的路径;返回完整负载时为 null
data.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。

相关工具