错误处理
开放 API 的错误分两层,处理方式完全不同:
| 层级 | 在哪里看 | 含义 | 例子 |
|---|---|---|---|
| 请求级 | 响应体的 ret 不为 1 |
这次 HTTP 请求没有被受理,没有创建任务、不扣费 | 参数错误、余额不足、Key 无效 |
| 任务级 | 任务的 status 为 failed 或 canceled,原因在 error_code |
任务已创建,但没有成功拿到回答,不扣费 | 平台抓取失败、派发前 Key 失效 |
请求失败时 HTTP 状态码仍是 200,响应体为:
{ "ret": 1023, "msg": "{\"zh\":\"点数余额不足,请充值后重试\",\"en\":\"Insufficient credits. Please top up and try again.\"}"}- 程序判断一律用
ret。 文案可能调整,不要按文案做逻辑。 msg是一个 JSON 字符串,解析后得到{"zh": "…", "en": "…"}两种语言的提示,可以直接展示给你的用户。为稳妥起见,解析失败时把msg原文当作提示即可。- 参数错误
1001的提示会写明具体字段,例如「参数错误:query 不能超过 4096 个字符」。
ret |
含义 | 可能出现在 | 可以重试吗 | 处理建议 |
|---|---|---|---|---|
1 |
成功 | 全部 | — | — |
1001 |
参数错误:请求体不是单个 JSON 对象、超过 64 KiB、包含未定义的字段,或字段值不合法 | 全部 | 否 | 按提示修正请求。注意字段名要用下划线风格,如 task_id、wait_seconds |
1022 |
开发者 API 计费未开通 | 提交任务 | 否 | 联系商务开通 |
1023 |
点数余额不足 | 提交任务 | 充值后 | 充值,或改用单价更低的平台 / P2 档 |
1024 |
待派发或执行中的任务数已达上限 | 提交任务 | 是,稍后 | 等部分任务结束后再提交,见限流与配额 |
1025 |
提交过于频繁 | 提交任务 | 是,稍后 | 降低提交速率,退避后重试 |
1029 |
平台暂不支持或正在维护 | 提交任务 | 换平台 | 重新获取平台列表,换一个可用平台 |
1030 |
API Key 缺失、无效、已停用或已过期 | 全部 | 否 | 检查 Key,见认证与 API Key |
1031 |
任务不存在 | 查询任务 | 否 | 检查 task_id;只能查询自己账号下的任务 |
1003 |
无权限(账户权限异常,例如 Key 所属账户的所有权已转移) | 全部 | 否 | 少见,请联系我们 |
-1 |
服务暂时不可用 | 全部 | 是,退避 | 指数退避重试;提交任务时遇到要先确认是否已创建,见下文 |
请求级错误(-1 除外)都发生在创建任务之前:被拒绝的提交不会创建任务,也不会扣点数。
HTTP 状态码不是 200
Section titled “HTTP 状态码不是 200”405(请求方法不是 POST)和 404(路径写错)说明请求没有进入业务处理,确定没有创建任务,修正代码后重新请求即可。
除此之外,如果收到 502、504 等非 200 状态码,或者连接超时、中断,说明请求在网络链路上出了问题,你无法确定服务端是否已经处理:
- 查询类接口(查询平台、查询任务、任务列表):直接重试即可。
- 提交任务:先按下文「提交结果未知时」的流程确认,再决定是否重新提交。
任务失败:error_code
Section titled “任务失败:error_code”任务的 status 为 failed 或 canceled 时,error_code 说明原因。失败和取消的任务都不扣点数,预占的点数会退回。
error_code |
status |
含义 | 处理建议 |
|---|---|---|---|
crawl_failed |
failed |
平台没有返回有效回答(超时、平台异常等) | 稍后重试,或换平台;时效要求不高时可改用 P2,平台不可用时会排队等待恢复 |
credential_invalid |
failed |
任务派发前,提交它的 API Key 已停用、删除或过期 | 换一个有效 Key 重新提交 |
crawler_rejected |
failed |
平台暂不支持或已下线 | 重新获取平台列表,换平台 |
crawler_internal_error |
failed |
抓取服务内部错误,任务没有被受理 | 稍后重试 |
crawler_submit_failed |
failed |
抓取服务没有受理这个任务 | 稍后重试 |
crawler_invalid_request |
failed |
抓取服务拒绝了这次的请求参数 | 检查问题内容和 country 后重新提交 |
crawler_method_unavailable |
failed |
抓取服务未开通原始结果抓取(服务端配置问题) | 请联系我们 |
crawler_auth_failed |
failed |
服务端配置问题 | 请联系我们 |
crawl_canceled |
canceled |
任务被取消 | 需要的话重新提交 |
下面两个值出现在未结束的任务上,不代表失败:
error_code |
status |
含义 |
|---|---|---|
dispatch_uncertain |
dispatching |
正在确认派发结果,服务端会自行确认、不会重复派发,继续轮询即可 |
crawler_query_unavailable |
未结束 | 暂时无法同步执行进度,继续轮询;长时间不变化请联系我们 |
error_message 与请求级错误的 msg 格式相同,是一个 JSON 字符串,解析后得到 {"zh": "…", "en": "…"} 两种语言的提示,可以写进日志或按语言展示给用户;解析失败时按原文处理。不要按文案做程序判断,分支请用 error_code。raw_result.error(以及 result_json 里的 error)格式相同。
| 情况 | 做法 |
|---|---|
-1、网络错误、非 200 状态码(查询类接口) |
指数退避重试,例如 2、4、8 秒…,最多几次 |
1024、1025 |
不会创建任务,等待后重试是安全的;同时降低并发和提交速率 |
-1、超时、连接中断(提交任务) |
不要直接重试,先确认是否已创建,见下节 |
1001、1003、1022、1023、1029、1030、1031 |
不要自动重试,修正问题后再请求 |
任务 failed / canceled |
重新提交会创建新任务,成功后才扣费;crawl_failed 建议间隔一段时间或换平台 |
提交结果未知时
Section titled “提交结果未知时”提交请求超时、连接中断或返回 -1 时,任务可能已经创建。直接重试会产生第二个任务,两个都成功就会扣两次点数。正确做法:
- 调用任务列表,用
keyword传入刚才的问题文本(keyword最多 256 个字符,按「包含」匹配,长问题取前 256 个字符即可),start_time设为提交前的时间。 - 找到了:继续轮询那个
task_id。 - 没找到:重新提交。
# BASE_URL、HEADERS 的定义同快速开始import jsonimport time
import requests
RETRYABLE = {-1, 1024, 1025}
class MaxAEOError(Exception): def __init__(self, ret, msg): self.ret = ret try: self.message = json.loads(msg).get("zh") or msg except (TypeError, ValueError, AttributeError): self.message = msg super().__init__(f"ret={ret}: {self.message}")
def post(path, body): resp = requests.post(BASE_URL + path, headers=HEADERS, json=body, timeout=30) resp.raise_for_status() data = resp.json() if data["ret"] != 1: raise MaxAEOError(data["ret"], data.get("msg", "")) return data["data"]["ent"]
def get_with_retry(path, body, retries=4): """查询类接口:可以安全重试。""" for attempt in range(retries + 1): try: return post(path, body) except (requests.RequestException, MaxAEOError) as e: retryable = not isinstance(e, MaxAEOError) or e.ret in RETRYABLE if not retryable or attempt == retries: raise time.sleep(2 ** (attempt + 1))
def submit_safely(platform, query): """提交任务:只有确认没有创建过,才重新提交。""" started = int(time.time()) - 60 # 留出时钟误差 for attempt in range(4): try: return post("/task/submit", {"platform": platform, "query": query}) except MaxAEOError as e: if e.ret in (1024, 1025): # 没有创建任务,等一会儿再试 time.sleep(15 * (attempt + 1)) continue if e.ret != -1: raise except requests.RequestException: pass # 结果未知:先找有没有刚才那次提交 found = get_with_retry("/task/key/list", { "keyword": query.strip()[:256], "platform": platform, "start_time": started, "page_size": 5, }) if found["items"]: return get_with_retry("/task/get", {"task_id": found["items"][0]["task_id"]}) raise RuntimeError("提交多次未成功,请稍后再试")// BASE_URL、headers 的定义同快速开始const RETRYABLE = new Set([-1, 1024, 1025]);
class MaxAEOError extends Error { constructor(ret, msg) { let message = msg; try { message = JSON.parse(msg).zh || msg; } catch {} super(`ret=${ret}: ${message}`); this.ret = ret; }}
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function post(path, body) { const resp = await fetch(BASE_URL + path, { method: "POST", headers, body: JSON.stringify(body), signal: AbortSignal.timeout(30_000), }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); const data = await resp.json(); if (data.ret !== 1) throw new MaxAEOError(data.ret, data.msg ?? ""); return data.data.ent;}
// 查询类接口:可以安全重试async function getWithRetry(path, body, retries = 4) { for (let attempt = 0; ; attempt++) { try { return await post(path, body); } catch (e) { const retryable = !(e instanceof MaxAEOError) || RETRYABLE.has(e.ret); if (!retryable || attempt === retries) throw e; await sleep(2 ** (attempt + 1) * 1000); } }}
// 提交任务:只有确认没有创建过,才重新提交async function submitSafely(platform, query) { const started = Math.floor(Date.now() / 1000) - 60; // 留出时钟误差 for (let attempt = 0; attempt < 4; attempt++) { try { return await post("/task/submit", { platform, query }); } catch (e) { if (e instanceof MaxAEOError && (e.ret === 1024 || e.ret === 1025)) { await sleep(15_000 * (attempt + 1)); // 没有创建任务,等一会儿再试 continue; } if (e instanceof MaxAEOError && e.ret !== -1) throw e; } // 结果未知:先找有没有刚才那次提交 const found = await getWithRetry("/task/key/list", { keyword: Array.from(query.trim()).slice(0, 256).join(""), platform, start_time: started, page_size: 5, }); if (found.items.length > 0) { return getWithRetry("/task/get", { task_id: found.items[0].task_id }); } } throw new Error("提交多次未成功,请稍后再试");}联系我们排查问题
Section titled “联系我们排查问题”需要我们协助排查时,请提供:
task_id(任务相关问题);- 请求时间(精确到分钟)与调用的接口;
- 响应头里的请求追踪 ID
ipalfish-trace-id(如有); - 完整的
ret、msg或error_code、error_message。
不要把完整的 API Key 发给任何人,包括我们的工作人员。
