Unlimited OCR + Python 接入实战:batch endpoint 的 pages 参数和多页响应解析,两个坑帮你踩完
上周三我们有个合同管理系统要加 OCR 能力,产品说"扫描件丢进去就能出结构化文本"。在 Hacker News 上看到 Unlimited OCR 获得较高关注,开源项目也上线了,心想接进来应该很快。单页图片确实 10 分钟跑通了——但等我切到 batch 模式处理多页 PDF 的时候,连踩两个坑,折腾了大半天才搞明白。这篇把这两个坑讲透,希望能节省你的排查时间。
这篇适合谁
- 已经注册了 Unlimited OCR API Key,准备接入多页 PDF 处理的后端开发
- 单页调用跑通了但 batch/多页模式返回结果不完整,怀疑是自己代码问题的人
- 想在 Python 项目里批量处理扫描合同、发票、报告等长文档的开发者
- 用过 PaddleOCR 本地部署但嫌运维麻烦,想切云端 API 的团队
整体流程
- 注册账号拿 API Key(2 分钟)
- 跑通单页图片识别,验证 Key 可用
- 切到多页 PDF,理解 batch endpoint 的参数格式差异
- 处理多页响应的嵌套结构差异,写出通用解析函数
- 加上限流保护和错误重试,部署到生产
graph TD
A[拿到 API Key] --> B[单页图片测试]
B --> C{需要多页?}
C -->|是| D[batch endpoint]
C -->|否| E[直接用单页]
D --> F[注意 pages 参数格式]
F --> G[注意响应嵌套层级]
G --> H[统一解析函数]
H --> I[加限流+重试]
先说结论
| 差异点 | 单页 endpoint | batch/多页 endpoint |
|---|---|---|
| pages 参数 | 不需要传 | 必须传数组 [1,2,3],不是字符串 "1,2,3" |
| 响应里 blocks 层级 | resp['blocks'] 直接是列表 |
resp['pages'][i]['blocks'] 多套一层 |
| confidence 位置 | 顶层 resp['confidence'] |
每个 page 对象里各有一个 |
| 典型报错 | 参数格式错误(400)或认证失败(401) | 静默返回只有第一页(不报错!) |
最坑的是第二点——多页模式下如果你用单页的解析逻辑去读 resp['blocks'],它不会报错,只会拿到第一页的内容,后面的页全部丢失。我排查了两个小时才发现数据其实返回了,只是取错了层级。
第一步:跑通单页,确认环境没问题
先确保你的 Key 是好的。去 Dashboard -> API Keys 页面拿到 Key 之后:
import requests
with open('image.png', 'rb') as f:
resp = requests.post(
'https://api.unlimitedocr.com/v1/ocr',
headers={'Authorization': 'Bearer YOUR_API_KEY'},
files={'file': f},
data={'language': 'chi_sim'}
)
如果返回 {"text": "...", "confidence": 85},说明通了。如果看到这个:
HTTP 401 Unauthorized
{"error": "Invalid API key or API key not provided"}
大概率是请求头写成了 Authorization: YOUR_API_KEY,漏掉了 Bearer 前缀。注意 Bearer 后面有个空格。
第二步:切到多页 PDF——第一个坑来了
单页跑通之后我直接把 PDF 丢上去,加了个 pages 参数想指定解析 1-5 页:
data={
'language': 'chi_sim',
'pages': '1,2,3,4,5' # ← 错的!
}
返回 200,但只有第一页的结果。没有报错,没有 warning,就是静默地只给你一页。说实话一开始我以为是 PDF 本身有问题,换了三个文件才意识到是参数格式不对。
正确写法——pages 要传 JSON 数组:
import json
data={
'language': 'chi_sim',
'pages': json.dumps([1, 2, 3, 4, 5])
}
或者如果你用 json= 参数而不是 data=,直接传 list 就行。但用 files= + data= 组合时(上传文件必须这么写),data 里的值只能是字符串,所以得手动 json.dumps。
文档里的示例全是单页的,压根没提这个区别。我是翻了 GitHub Issues 才确认的。
第三步:解析多页响应——第二个坑
单页响应长这样:
{
"text": "识别结果...",
"confidence": 87,
"blocks": [{"text": "段落1", "bbox": [...]}]
}
多页响应长这样:
{
"pages": [
{"page": 1, "confidence": 85,
"blocks": [{"text": "...", "bbox": [...]}]},
{"page": 2, "confidence": 82,
"blocks": [...]}
]
}
看到区别了吗?单页时 blocks 在顶层,多页时 blocks 藏在 pages[i] 里面。如果你写了一个通用解析函数直接读 resp['blocks'],多页模式下会抛 KeyError——或者更糟,如果你用了 .get('blocks', []),它会静默返回空列表,你以为没识别到内容。
我写了个兼容函数:
def parse_ocr_response(resp_json):
if 'pages' in resp_json:
return resp_json['pages']
return [resp_json] # 包装成统一格式
这样不管单页还是多页,后续逻辑都统一遍历列表就行。每个元素保证有 blocks 和 confidence。
第四步:加上限流保护
Unlimited OCR 默认 Rate Limit 以官方文档为准,批量处理 PDF 的时候容易触发限流:
HTTP 429 Too Many Requests
{"error": "Rate limit exceeded. Please retry after 60 seconds"}
我用了个简单的信号量控制并发:
import asyncio
sem = asyncio.Semaphore(10) # 最多 10 并发
async def ocr_with_limit(file_path):
async with sem:
# 调用 OCR API
await asyncio.sleep(1.1) # 保险间隔
控制每秒不超过 1 次请求是比较稳妥的起点,具体上限请以官方文档为准。如果业务量较大需要更高 QPS,可咨询官方的企业版方案。
不同场景怎么选
只处理单张图片/截图:直接用基础 endpoint,不用管 pages 参数,files={'file': f} 即可。
处理多页 PDF(合同/报告/发票):必须走 batch 模式,pages 参数传数组,解析时用上面的兼容函数。建议开 enhance_image: true,扫描件质量参差不齐。
混合场景(图片+PDF 都有):统一用兼容解析函数,上传前判断文件类型决定要不要传 pages。文件大小上限请以官方文档为准。
需要对接多种 AI 模型做后处理:OCR 拿到文本之后,很多人会丢给大模型做结构化提取。这时候如果你同时调多个模型 API,用 OpenRouter 或 ofox.io 这类聚合网关会方便一些,改个 base_url 就能切模型,不用每个厂商单独维护 Key 和 SDK。各平台的定价和加价比例以其官方定价页为准,可根据预算选择合适套餐。
中文识别场景:language 必须显式指定 chi_sim(简体)或 chi_tra(繁体),别用 auto。图片分辨率不足时识别率会明显下降,开 enhance_image 有一定改善效果,但并非万能。
踩坑记录 / 常见问题 FAQ
Q: pages 参数传了数组但还是只返回第一页?
A: 检查你是不是用了 data= 传参。data 字典里的值必须是字符串,所以要 json.dumps([1, 2, 3])。如果你直接传了 Python list 对象,requests 会将 list 展开为重复字段(如 pages=1&pages=2&pages=3),而非 JSON 数组,服务端通常无法正确解析,就会 fallback 到只处理第一页。
Q: confidence 低于多少需要重试或人工复核?
A: 没有官方标准,需根据业务容忍度自行设定。作者经验是低于 65 的会进人工队列,可根据业务需求调整这个阈值。
Q: 文件超过限制怎么办?
A: 超出大小限制会直接返回 413 Payload Too Large。具体的单图和 PDF 上限请以官方文档为准。如果是高分辨率扫描件,可以先压缩再传。用 Pillow 降低 DPI 是常见做法,体积可显著减小(实际比例因图像内容和格式而异),识别率通常影响不大。
Q: 批量处理时怎么避免触发 429 限流?
A: 控制请求频率,用 asyncio.Semaphore 或 time.sleep 限速均可。具体的速率上限请以官方文档为准,如需更高 QPS 可咨询企业版方案。
Q: 返回结果乱码,中文全是问号?
A: 大概率是 language 参数没指定或者写错了。中文必须是 chi_sim 或 chi_tra,写 chinese 或 cn 都不行,会返回 400 Bad Request: Language code 'cn' is not supported。
小结
Unlimited OCR 接入确实快,单页场景基本是"贴个 Key 就能跑"的体验。但多页 PDF 场景有两个文档里没写清楚的坑:pages 参数必须是 JSON 数组而非逗号分隔字符串,响应结构的嵌套层级在单页和多页之间不一致。写一个兼容解析函数,加上限流逻辑,生产环境就基本稳了。后续版本会不会统一这两个 endpoint 的行为尚不确定,目前只能在应用层做适配。
更多推荐

所有评论(0)