开放平台 API 文档
把「AI 推荐查询」能力接入你的应用:提交问题 → 轮询结果 → 拿到正文与引用来源。提交即采集,结果实时回传。
除「AI 推荐查询」/v1/ask/* 外,v1 还提供 监测(/v1/monitor/*,看品牌是否被 AI 推荐)与 联网检索(/v1/search/*,找引用源)。下方「新增章节」逐一带你接。
Base URL:https://www.geocha.cn(https://www.v2zy.com 为同站点别名,二者等价) · 版本 v2 · 更新 2026-10-14(新增 P1 三字段 / 4 码构造法 / 字段名口径) · 管理后台:开发者中心
鉴权
所有接口在请求头带 X-API-Key。Key 在 开发者中心 生成,完整 Key 仅显示一次,请妥善保存。
X-API-Key: sk-714ff81f3b63b329bbd23651b36d511e857c4034
平台编码
提交任务时用 name 指定平台。共 11 个编码(7 个网页端 + 4 个 APP 端):
| 编码(新) | 模型 | 端 | 旧编码(兼容) |
|---|---|---|---|
doubao-web | 豆包 | web | DB |
doubao-app | 豆包 | app | DOUBA |
deepseek-web | DeepSeek | web | DP |
deepseek-app | DeepSeek | app | DPA |
qwen-web | 千问 | web | TYQW |
qwen-app | 千问 | app | TYQWA |
yuanbao-web | 腾讯元宝 | web | TXYB |
yuanbao-app | 腾讯元宝 | app | TXYBA |
kimi-web | Kimi | web | KIMI |
wenxin-web | 文心一言 | web | WXYY |
zhipu-web | 智谱清言 | web | ZP |
doubao-web)。旧编码(DB / DOUBA 等)仍完全兼容,老调用方无需改动。接口
POST/v1/ask/check
提交一个问答任务,返回 reqId 用于轮询结果。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
prompt | string | 是 | 提问内容 |
name | string | 是 | 平台编码(见上表) |
thinking_enabled | int | 否 | 思考模式(一期忽略,预留) |
curl -X POST https://www.geocha.cn/v1/ask/check \
-H "X-API-Key: sk-xxxx" \
-H "Content-Type: application/json" \
-d '{"prompt":"重庆小面培训哪家好","name":"doubao-web"}'
{
"code": 200,
"msg": "成功",
"data": { "reqId": "ask_20261005_123456" }
}
GET/v1/ask/result
轮询任务结果。status 为 ING 时表示仍在采集,建议间隔 3~5 秒重试,超时 5 分钟视为失败。
| 参数 | 位置 | 必填 | 说明 |
|---|---|---|---|
reqId | query | 是 | 提交任务时返回的任务 ID |
curl "https://www.geocha.cn/v1/ask/result?reqId=ask_20261005_123456" \ -H "X-API-Key: sk-xxxx"
{
"code": 200,
"msg": "success",
"data": {
"status": "SUCCESS",
"prompt": "重庆小面培训哪家好",
"result": [
{
"context": "重庆小面培训推荐……(正文)",
"quote": [
{ "url": "https://example.com/a", "title": "推荐A", "snippet": "" }
],
"think": "",
"search_word": ["重庆小面", "培训"],
"suggestions": [],
"rich_media_block": []
}
]
}
}
GET/v1/account/quota
查询当前 Key 的配额使用情况。
curl "https://www.geocha.cn/v1/account/quota" -H "X-API-Key: sk-xxxx"
{ "code": 200, "msg": "success", "data": { "used": 12, "total": 100, "window": "total" } }
状态码
| code | 含义 | 触发场景 |
|---|---|---|
200 | 成功 | — |
400 | reqId 未提交 | 未带 reqId / 任务不存在或已过期 |
401 | 未授权 | Key 无效、已禁用、或访问他人任务 |
400 | 参数错误 | prompt/name 缺失、或平台编码未知(会提示「编码格式已升级」) |
406 | 配额不足 | 已用 ≥ 总额度 |
429 | 请求频繁 | 同 Key 并发任务 ≥ 5 |
code 字段一致,便于网关直接判断。计费
按 「项」 计费:1 模型 × 1 端 = 1 项。例如同时查「豆包(web) + DeepSeek(app)」= 2 项。任务提交即扣减,失败可在控制台申请退还。
1 项 = 1 关键词 × 1 平台 × 1 采样(accepted_items = keywords × platforms × n)。cloud_phone(APP)¥0.15/项,browser(Web)与 /v1/search ¥0.05/项;n ≥ 2 按倍数计。失败不计费(cost_cny = 0),对账恒等式:total_cost_cny ÷ succeeded_items = 单价。💎
judge: true → 每条成功结果额外一次 LLM 判定成本(按项另计 / 或已含在单价内,实际以「内部台账」为准);未传 → 无额外成本(见「P1 三字段」)。返回字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
context | string | AI 回答正文(已做跨屏/跨页拼接) |
quote | array | 引用来源列表:{url, title, snippet} |
think | string | 思考过程(当前多为空,预留) |
search_word | array | 模型检索使用的关键词 |
suggestions | array | 推荐追问(可选) |
rich_media_block | array | 视频/富媒体块(暂不返回) |
端点总表 & 五分钟上手
v1 对外端点统一 X-API-Key 鉴权,统一风格:提交 → 拿 req_id → 轮询查询。除 /v1/ask/*(AI 推荐查询)外,还有以下端点:
| # | 端点 | 方法 | 用途 | 计费 |
|---|---|---|---|---|
| 1 | POST /v1/monitor/submit | POST | 提交监测任务(品牌是否被 AI 推荐) | 按项 |
| 2 | GET /v1/monitor/{req_id} | GET | 查询监测结果(轮询) | — |
| 3 | POST /v1/search/submit | POST | 提交联网检索任务(供写文章找引用源) | 按次 |
| 4 | GET /v1/search/{req_id} | GET | 查询联网结果 | — |
| 5 | GET /v1/quota | GET | 查额度与并发上限 | — |
| 6 | GET /v1/internal/bill | GET | 内部账号月度台账(仅内部账号) | — |
/api/*(含 /api/collector/*、/api/status、/api/sys/*)、/v1/keys*、/v1/_test/*、/api/admin/* 均为我方内部 / 管理 / 网站前端接口,访问会得到 401 / 404 —— 属预期行为,不是「缺权限」。五分钟上手
# ① 提交监测(1 关键词 × 1 平台 × 1 采样 = 1 项)
curl -X POST https://www.geocha.cn/v1/monitor/submit \
-H "X-API-Key: sk-xxxxxxxx" -H "Content-Type: application/json" \
-d '{"brand_name":"米途GEO","keywords":["重庆GEO哪家好"],"platforms":["doubao"],"mode":"cloud_phone","n":1,"judge":true}'
# ↑ 末尾 "judge":true 即生成 P1 三字段(mention_rank / sentiment / competitors)
# 省略则不生成(P0 兼容,三字段为 null / [])—— 见「P1 三字段」章节
# ② 轮询(拿到 req_id 后;建议 3-5s 起指数退避)
curl https://www.geocha.cn/v1/monitor/geo_req_20261011_123456 -H "X-API-Key: sk-xxxxxxxx"
监测:提交 · POST /v1/monitor/submit
提交一个「品牌是否被 AI 推荐」监测任务,返回 req_id 用于轮询结果。统一风格:提交 → 拿 req_id → 轮询查询。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
brand_name | string | 是 | 品牌名(用于判定是否被提及) |
keywords | string[] | 是 | 关键词/问题,非空;≤ 50 条(超出截断) |
platforms | string[] | 是 | 平台,非空;≤ 11 条;裸名或 V2 码(见平台编码 / mode) |
mode | string | 是 | 通道,4 值(见 mode 章节)。只有 cloud_phone / browser 可执行 |
n | int | 否 | 每个 (关键词,平台) 采样次数,默认 1,上限 10 |
meta | object | 否 | 透传对象(如 {"report_id":"..."}),原样回显;同 meta.report_id 可做业务侧关联 |
callback_url | string | 否 | ⚠️ 预留字段,当前版本不发起回调;请用轮询 |
judge | bool | 否 | 是否生成 P1 三字段(mention_rank / sentiment / competitors)。默认 false;传 true → 对每条有答案结果额外跑一次 LLM 判定(见「P1 三字段」) |
curl -X POST https://www.geocha.cn/v1/monitor/submit \
-H "X-API-Key: sk-xxxxxxxx" -H "Content-Type: application/json" \
-d '{"brand_name":"米途GEO","keywords":["重庆GEO哪家好"],"platforms":["doubao"],"mode":"cloud_phone","n":1}'
{
"req_id": "geo_req_20261011_123456",
"status": "queued",
"accepted_items": 1,
"estimated_points": 1,
"estimated_duration_sec": 90,
"created_at": "2026-10-11T20:30:00+08:00"
}
estimated_duration_sec = accepted_items × 单条实测耗时(cloud_phone 按 90s、browser 按 50s 估)。用它决定下一批何时发(估算偏保守,宁等勿撞 429)。// 400 参数错误(例:缺 keywords)
{ "code": "INVALID_PARAMS", "message": "keywords 非空数组必填" }
// 501 不支持的 mode(例:real_device)
{ "code": "NOT_IMPLEMENTED",
"message": "mode=real_device(物理真机)我方无真机资源、P0 不交付;请改用 cloud_phone(MuMu 本地模拟器,同属 App 端,结果等价)" }
// 402 额度不足
{ "code": "QUOTA_EXCEEDED", "message": "额度不足:本次需 4 项,剩余 2 项" }
// 429 并发超限(该 Key 活跃 ≤5)
{ "code": "RATE_LIMITED", "message": "请求频繁:该 Key 并发任务过多(≥5),请退避后重试" }
监测:查询 · GET /v1/monitor/{req_id}
轮询监测结果。建议 3–5s 起指数退避。status 为 queued / running 时 finished_at 为 null 属正常。
curl https://www.geocha.cn/v1/monitor/geo_req_20261011_123456 -H "X-API-Key: sk-xxxxxxxx"
// 未完成
{ "req_id": "geo_req_20261011_123456", "status": "running",
"created_at": "2026-10-11T20:30:00+08:00", "finished_at": null,
"progress": { "done": 0, "total": 1 }, "results": [], "meta": {} }
// 完成
{
"req_id": "geo_req_20261011_123456",
"status": "done",
"created_at": "2026-10-11T20:30:00+08:00",
"finished_at": "2026-10-11T20:31:35+08:00",
"progress": { "done": 1, "total": 1 },
"results": [ /* 见「返回字段」章节的结果字段全表 */ ],
"summary": { "total_items": 1, "succeeded": 1, "failed": 0, "total_cost_cny": 0.15 },
"meta": {}
}
judge:true 的批次,完成时顶层多返回 sentiment_axis(见「P1 三字段」);未开 judge 的批次不出现该字段。// 404 从未存在(或 kind 不符)
{ "code": "NOT_FOUND", "message": "req_id 不存在:geo_req_20261011_999999" }
// 410 曾存在但已超保留期(≥7 天,实际 8 天)
{ "code": "EXPIRED", "message": "req_id 已过期(结果保留 ≥7 天):geo_req_20261001_000001" }
// 批级回收 / 全失败时 status=failed,带机器可读 error
{ "req_id": "geo_req_20261011_123456", "status": "failed",
"created_at": "…", "finished_at": "…",
"progress": { "done": 0, "total": 2 }, "results": [],
"summary": { "total_items": 2, "succeeded": 0, "failed": 0, "total_cost_cny": 0 },
"error": "BATCH_TIMEOUT",
"error_detail": "批级超时(30 分钟无进展):已完成 0/2 …" }
联网检索:提交 · POST /v1/search/submit
提交一个联网检索任务(供写文章找引用源),返回 req_id 用于轮询。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
query | string | 是 | 检索问题 |
platforms | string[] | 否 | 默认 ["doubao-web","kimi-web","qwen-web"](定序 fallback:任一成功即返回) |
top_k | int | 否 | 引用条数上限,默认 10 |
meta | object | 否 | 透传 |
/v1/search 只走 Web 端:platforms 含 -app 码(如 doubao-app)→ 400(fail-fast,不会静默返回空引用)。curl -X POST https://www.geocha.cn/v1/search/submit \
-H "X-API-Key: sk-xxxxxxxx" -H "Content-Type: application/json" \
-d '{"query":"重庆GEO优化公司怎么选","top_k":5}'
{ "req_id": "geo_search_20261011_654321", "status": "queued",
"accepted_items": 1, "estimated_points": 1,
"estimated_duration_sec": 50, "created_at": "2026-10-11T20:32:00+08:00" }
// 400:带 -app 码
{ "code": "INVALID_PARAMS", "message": "/v1/search 仅支持 Web 端(references 依赖网页引用);请改用 doubao-web 等" }
联网检索:查询 · GET /v1/search/{req_id}
轮询联网结果。注意:results 是对象不是数组(与 /v1/monitor 不同!)。
// 未完成
{ "req_id": "geo_search_…", "status": "running", "created_at": "…",
"progress": { "done": 0, "total": 3 }, "results": null }
// 完成
{
"req_id": "geo_search_20261011_654321",
"status": "done",
"created_at": "2026-10-11T20:32:00+08:00",
"finished_at": "2026-10-11T20:33:10+08:00",
"results": {
"answer_text": "……联网检索摘要……",
"references": [
{ "url": "https://…", "title": "…", "site_name": "…", "publish_time": "2026-01-15" }
],
"platform": "doubao",
"cost_cny": 0.05,
"latency_ms": 6400
}
}
// 失败
{ "req_id": "geo_search_…", "status": "failed", "created_at": "…", "finished_at": "…",
"error": "CAPTCHA_BLOCKED", "error_detail": "…", "results": null }
结果字段全表(results[])
GET /v1/monitor/{req_id} 完成时,results 为数组,每项是下表一条;GET /v1/search/{req_id} 的 results 是对象(见上「检索·查询」)。
| 字段 | 类型 | 说明 |
|---|---|---|
keyword | string | 关键词(本条对应的问题) |
platform | string | 平台裸名(doubao / wenxin / …) |
mode | string | 通道(=cloud_phone / browser) |
n_index | int | 第几次采样(1-based) |
checked_at | ISO8601 | 该条采集完成时刻(≠ 提交时刻、≠ 整批 finished_at) |
answer_text | string | AI 回答正文;失败条目恒为 "" |
cited_urls | string[] | 引用链接(去重后) |
structured | object | { is_mentioned, mention_rank, mention_quality, context_type, sentiment, competitors, cited_sources[], citations_status };P0:仅 is_mentioned / cited_sources 有值;P1(judge:true):mention_rank / sentiment / competitors 有值(详见「P1 三字段」),其余恒为 null/[] |
source | string | ui_cloud(cloud_phone)/ ui_web(browser)/ api(未交付) |
end | string | app / web / none |
status | string | ok / failed |
error | string | 失败原因(ok 时不存在该字段,或为空) |
error_detail | string | 原始失败说明(可选,排查用) |
citations_status | string | ok / empty / not_supported |
citations_note | string | 引用状态说明(可选) |
cost_cny | float | 真实成本;失败 = 0 |
meta | object | 提交时透传的 meta(原样) |
model | string | 内部模型 id(一般不必消费) |
structured.citations_status = "not_supported" = 该通道本就不提供引用(APP 端)→ 不要重试,如需引用请用 mode=browser 或 /v1/search。整批 summary:{ total_items, succeeded, failed, total_cost_cny }(total_cost_cny 只累计成功项)。P1 三字段(judge:true 时生成)
🆕 2026-10-13 上线。未开 judge 时三字段恒为 null / [](向后兼容)。
12.1 触发
| 提交参数 | 三字段 | LLM |
|---|---|---|
judge: true | ✅ 生成 | 跑一次判定 |
未传 / judge: false(默认) | ❌ 全 null / [] | 不跑、不加价 |
judge 是 POST /v1/monitor/submit 的可选布尔参数(默认 false),见「监测:提交」参数表。
12.2 字段(results[].structured)
| 字段 | 类型 | 值域 | 含义 |
|---|---|---|---|
mention_rank | int | null | 1 / 2 / … / null | 品牌首次被提及的位次(1-based);未提及 → null |
sentiment | string | null | recommended / neutral / negative / null | 轴②情感极性 |
competitors | array | [{name, is_recommended, rank}] | 竞品清单(形状见 12.4) |
12.3 批次级(顶层)
"sentiment_axis": { "total": 1, "recommended": 1, "neutral": 0, "negative": 0 }
total = 本批已判定条目数;recommended + neutral + negative = total。仅 judge:true 批次返回;未开 judge 的批次不出现该字段。12.4 competitors 形状(P0 → P1)
原(P0):无此字段,恒为 null / []。新(P1):
"competitors": [ { "name": "海星小象", "is_recommended": true, "rank": 2 } ]
| 键 | 类型 | 说明 |
|---|---|---|
name | string | 竞品名(识别不出的返原文字符串) |
is_recommended | bool | null | 是否被 AI 推荐;判不出 → null |
rank | int | null | 竞品位次;判不出 → null |
12.5 空值语义
null= 未判定(judge 未开 / LLM 判定失败 / 文本太短)- 不存在「通道不支持」 —— 只要有
answer_text就能判(cloud_phone/browser均适用) - 🔴 品牌未提及 →
sentiment强制null(「未提及」 ≠ 「negative」,不臆造负面)
12.6 边界
- 品牌识别不出 →
name返原文字符串,is_recommended = null,rank = null - 不猜、不静默丢:判不出就给
null,绝不编造 - 判定失败收敛为
null/[],不抛错、不阻断整批(其余字段照常返回)
12.7 成本
- 传
judge: true→ 额外 LLM 判定成本(按项另计 / 或含在单价,见「计费」) - 未传 → 无额外成本
12.8 示例(judge:true 批次 · 服务端真实返回)
{
"req_id": "geo_req_20261013_362984",
"status": "done",
"results": [
{
"platform": "doubao",
"mode": "cloud_phone",
"status": "ok",
"answer_text": "…米途GEO(探源者GEO)…",
"structured": {
"is_mentioned": true,
"mention_rank": 1,
"mention_quality": null,
"context_type": null,
"sentiment": "recommended",
"competitors": [
{ "name": "海星小象", "is_recommended": true, "rank": 2 },
{ "name": "珍岛", "is_recommended": true, "rank": 3 },
{ "name": "派活鹿信息科技", "is_recommended": true, "rank": 4 }
],
"cited_sources": [],
"citations_status": "not_supported"
},
"cost_cny": 0.15
}
],
"sentiment_axis": { "total": 1, "recommended": 1, "neutral": 0, "negative": 0 }
}
competitors 条数随 AI 回答而定 —— 本示例为线上真实返回(共 3 家),实际可能更多或更少;三字段均以本条 structured 为准。字段名与失败条目口径(易错点澄清)
13.1 三个易写错的字段名
| ✅ 正确 | ❌ 易错写法 | 位置 |
|---|---|---|
cost_cny | results[].cost_cny、summary.total_cost_cny | |
platform | results[].platform(恒为裸名) | |
answer_text | results[].answer_text |
undefined。13.2 失败条目口径
| 项 | 口径 |
|---|---|
条目级 status | "failed"(不是 "error") |
cost_cny | 恒为 0(失败不计费) |
answer_text | 恒为 "" |
structured 三键 | 恒为 null / [] |
error | 失败码(见「错误码全表」/「browser 专属 error」) |
外层批 status | 随整体成败为 done / failed —— 不要与条目级混用 |
status="failed" 当成 "error";② 以为失败条目也计费。都不对。额度与并发 · GET /v1/quota
查询当前 Key 的额度与并发上限。limits.max_inflight(默认 5)即同 Key 活跃子任务上限,运行时读取它来决定退避。
curl "https://www.geocha.cn/v1/quota" -H "X-API-Key: sk-xxxxxxxx"
// 外部账号
{ "used": 3, "total": 500, "window": "total", "limits": { "max_inflight": 5 } }
// 内部账号(无余额概念,用量走「内部台账」)
{ "account_type": "internal", "account": "tanyuanzhe",
"used": 0, "total": null, "window": "none", "limits": { "max_inflight": 5 } }
mode 4 值(关键)
mode | 通道 | 执行位置 | 支持 |
|---|---|---|---|
cloud_phone | MuMu 本地 Android 模拟器(APP 端) | 我方采集机 | ✅ |
browser | Playwright 无头浏览器(Web 端) | 服务器 / 采集机(见可用性) | ✅ |
real_device | 物理真机 | — | ❌ 501 |
api | 各平台官方 API | — | ❌ 501 |
real_device / api 传了会直接返回 501,即使用其它字段都合法。为什么 501 而不是「静默降级」:把 real_device 当 app 跑却标 source=ui_real、把 web 结果标 source=api,都是数据污染(比不交付更糟)。cloud_phone 与 real_device 结果等价(我方「真机形态」实为本地模拟器)→ 请直接用 cloud_phone。传一个完全不存在的 mode(如 telepathy)→ 400 INVALID_PARAMS(列出 4 个合法值),与「501 已明确不支持」区分开。平台 × 通道 支持矩阵(先看这个)
| 平台(裸名) | mode=browser(Web 端) | mode=cloud_phone(App 端) |
|---|---|---|
doubao / deepseek / qwen / yuanbao | ✅ | ✅ |
kimi / wenxin / zhipu | ✅ | ❌ 无 App 脚本 → 400 |
| 合计 | 7 / 7 | 4 / 7 |
mode 与 platforms 必须同端,否则提交即 400(不派发、不占槽位、不计费):
{
"code": "INVALID_PARAMS",
"message": "mode=cloud_phone 与 platforms 不匹配 —— 无 App 通道:wenxin。App 端仅支持 doubao/deepseek/qwen/yuanbao;其余平台请用 mode=browser(Web 端 7 家全)"
}
同理 mode=browser + doubao-app(显式 -app 码)→ 400(否则会实际跑 App 端却标 end=web、并按 browser 单价少计费)。平台编码 V2(11 项 · 已冻结)
| V2 码 | 对外 platform(裸名) | 端 |
|---|---|---|
doubao-web | doubao | Web |
deepseek-web | deepseek | Web |
qwen-web | qwen | Web |
yuanbao-web | yuanbao | Web |
kimi-web | kimi | Web |
wenxin-web | wenxin | Web |
zhipu-web | zhipu | Web |
doubao-app | doubao | APP |
deepseek-app | deepseek | APP |
qwen-app | qwen | APP |
yuanbao-app | yuanbao | APP |
DOUBAO-WEB 等价 doubao-web)。⚠️ APP 端只有 4 家(doubao/deepseek/qwen/yuanbao)→ kimi/wenxin/zhipu 没有 APP 通道,只能用 browser(Web)。写法:推荐 裸名 + mode({"platforms":["doubao","qwen"],"mode":"cloud_phone"});也兼容 V2 码(自带端,与 mode 需一致,如 ["doubao-app","qwen-web"])。无论传裸名还是 V2 码,响应里 results[].platform 恒为裸名。旧缩写码(DB/DOUBA/TYQW… 11 个)仍受理(向后兼容),但新接入请只用上表两种。
错误码全表
error(results[] 里单条的成败,HTTP 仍是 200)。两者不要混用:例如某平台风控失败时,HTTP 是 200,而 results[0].error = "CAPTCHA_BLOCKED"。HTTP 层
| HTTP | code | 场景 | 建议动作 |
|---|---|---|---|
400 | INVALID_PARAMS | 参数错误(缺字段 / 类型错 / 超上限 / 未知平台码) | 不重试(先修请求) |
401 | UNAUTHORIZED | X-API-Key 无效或缺失 | 不重试 + 告警(Key 可能被禁用) |
402 | QUOTA_EXCEEDED | 额度用完(外部账号) | 停止 + 告警(重试无意义) |
404 | NOT_FOUND | req_id 从未存在(或 kind 不符) | 不重试(检查 ID / 是否提交成功) |
410 | EXPIRED | req_id 曾存在但超保留期 | 重新提交 |
429 | RATE_LIMITED | 该 Key 活跃子任务 ≥ max_inflight(5) | 退避重试(指数退避) |
500 | INTERNAL_ERROR | 服务端内部错误(兜底码,正常不出现) | 退避重试 1 次;持续出现请报我方 |
501 | NOT_IMPLEMENTED | mode=api / mode=real_device / /v1/monitor/batch | 不重试(换 mode 或换端点) |
条目级 error(写在 results[].error,HTTP 仍 200)
error | 含义 | 建议动作 |
|---|---|---|
CAPTCHA_BLOCKED | 命中风控 / 人机验证 | 不重试(换平台 / 降频) |
LOGIN_REQUIRED | 站点登录态失效 | 不重试(需我方重新登录,请联系) |
PAGE_ERROR | 页面结构异常(找不到输入框等) | 可重试 1 次;持续请报我方 |
EMPTY_RESPONSE | 页面正常但正文为空 | 可重试 1 次;持续则降频 |
TIMEOUT | 采集超时(页面卡住/加载慢) | 可重试(退避) |
UPSTREAM_FAILED | 上游平台 / 采集机失败 | 按轮询协议退避重试 |
TIMEOUT / UPSTREAM_FAILED 不是 HTTP 504/502 —— 它们是条目级取值,HTTP 仍是 200。🔴 失败条目的条目级
status 是 "failed"(不是 "error");只有整批的外层 status 才取 done / failed —— 两者不要混用(详见「字段名口径」)。失败条目
cost_cny 恒为 0(失败不计费)。每条失败还带 error_detail(原始说明,含采集侧备注)—— 排查用,非必须消费。browser 专属 error(5 条)
mode=browser 的失败条目,error 取值与建议动作:
error | 建议动作 |
|---|---|
CAPTCHA_BLOCKED | 🔴 勿重试(同 IP 重试仍被拦;换平台或降频) |
LOGIN_REQUIRED | 🔴 勿重试(需我方重登后恢复) |
PAGE_ERROR | 可重试 1 次 |
EMPTY_RESPONSE | 可重试 1 次 |
TIMEOUT | 可重试(退避) |
6.1 4 码构造法(含复现步骤 · 🆕 2026-10-13)
上表中 4 个可定向构造的码,触发条件 / 重试策略 / 独立复现如下:
error | 触发条件 | 重试策略 | 复现步骤 |
|---|---|---|---|
CAPTCHA_BLOCKED | 命中风控 / 人机验证(滑块) | ❌ 不重试 | 同 IP 高频请求(连提 3+ 次)/ 机房 IP 首发 |
LOGIN_REQUIRED | 站点登录态失效(cookie/session 过期) | ❌ 不重试 | 删除 states / 退出登录后查询 |
PAGE_ERROR | 页面结构异常(选择器缺失) | ✅ 重试 1 次 | 平台改版 / mock 错选择器 |
TIMEOUT | 采集超时(> WEB_TASK_TIMEOUT_MS) | ✅ 退避重试 | 设 WEB_TASK_TIMEOUT_MS=2000 → 任意查询必超时 |
verify-isolated-kit.js —— 用真实生产代码 composeMonitor 生成 4 段与线上 GET /v1/monitor/{req_id} 逐字节一致的失败响应;配套 4 段 sha256(失败基准比 sha256,不比字符串,避免行尾/空行扯皮)。交付文档:4码构造法-探源者.md。NO_ANSWER_FOUND(重要):采集脚本在「没抓到答案」时会写占位串 NO_ANSWER_FOUND(恰好 15 字符)。我方已保证它不会出现在 status:"ok" 的 answer_text 里(三层防线:采集侧判失败 → 组装层置空 → 输出层双判)。但请读侧仍做一次廉价兜底:判定成功 = status=="ok" ∧ 非空 ∧ 非 NO_ANSWER_FOUND ∧ 长度 ≥ 40。完整判据 + 可直接照抄的 Python/JS 代码:_nav_fix/SENTINEL-NO-ANSWER-FOUND.md。内部台账 · GET /v1/internal/bill
仅内部账号(account_type=internal)或 admin(X-Admin-Token + ?account=<账号>)。month 省略默认当月,格式须 YYYY-MM,否则 400。凡内部账号调用不扣余额,全部计入台账。
curl "https://www.geocha.cn/v1/internal/bill?month=2026-10" \ -H "X-API-Key: sk-xxxxxxxx"
{
"account": "tanyuanzhe",
"month": "2026-10",
"submitted_items": 16,
"calls": 10,
"succeeded_items": 11,
"cost_cny": 1.05,
"markup_cny": 0.11,
"total_cny": 1.16,
"failures": [ { "req_id": "geo_req_…", "platform": "wenxin", "error": "…" } ],
"details": [ { "req_id": "geo_req_…", "kind": "monitor", "submitted_items": 2, "succeeded_items": 2, "cost_cny": 0.3, "status": "settled" } ]
}
submitted_items/calls= 提交口径(对账参考,提交即计,不代表要付钱)succeeded_items/cost_cny= 实结口径(计费依据:只有成功的项才计费)markup_cny=cost_cny × 10%(管理费);total_cny=cost_cny + markup_cny- 🔴 台账范围 = 该账号「全部实际提交」,不剔任何批次(含早期联调批)—— 探源者已确认「账要全,不删」
- 🔴
succeeded_items ≤ submitted_items是常态(失败既不计费也不计成功)
轮询协议
| 项 | 规则 |
|---|---|
| 幂等 | 同一 (brand_name, keywords, platforms, mode, n, meta.report_id) 在 5 分钟内重复提交 → 返回同一 req_id,不重复计费、不被限流 |
| 退避 | 建议 3–5s 起,指数退避,上限 30s(queued 久停 ≠ 失败,见可用性) |
| 保留期 | 结果保留 ≥7 天(实际 8 天;未命中 404) |
| 404 vs 410 | 404 = 从未存在(含 kind 不符);410 = 曾存在但超期被清理 |
| 并发上限 | 该 Key 活跃子任务(queued+running)≥5 → 新的 */submit 直接 429(可用 GET /v1/quota 的 limits.max_inflight 运行时读取) |
| 别重复提交 | 看到 queued 就重复提交 → 占并发名额;超出 5 分钟幂等窗口还会重复计费 |
可用性(关键)
11.1 两条通道的执行方与在线性
| 通道 | 执行位置 | 可用性 |
|---|---|---|
browser | 服务器(7×24) | ✅ 不依赖我方采集机开关机 —— 夜里也能跑 |
browser 的 doubao / zhipu / yuanbao | 采集机(住宅 IP,自动路由) | ⚠️ 依赖采集机在线 |
cloud_phone(APP,7 家) | 采集机(MuMu,个人 PC) | ⚠️ 依赖采集机在线 |
mode=browser 请求,doubao/zhipu/yuanbao 我方自动确保由住宅 IP 执行。返回的 source 仍是 ui_web、end 仍是 web。为什么这三家要绕本机:服务器是机房 IP,实测 doubao 0/3(人机验证)、zhipu 0/3;换住宅 IP 后 3/3。其余 4 家(deepseek/kimi/qwen/wenxin)在服务器上稳定 → 走 7×24。11.2 notice 字段:「在等采集机」会被显式告知
当一批里含 doubao/zhipu/yuanbao(本机专属)且采集机当前离线,GET /v1/monitor/{req_id} 会多返回两个字段(正常批次不出现):
{
"status": "queued",
"notice": "LOCAL_COLLECTOR_OFFLINE",
"notice_text": "本批含 1 项「仅本机可执行」的平台任务(doubao):…本机当前已离线约 2 分钟 → 任务保持排队(不失败、不计费、不代跑);本机恢复后自动继续,无需重新提交。"
}
notice 时在 UI/日志上标注「等待采集机」,避免误判卡死。不要因为它是 queued 就重复提交。11.3 「排队」与「失败」的边界
queued 久停 = 在等采集机(我方已给 notice)。「等待」本身不算失败。失败只在两种情况发生:① 批级 BATCH_TIMEOUT(默认 30 分钟无任何进展);② 采集侧返回了终态错误。
11.4 该用哪些端点(白名单)
✅ 该用(X-API-Key) | ⛔ 不该用(内部,401/404 属预期) |
|---|---|
POST /v1/monitor/submit | /api/collector/*(采集机 ↔ 服务器内部链路) |
GET /v1/monitor/{req_id} | /api/status、/api/sys/*(我方运维观测) |
POST /v1/search/submit | /v1/keys*(Key 管理,admin) |
GET /v1/search/{req_id} | /v1/_test/*、/api/admin/* |
GET /v1/quota | /api/auth/*、/api/user/*、/api/brand/*(我站网页前端用) |
GET /v1/internal/bill(内部账号) | — |
notice(11.2);需要「谁执行的」→ 看 source/end(结果字段)。/v1/quota 与 /v1/monitor/* 已覆盖你们需要的全部信号,无需访问任何内部端点。