CrawlForge MCP
基础工具结果句柄1 credit

read_result

读回因体积过大而无法内联返回的结果。当某个工具的 JSON 超过 max_inline_chars 时,其响应会携带 preview、result_handle 和 truncated: true;把该句柄传给本工具,即可对文本切片、搜索、按行翻阅或读取单个 JSON 路径——无需重新抓取页面,也无需再付一次工具的费用。

使用场景

找到你需要的章节

先用 search 查找标题或短语,再从匹配的 offset 开始 slice——两次调用读取长页面中的一个章节,而不是整个结果。

翻阅长文本

lines 返回带 has_more 的行窗口,因此大型 markdown 文档可以按你的上下文容得下的窗口逐段读取。

从大型 JSON 中取出一个字段

json_path 从已存储的 crawl_deep、batch_scrape 或 deep_research 结果中读取单个路径,也可以读取 JSON 格式 fetch_url 的解析后正文。

绝不重复抓取

已存储的结果就是你已经付过费的那份。读取它每次 1 credit;重新运行工具则要付全价,并再次访问站点。

Endpoint

POST/api/v1/tools/read_result
Auth Required
Free 计划 1 req/s
1 credit

Parameters

handle 来自带 truncated: true 的响应——本工具从不抓取页面,只读取你已经拥有的结果。句柄在结果存储 1 小时后过期。
NameTypeRequiredDefaultDescription
handle
stringRequired-
来自截断响应的 `result_handle`(`res_` 加一个 UUID)。仅创建它的账户可读,有效期 1 小时。
Example: res_9f2c1e6a-4b7d-4c3e-8a5f-1d2e3f4a5b6c
operation
stringRequired-
`slice` 返回文本视图的一段字符范围;`search` 查找不区分大小写的字面子串(绝不是正则表达式)并返回每个带上下文的匹配;`lines` 返回一个行窗口;`json_path` 从已存储的 JSON 中读取一个路径。
Example: search
offset
numberOptional0
`slice`:返回的第一个字符。`lines`:返回的第一行的索引。
Example: 18240
length
numberOptional10000
`slice`:返回的字符数(默认 10,000)。`lines`:返回的行数(默认 200,最多 5,000)。
Example: 4000
query
stringOptional-
仅用于 `search`,且在其中必填:要查找的字面文本,不区分大小写。
Example: rate limits
max_matches
numberOptional20
`search`:返回的最大匹配数,1-100。响应中的 `truncated: true` 表示匹配数多于返回数。
Example: 5
path
stringOptional-
仅用于 `json_path`,且在其中必填:点分键名和数组索引,点号或方括号形式均可(`pages.0.url` 或 `pages[0].url`);不支持通配符、过滤器或切片。从已存储的结果对象中读取——当已存储的文本是 JSON(例如 `fetch_url` 的正文)时,则从解析后的正文中读取。
Example: pages.0.url
max_inline_chars
numberOptional40000
内联返回的最大文本量,1,000-10,000,000 个字符;每个操作返回的文本都以此为上限。对 `json_path`,更大的 `value` 会以 `value: null` 返回,附带 `preview`、`truncated: true` 和一条建议收窄路径的警告。
Example: 40000

操作

每个响应都携带 handle、tool、operation、view、view_path、total_chars 和 expires_at,然后是你所请求操作的字段。

slice
文本视图的一段字符范围:offset、length、text 和 has_more。默认为前 10,000 个字符。
search
不区分大小写的字面子串匹配,绝不是正则表达式:query、matches(每项带 offset、length、context_offset 和 context——前后各 200 个字符)、total_matches 和 truncated。
lines
一个行窗口:first_line、line_count、total_lines、char_offset、lines 和 has_more。offset 是第一行的索引,length 是行数(默认 200,最多 5,000)。
json_path
已存储结果对象中的一个路径:path、value 和 value_chars。超过 max_inline_chars 时,值会被替换为 value: null、一个 preview 和 truncated: true,并附带一条建议收窄路径的警告。

已存储的结果放在哪里

在 REST API 上,已存储的结果是按账户保存的值,保留 1 小时,仅创建它的账户可读。在自托管的 MCP 服务器上,存储位于你自己机器上的 ~/.crawlforge/results/(TTL 1 小时,200 MB LRU),不会上传任何内容。错误响应从不存储,batch_scrape 任务共用同一个存储。

请求示例

cURL

