适用场景:从纸质票到数字化票据的最后一公里
在企业财务报销、行程管理、票据核验等业务中,火车票(含高铁、动车、普通车票)是最常见的纸质凭证之一。传统人工录入方式存在效率低、易出错、难追溯等问题。借助OCR技术,将火车票图片直接转化为结构化数据,可大幅提升自动化水平。
典型业务场景包括:
- 差旅费报销自动填报:员工上传火车票照片,系统自动提取乘车人、车次、日期、金额等信息,免去手动输入。
- 行程单电子化归档:对接ERP或费控系统,将识别结果存入数据库,便于后续统计与审计。
- 票务信息校验:对比用户填写的行程与票面信息是否一致,减少虚假报销风险。
接口能力边界
火车票识别接口(slug: ocr-train-ticket)支持国内全类型火车票,包括红色软纸票、蓝色磁票、电子客票报销凭证等。单次请求返回13个字段,覆盖票面所有关键信息。
接口限制:
- 鉴权方式:仅限已登录用户调用,匿名访问不开放。请求需携带Bearer Token形式的API Key。
- QPS:每秒最多2次请求,超出限制会返回频率限制错误。
- 图片输入:支持URL和Base64两种方式,单张图片大小建议不超过10MB,分辨率不低于300x300像素。
接口不承诺识别成功率(与图片质量直接相关),但实测对清晰、无遮挡、正角度拍摄的票面可达较高准确率。
请求参数与鉴权
接口地址
POST https://v1.apizero.cn/api/ocr-train-ticketHeader参数
| 参数名 | 必须 | 类型 | 说明 |
|---|---|---|---|
| Authorization | 是 | string | 格式:Bearer <你的API Key> |
| Content-Type | 否 | string | 建议设为application/json |
请求体(JSON)
请求体是一个单元素数组,内含一个对象,包含两个字段:
[ { "input_type": "url", "input_data": "https://example.com/train-ticket.jpg" } ]| 字段 | 必须 | 类型 | 说明 |
|---|---|---|---|
| input_type | 是 | string | 图片传输方式,可选url(公网可访问的图片链接)或base64(图片的Base64编码,可包含data:image/xxx;base64,前缀) |
| input_data | 是 | string | 图片内容:若input_type=url则填http/https链接;若input_type=base64则填Base64字符串 |
注意:请求体需包裹在数组内(API设计为支持批量,但目前仅建议单张传入)。
代码接入示例
1. cURL请求示例
以下命令使用公网图片URL进行识别,需将$APIZERO_API_KEY替换为实际密钥:
curl -sS \ -X POST \ -H "Authorization: Bearer $APIZERO_API_KEY" \ -H "Content-Type: application/json" \ -d '[{"input_type": "url", "input_data": "https://example.com/train-ticket.jpg"}]' \ "https://v1.apizero.cn/api/ocr-train-ticket"若图片为本地文件,可先转为Base64并内嵌:
# 将图片转换为Base64字符串(去掉换行) IMAGE_BASE64=$(base64 -w0 /path/to/ticket.jpg) # 发送请求 curl -sS -X POST \ -H "Authorization: Bearer $APIZERO_API_KEY" \ -H "Content-Type: application/json" \ -d "[{\"input_type\":\"base64\",\"input_data\":\"$IMAGE_BASE64\"}]" \ "https://v1.apizero.cn/api/ocr-train-ticket"2. Python 接入示例
使用requests库,代码简洁且易于集成到现有工程:
import requests import base64 API_URL = "https://v1.apizero.cn/api/ocr-train-ticket" API_KEY = "你的API Key" # 从环境变量或配置文件读取 def ocr_train_ticket(image_path_or_url, is_url=True): headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" } if is_url: body = [{ "input_type": "url", "input_data": image_path_or_url }] else: with open(image_path_or_url, "rb") as f: encoded = base64.b64encode(f.read()).decode("utf-8") body = [{ "input_type": "base64", "input_data": encoded }] resp = requests.post(API_URL, headers=headers, json=body) resp.raise_for_status() return resp.json() # 使用示例 result = ocr_train_ticket("https://example.com/train-ticket.jpg", is_url=True) print(result)注意:生产环境应将API Key从环境变量读取(如os.getenv("APIZERO_API_KEY")),避免硬编码。
返回值解读
成功响应(HTTP 200)JSON结构如下:
{ "code": 0, "msg": "成功", "request_id": "req_abc123", "data": { "start_station": "北京南", "end_station": "上海虹桥", "train_num": "G101", "name": "张三", "id_num": "110101199001011234", "seat_cls": "二等座", "seat_num": "05车12A号", "ticket_num": "E123456789", "sale_num": "G123456", "time": "2024-01-15 09:00", "price": "553.00", "total_amount": "¥553.00", "sale_station": "北京南" } }data字段详解
| 字段 | 类型 | 说明 |
|---|---|---|
| start_station | string | 出发站名称 |
| end_station | string | 到达站名称 |
| train_num | string | 车次号(如G101) |
| name | string | 乘车人姓名 |
| id_num | string | 身份证号(脱敏处理视业务需求) |
| seat_cls | string | 座位等级(二等座/一等座/硬卧等) |
| seat_num | string | 座位编号(如05车12A号) |
| ticket_num | string | 票号(识别码) |
| sale_num | string | 售票编码 |
| time | string | 出发时间(格式:YYYY-MM-DD HH:mm) |
| price | string | 票价金额(数字字符串,不含货币符号) |
| total_amount | string | 含货币符号的总金额(如¥553.00) |
| sale_station | string | 售票站名称 |
注意:id_num字段包含敏感个人信息,在日志存储及前端展示时需进行脱敏处理(如110101********1234)。
错误码说明
| code | msg | 处理建议 |
|---|---|---|
| 0 | 成功 | 正常解析 |
| 1001 | 参数错误 | 检查请求体格式,确保 input_type 和 input_data 不为空 |
| 1002 | 图片不存在或无法下载 | 若使用URL模式,确认图片链接可公网访问;若使用Base64,检查编码是否完整 |
| 1003 | 识别失败 | 图片模糊、非火车票、或票面覆盖严重,建议重新拍照上传 |
| 1004 | 频率限制 | 当前QPS为2/s,请控制并发或添加重试退避 |
| 1005 | 鉴权失败 | 检查 Authorization Header 格式是否正确,API Key是否有效 |
| 2001 | 系统内部错误 | 联系服务提供商排查 |
常见错误排查
- 401 Unauthorized:确认Header中
Authorization前缀是否为Bearer(注意大小写),且API Key未过期。 - 400 参数错误:检查请求体是否包裹在数组内(
[{...}]),而非直接传对象。部分开发者容易遗漏最外层方括号。 - 图片无法识别:首选URL方式调试,确认图片链接未失效且为火车票正面照;若使用Base64,建议去掉换行符。
- 返回字段缺失:部分旧版票面可能缺少某些字段(如sale_station),这些字段可能为空字符串,需在业务逻辑中做空值判断。
工程化注意事项
1. 图片质量优化
- 拍照时确保票面平整、无反光、无折叠,字符清晰可辨。
- 建议图片分辨率不低于800x600,文件大小不超过5MB。
- 对于批量上传场景,可增加预检步骤:检测图片尺寸和清晰度,对不合格图片提前提示用户。
2. 并发与重试策略
接口QPS限制为2次/秒,若业务需要高吞吐(例如财务月末集中报销),可引入队列和限流:
import time import threading class RateLimiter: def __init__(self, max_per_second): self.min_interval = 1.0 / max_per_second self.last_call = time.monotonic() self.lock = threading.Lock() def acquire(self): with self.lock: now = time.monotonic() sleep_time = self.min_interval - (now - self.last_call) if sleep_time > 0: time.sleep(sleep_time) self.last_call = time.monotonic()同时建议对非200状态码(如429或503)实现指数退避重试(最多3次)。
3. 数据安全与脱敏
接口返回的id_num(身份证号)和name(姓名)属于高度敏感信息。在存储和传输过程中应遵循最小权限原则:
- 数据库表中对身份证号进行AES加密存储,仅展示脱敏格式。
- 日志中禁用完整身份证号,可统一替换为
***。 - 前端展示时使用
*隐藏中间8位。
4. 异常情况处理
- 对于返回
code != 0的情况,记录request_id以便后续排查。 - 考虑识别置信度(接口暂未提供,可结合业务规则校验:如日期格式、金额合理性、车站名称是否在已知列表中)。
参考文档
- 火车票识别接口文档
- 原始Markdown文档