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
/api/v1/tools/read_resultParameters
handle 来自带 truncated: true 的响应——本工具从不抓取页面,只读取你已经拥有的结果。句柄在结果存储 1 小时后过期。| Name | Type | Required | Default | Description |
|---|---|---|---|---|
handle | string | Required | - | 来自截断响应的 `result_handle`(`res_` 加一个 UUID)。仅创建它的账户可读,有效期 1 小时。 Example: res_9f2c1e6a-4b7d-4c3e-8a5f-1d2e3f4a5b6c |
operation | string | Required | - | `slice` 返回文本视图的一段字符范围;`search` 查找不区分大小写的字面子串(绝不是正则表达式)并返回每个带上下文的匹配;`lines` 返回一个行窗口;`json_path` 从已存储的 JSON 中读取一个路径。 Example: search |
offset | number | Optional | 0 | `slice`:返回的第一个字符。`lines`:返回的第一行的索引。 Example: 18240 |
length | number | Optional | 10000 | `slice`:返回的字符数(默认 10,000)。`lines`:返回的行数(默认 200,最多 5,000)。 Example: 4000 |
query | string | Optional | - | 仅用于 `search`,且在其中必填:要查找的字面文本,不区分大小写。 Example: rate limits |
max_matches | number | Optional | 20 | `search`:返回的最大匹配数,1-100。响应中的 `truncated: true` 表示匹配数多于返回数。 Example: 5 |
path | string | Optional | - | 仅用于 `json_path`,且在其中必填:点分键名和数组索引,点号或方括号形式均可(`pages.0.url` 或 `pages[0].url`);不支持通配符、过滤器或切片。从已存储的结果对象中读取——当已存储的文本是 JSON(例如 `fetch_url` 的正文)时,则从解析后的正文中读取。 Example: pages.0.url |
max_inline_chars | number | Optional | 40000 | 内联返回的最大文本量,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,然后是你所请求操作的字段。
offset、length、text 和 has_more。默认为前 10,000 个字符。query、matches(每项带 offset、length、context_offset 和 context——前后各 200 个字符)、total_matches 和 truncated。first_line、line_count、total_lines、char_offset、lines 和 has_more。offset 是第一行的索引,length 是行数(默认 200,最多 5,000)。path、value 和 value_chars。超过 max_inline_chars 时,值会被替换为 value: null、一个 preview 和 truncated: true,并附带一条建议收窄路径的警告。已存储的结果放在哪里
~/.crawlforge/results/(TTL 1 小时,200 MB LRU),不会上传任何内容。错误响应从不存储,batch_scrape 任务共用同一个存储。请求示例
cURL
# 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
// 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
# 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响应示例
{ "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}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` 为 truecredits_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 成本
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 美元/月)