terminalBash
# 1. A scrape whose JSON exceeded max_inline_chars (default 40,000) came back
#    with a preview instead of the markdown:
#      "truncated": true,
#      "result_handle": "res_9f2c1e6a-4b7d-4c3e-8a5f-1d2e3f4a5b6c",
#      "total_chars": 182406

# 2. Search the stored result for the section you need (1 credit)
curl -X POST https://crawlforge.dev/api/v1/tools/read_result \
  -H "X-API-Key: cf_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "handle": "res_9f2c1e6a-4b7d-4c3e-8a5f-1d2e3f4a5b6c",
    "operation": "search",
    "query": "rate limits",
    "max_matches": 5
  }'

# 3. Read the section at the first match offset (1 credit) — no second fetch
curl -X POST https://crawlforge.dev/api/v1/tools/read_result \
  -H "X-API-Key: cf_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "handle": "res_9f2c1e6a-4b7d-4c3e-8a5f-1d2e3f4a5b6c",
    "operation": "slice",
    "offset": 18240,
    "length": 4000
  }'

TypeScript

readResult.tsTypescript
// npm install crawlforge-sdk
import { CrawlForge } from 'crawlforge-sdk';

const client = new CrawlForge({ apiKey: process.env.CRAWLFORGE_API_KEY });

// 1. Scrape a long page. Over max_inline_chars (default 40,000 characters of
//    JSON) the markdown is replaced by a preview and a result_handle.
const page = await client.scrape({
  url: 'https://example.com/docs/api',
  formats: ['markdown'],
});

// page.data is untyped in crawlforge-sdk 0.1 — a truncated result carries the
// handle fields shown in the cURL tab instead of the markdown.
const scraped = page.data as
  | { truncated: true; result_handle: string; total_chars: number }
  | { truncated?: false; formats: { markdown: string } };

if (scraped.truncated) {
  const { result_handle, total_chars } = scraped;
  console.log('Stored ' + total_chars + ' characters as ' + result_handle);

  // 2. Search the stored markdown for the section you need (1 credit).
  const found = await client.readResult({
    handle: result_handle,
    operation: 'search',
    query: 'rate limits',
    max_matches: 5,
  });

  // result.data is untyped in crawlforge-sdk 0.1 — its shape is the Response Example below.
  const { matches } = found.data as { matches: { offset: number }[] };

  // 3. Slice from the first match offset (1 credit) — no second fetch.
  const [first] = matches;
  if (first) {
    const section = await client.readResult({
      handle: result_handle,
      operation: 'slice',
      offset: first.offset,
      length: 4000,
    });
    const { text, has_more } = section.data as { text: string; has_more: boolean };
    console.log(text);
    console.log('More after this slice:', has_more);
  }
} else {
  console.log(scraped.formats.markdown); // small enough to arrive inline
}

Python

read_result.pyPython
# pip install crawlforge
from crawlforge import CrawlForge

client = CrawlForge()  # reads CRAWLFORGE_API_KEY

# 1. Scrape a long page. Over max_inline_chars (default 40,000 characters of
#    JSON) the markdown is replaced by a preview and a result_handle.
page = client.scrape(url='https://example.com/docs/api', formats=['markdown'])

# result.data is a plain dict — its shape is the Response Example below.
if page.data.get('truncated'):
    handle = page.data['result_handle']
    print(f"Stored {page.data['total_chars']} characters as {handle}")

    # 2. Search the stored markdown for the section you need (1 credit).
    found = client.read_result(
        handle=handle,
        operation='search',
        query='rate limits',
        max_matches=5,
    )

    # 3. Slice from the first match offset (1 credit) - no second fetch.
    if found.data['matches']:
        first = found.data['matches'][0]
        section = client.read_result(
            handle=handle,
            operation='slice',
            offset=first['offset'],
            length=4000,
        )
        print(section.data['text'])
        print('More after this slice:', section.data['has_more'])
else:
    print(page.data['formats']['markdown'])  # small enough to arrive inline

响应示例

200 OK42ms
{
"success": true,
"data": {
"handle": "res_9f2c1e6a-4b7d-4c3e-8a5f-1d2e3f4a5b6c",
"tool": "scrape",
"operation": "search",
"view": "text",
"view_path": "markdown",
"total_chars": 182406,
"expires_at": "2026-09-05T15:42:10.000Z",
"query": "rate limits",
"matches": [
{
"offset": 18240,
"length": 11,
"context_offset": 18040,
"context": "…request. Every plan is metered per API key.\n\n## Rate limits\n\nEach key may make one request per second on the Free plan…"
},
{
"offset": 61377,
"length": 11,
"context_offset": 61177,
"context": "…returns 429 with a Retry-After header; see the rate limits table above for the per-plan ceilings…"
}
],
"total_matches": 2,
"truncated": false
},
"credits_used": 1,
"credits_remaining": 998,
"processing_time": 42
}
Field Descriptions
data.handle回显你传入的句柄
data.tool生成该已存储结果的工具
data.view操作读取文本视图时为 `text`(scrape 的 markdown、fetch_url 的正文);`json_path` 时为 `json`
data.view_path文本视图对应原始结果中的哪个字段
data.total_chars已存储文本视图的大小(字符数)
data.expires_at已存储结果被删除的时间——存储后 1 小时
data.matches每个匹配一条记录:`offset` 和 `length` 指向文本视图;`context` 从 `context_offset` 开始,携带前后各至多 200 个字符
data.total_matches总共存在多少个匹配;当匹配数超过 `max_matches` 允许的数量时 `truncated` 为 true
credits_used本次请求扣除的 credits(每次读取 1 credit)
credits_remaining你剩余的 credits 余额

错误处理

输入无效(400 Bad Request)

缺少 handle,operation 不是 slice、search、lines 或 json_path 之一,search 未带 query,json_path 未带 path,或 max_matches 超出 1-100 范围(VALIDATION_ERROR)。

路径未找到(400 Bad Request)

json_path 无法解析 path(PATH_NOT_FOUND);错误信息会列出停止处可用的键。不扣除 credits。

结果未找到(404 Not Found)

结果句柄未知或已过期(RESULT_NOT_FOUND)——结果保留 1 小时,且仅创建它的账户可读。不扣除 credits。

存储不可用(503 Service Unavailable)

无法连接结果存储(STORAGE_UNAVAILABLE)。请稍后重试;未完成的读取不收费。

credits 不足(402 Payment Required)

你的账户 credits 不足。购买更多 credits 或升级你的计划。

超出速率限制(429 Too Many Requests)

你已超出所在计划的速率限制。稍等片刻,或升级你的计划以获得更高上限。

专业提示: 先 search,再从匹配的 offset 开始 slice,用 2 credits 读取一个章节。按 10,000 字符逐段读完整个结果每段 1 credit,而重新运行工具要付全价,外加再访问一次站点。

credits 成本

1 credit
每次请求 1 credit
每次 read_result 请求扣除 1 credit,无论何种操作、返回多少内容。抓取本身已由存储该结果的工具计费。

Free 计划: 1,000 个一次性 credits = 1,000 次请求

Hobby 计划: 每月 5,000 credits = 5,000 次请求(19 美元/月)

Professional 计划: 每月 50,000 credits = 50,000 次请求(99 美元/月)

Business 计划: 每月 250,000 credits = 250,000 次请求(399 美元/月)

相关工具

scrape
result_handle 最常见的来源——max_inline_chars 决定内联返回多少内容(2 credits)
crawl_deep
多页爬取是最大的结果;json_path 从已存储的爬取中读取一个页面(4 credits)
fetch_url
超过内联上限的 JSON 正文同样会被存储;json_path 读取其解析后的正文(1 credit)
get_batch_results
翻阅 batch_scrape 任务;批量任务的已存储结果共用同一个 1 小时存储(1 credit)
准备好试用 read_result 了吗?免费注册,获得 1,000 credits 开始构建。

页脚

CrawlForge MCP

面向 AI Agent 的企业级网页抓取。30 个专业 MCP 工具,专为构建智能系统的现代开发者而设计。

产品

  • 功能
  • Playground
  • 价格
  • 应用场景
  • 集成
  • 替代方案
  • 更新日志

资源

  • 快速上手
  • API 参考
  • 模板
  • 指南
  • 博客
  • 术语表
  • 常见问题
  • 网站地图

开发者

  • MCP 协议
  • Claude Desktop
  • Cursor IDE
  • LangChain
  • LlamaIndex

公司

  • 关于我们
  • 联系我们
  • 隐私政策
  • 服务条款
  • 可接受使用政策
  • 安全
  • Cookie

保持更新

获取新工具和新功能的最新动态。

基于 Next.js 和 MCP 协议构建

© 2025-2026 CrawlForge。保留所有权利。