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。

图片预处理建议按这个顺序做:

  1. 用图像库读取图片,确认真实格式,不信任扩展名。
  2. 按长边限制等比缩放,例如先压到 2000–3000 像素再测。
  3. 修正 EXIF 方向,避免文字横躺导致识别为空。
  4. 转成接口支持的格式,控制压缩质量,别把字压糊。
  5. 多页 PDF 拆分后逐页处理,或改用支持整份文档的接口。
  6. 记录处理前后的体积和尺寸,方便复盘是哪一步超限。

不要为了过 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。

相关阅读: