OCR API 的故障大致分三类:请求根本没被接受(401、413、415)、请求被限流或超时(429、5xx)、请求成功但结果不可用(识别为空、乱码、低置信度)。前两类看状态码就能定位,第三类最容易被误判成“接口坏了”。
本文按症状给出排查顺序、图片预处理方法、重试策略和日志字段,最后附一段可直接复用的错误处理代码。
说明:不同厂商的 OCR 接口、错误码和限额规则差异较大,本文以 2026 年 10 月的常见行为为例,具体以服务商文档和实际返回为准。OpenAI、Google、Microsoft、阿里云、腾讯云、百度智能云、Tesseract、PaddleOCR 等名称,分别为其权利人的商标或产品名,本文仅作技术说明。
一、先保留四样证据
排查之前不要急着重试,先把这些信息存下来:
- HTTP 状态码和响应体里的
error.code、error.message; - 响应头里的请求 ID 或
x-request-id; - 原图格式、文件大小、像素尺寸和 EXIF 方向;
- 调用时间、使用的接口地址和模型名。
| 状态码 | 常见含义 | 先查什么 |
|---|---|---|
| 400 | 参数或图片内容不合法 | Base64、URL、字段名、图片是否损坏 |
| 401 | 鉴权失败 | Key 是否缺失、过期、带空格 |
| 403 | 无权限或额度受限 | 模型权限、项目状态、账户余额 |
| 413 | 请求体或文件过大 | 文件体积、像素尺寸、PDF 页数 |
| 415 | 不支持的媒体类型 | Content-Type、扩展名、实际编码 |
| 429 | 限流或并发超限 | QPS、并发数、Retry-After |
| 500/502/503/504 | 上游故障或超时 | 重试、降级、服务状态页 |
二、401 与 403:先核对身份,再核对权限
401 表示服务端不认这把 Key。高频原因包括:请求头漏了 Authorization: Bearer,Key 复制时多了空格或换行,环境变量为空,Key 已轮换,或者把 A 平台的 Key 发到了 B 平台的地址。
排查时先用最小请求验证,不要在完整业务链路里猜。确认请求头实际发出的值,而不是只看配置文件。403 则通常是 Key 有效但没有该模型或接口的权限,或者账户被限额、项目被停用。
另外注意:网关地址和厂商原生地址的 Key 通常不能混用。换地址时,Key、模型名、请求路径要一起核对。
三、413 与 415:文件太大、格式不对
413 不是“图片内容识别不了”,而是请求在传输层就被拒绝了。常见触发条件:
- 单张图片超过接口的体积上限;
- 像素尺寸过大,解码后内存超限;
- PDF 页数过多,或一次上传了多页;
- Base64 编码后体积膨胀约三分之一,原本刚好超限。
415 则多与格式有关:接口只接受 JPG、PNG,你传了 HEIC、TIFF、BMP;或者文件扩展名是 JPG,实际内容是 WebP;又或者 Content-Type 写成了 application/json,但接口要的是 multipart/form-data。
图片预处理建议按这个顺序做:
- 用图像库读取图片,确认真实格式,不信任扩展名。
- 按长边限制等比缩放,例如先压到 2000–3000 像素再测。
- 修正 EXIF 方向,避免文字横躺导致识别为空。
- 转成接口支持的格式,控制压缩质量,别把字压糊。
- 多页 PDF 拆分后逐页处理,或改用支持整份文档的接口。
- 记录处理前后的体积和尺寸,方便复盘是哪一步超限。
不要为了过 413 把图片压到看不清。压缩后文字笔画粘连,接口会返回成功,但结果质量很差。
四、429:限流、并发和重试节奏
429 的本质是“请求来得太快或太多”。常见维度有每秒请求数、每分钟请求数、并发数和按 Token 或页数计算的配额。即使总量没超,瞬时并发也可能触发限流。
处理原则:
- 读
Retry-After,有就按它等; - 没有就指数退避,加随机抖动,避免所有客户端同时重试;
- 只对 429 和 5xx 重试,401、413、415 重试没有意义;
- 设置最大重试次数和总超时,防止任务堆积;
- 把突发流量放进队列,按服务端承受能力平滑发送。
重试不是越多越好。对于按次计费的接口,失败请求是否计费要以服务商规则为准,盲目重试可能放大成本。
五、超时与 5xx:区分网络问题和上游问题
超时先分连接超时和读取超时。连接超时通常是网络、DNS、代理或防火墙问题;读取超时说明请求已到达,但处理时间过长,常见于大图、多页 PDF 或上游排队。
建议给 OCR 请求单独设置超时,不要沿用普通文本接口的短超时。同步接口适合小图,大文档改用异步任务加轮询,能显著降低超时率。5xx 先重试一到两次,仍失败就切备用方案,同时记录请求 ID 找服务方核对。
六、识别为空:请求成功,不等于结果可用
“识别为空”是最容易被误判的一类。接口返回 200,文本却是空字符串、只有标点,或者字段全部为 null。按下面顺序排查:
- 图片本身有没有字:先人工打开原图,确认不是空白页或纯图。
- 方向是否正确:手机拍照常带旋转信息,服务端可能没按 EXIF 处理。
- 文字是否太小:整页缩略图里的小字,放大裁剪后往往能识别。
- 对比度是否太低:浅灰字、阴影、反光、透印都会拉低召回。
- 语言是否匹配:用中文模型识别俄文、英文单据,可能只输出零星字符。
- 版式是否超纲:多栏、竖排、表格、印章覆盖,传统 OCR 容易漏。
- 接口是否只返回结构化字段:有些接口把全文放在别的字段里,代码却只读了
text。
如果图片清晰、方向正确、语言也匹配,仍然为空,就升级处理:换预处理参数、换模型、改用多模态抽取,或转人工复核。不要用“再调一次”碰运气。
低置信度同样要处理。为字段设置阈值,低于阈值进入人工队列;对金额、证件号、日期做格式校验和交叉校验。日志里记录置信度分布,才能发现质量是慢慢变差还是一直很差。
七、可复用的错误处理代码
下面示例假设接口接受 multipart/form-data,实际路径和字段名请按服务商文档调整。
import random
import time
import requests
FATAL = {400, 401, 403, 413, 415}
RETRYABLE = {429, 500, 502, 503, 504}
def ocr_call(image_bytes, api_key, endpoint, max_retries=5):
last_error = None
for attempt in range(max_retries):
try:
resp = requests.post(
endpoint,
headers={"Authorization": f"Bearer {api_key}"},
files={"file": ("page.png", image_bytes, "image/png")},
timeout=(5, 90), # 连接 5 秒,读取 90 秒
)
except requests.Timeout as exc:
last_error = exc
else:
if resp.status_code in FATAL:
raise RuntimeError(f"fatal_{resp.status_code}: {resp.text[:300]}")
if resp.status_code == 200:
data = resp.json()
text = (data.get("text") or "").strip()
if not text:
raise ValueError("empty_result: check rotation, crop and format")
return data
if resp.status_code not in RETRYABLE:
raise RuntimeError(f"unexpected_{resp.status_code}")
last_error = RuntimeError(resp.text[:300])
retry_after = resp.headers.get("Retry-After") if "resp" in locals() else None
wait = float(retry_after) if retry_after else min(2 ** attempt + random.random(), 30)
time.sleep(wait)
raise RuntimeError(f"retry_exhausted: {last_error}")
这段代码只对可重试错误退避,对 401、413、415 立即失败,并把“空结果”当成需要人工介入的业务异常,而不是网络异常。
八、日志与监控要记什么
最少记录这些字段:请求 ID、状态码、错误码、耗时、图片尺寸、文件大小、重试次数、识别字符数、平均置信度、是否转人工。按接口和模型分组统计,才能看出是某类图片有问题,还是某个上游不稳定。
监控上建议设三个告警:错误率突增、空结果率突增、P95 延迟突增。只盯平均耗时容易漏掉长尾,而 OCR 的超时往往就出在长尾。
九、常见问题
401 换了 Key 还是失败?
先确认请求头实际发送的值,再确认 Key 与接口地址是否匹配。网关 Key 和厂商原生 Key 通常不能混用,环境变量也可能被旧进程缓存。
413 压缩后还是提示过大?
检查像素尺寸、PDF 页数和 Base64 膨胀。有时体积没超,但解码后的分辨率超过了接口限制,需要先缩放再上传。
429 加了重试还是频繁触发?
看是否所有客户端同时重试,以及并发数是否超过限额。加随机抖动、排队和并发上限,通常比单纯增加重试次数有效。
图片看得清,为什么识别为空?
优先检查方向、语言、对比度和是否只读了错误的返回字段。清晰但倾斜、反光或语言不匹配的图片,仍可能返回空结果。
重试会不会重复计费?
取决于服务商的计费规则和请求是否已被处理。对可能已成功的超时请求,先查任务状态或用请求 ID 核对,再决定是否重试。
下一步
排查稳定后,建议把 OCR 调用统一收口到一个客户端层:统一超时、重试、日志和降级,避免每个业务模块各写一套。需要统一接入多个视觉模型时,可以了解中商起航 AI 网关,注册见 /sign-up,模型与计费见 /pricing。
相关阅读:
