本文档手把手教你如何接入讯飞图片 OCR 服务,并实现智能解析。从零开始,10 分钟跑通整个流程。

目录

  1. 什么是讯飞 OCR?
  2. 快速开始(10 分钟上手)
  3. 理解 OCR 返回数据
  4. 解析规则详解
  5. 常见问题排查
  6. 进阶扩展

什么是讯飞 OCR?

讯飞 OCR(OCR for LLM)是讯飞开放平台提供的图片文字识别服务,可以将图片中的表格、文字自动识别并转换为结构化数据。

适用场景:

  • 员工工时单识别
  • 票据信息提取
  • 表格数据自动化录入
  • 文档数字化

你将学到:

  • 如何配置和接入讯飞 OCR
  • 如何调用 API 识别图片
  • 如何编写解析规则提取关键信息
  • 如何处理识别结果和常见错误

快速开始(10 分钟上手)

第一步:开通服务并获取密钥

  1. 访问讯飞开放平台
  2. 开通「OCR for LLM」服务
  3. 在应用详情页获取以下凭证:
IFLYTEK_APP_ID=你的应用ID
IFLYTEK_API_KEY=你的API密钥
IFLYTEK_API_SECRET=你的API密钥
IFLYTEK_API_BASE_URL=https://cbm01.cn-huabei-1.xf-yun.com
IFLYTEK_FUNCTION_ID=se75ocrbm  # 可选,默认值

安全提示:请勿将真实密钥提交到代码仓库,统一通过 .env 文件或 CI/CD 平台的加密环境变量管理。

第二步:安装依赖

# 克隆项目
git clone <你的仓库地址>
cd 项目

# 创建虚拟环境
python -m venv venv
source venv/bin/activate  # Windows: venv\Scripts\activate

# 安装依赖
pip install -r requirements.txt

第三步:配置环境变量

# 复制示例文件
cp .env.example .env

# 编辑 .env 文件,填入你的密钥
IFLYTEK_APP_ID=你的APP_ID
IFLYTEK_API_KEY=你的API_KEY
IFLYTEK_API_SECRET=你的API_SECRET
IFLYTEK_API_BASE_URL=https://cbm01.cn-huabei-1.xf-yun.com
IFLYTEK_FUNCTION_ID=se75ocrbm

第四步:启动后端服务

uvicorn backend.app:app --reload --host 127.0.0.1 --port 8000

注意:所有 API 请求都需要携带请求头 X-Token: financial

第五步:测试 OCR 识别

# 1. 创建测试工作区
curl -X POST http://127.0.0.1:8000/api/workspaces \
  -H 'Content-Type: application/json' \
  -H 'X-Token: financial' \
  -d '{"month":"2025-08","name":"测试工作区"}'

# 2. 上传图片进行识别
curl -X POST 'http://127.0.0.1:8000/api/workspaces/2025-08/upload?file_type=timesheet' \
  -H 'X-Token: financial' \
  -F 'files=@/path/to/your/image.png'

# 3. 查看识别结果
curl -H 'X-Token: financial' \
  http://127.0.0.1:8000/api/workspaces/2025-08/documents

如果看到返回结果中包含 ocr_metadataocr_table 字段,说明 OCR 识别已成功!


理解 OCR 返回数据

数据结构概览

OCR 识别后返回的数据结构如下:

{
    "text": "识别的原始文本(JSON字符串或纯文本)",
    "metadata": {
        "provider": "iflytek",           # 供应商标识
        "result_format": "json",         # 结果格式:json 或 plain
        "tables": [[...], [...]],        # 第一张识别到的表格(二维数组)
        "all_tables": [[[...]], ...],    # 所有识别到的表格(可选)
        "raw": {...},                    # 原始结构化对象(JSON格式时)
        "raw_text": "...",               # 原始文本(纯文本格式时)
        "sid": "会话ID(用于排查)"      # 可选
    }
}

表格数据结构

表格以三维数组的形式存储:

# tables 是一个列表,包含多个表格
tables = [
    # 第一个表格(二维数组)
    [
        ["姓名", "标准工时", "加班", "实际出勤", "月份"],  # 表头行
        ["张三", "152", "59", "211", "2025-08"],         # 数据行1
        ["李四", "152", "45", "197", "2025-08"],         # 数据行2
    ],
    # 第二个表格(如果有)
    [...]
]

在代码中使用

from pathlib import Path
from backend.infrastructure.iflytek import IFlyTekOCRClient

# 初始化客户端
client = IFlyTekOCRClient(
    app_id="你的APP_ID",
    api_key="你的API_KEY",
    api_secret="你的API_SECRET",
    api_base="https://cbm01.cn-huabei-1.xf-yun.com",
    function_id="se75ocrbm",
)

# 识别图片
result = client.extract_text(Path("/path/to/your/image.png"))

# 查看结果
print("原始文本:", result.text[:200])  # 前200个字符
print("元数据键:", result.metadata.keys())
print("第一张表格:", result.metadata.get("tables"))
print("所有表格:", result.metadata.get("all_tables", []))

解析规则详解

OCR 识别只是第一步,我们还需要从识别结果中提取关键信息。系统支持多条规则并行执行,自动选择最佳结果。

规则配置

config/ocr_rules.py 中配置规则和阈值:

# 阈值配置
OCR_THRESHOLDS = {
    "sum_tolerance": 0.1,        # 数值校验容差:|actual - (standard+overtime)| <= 0.1
    "header_scan_rows": 10,      # 表头扫描行数上限
    "max_search_steps": 6,       # 标签右/下搜索步数
    "sum_column_max_rows": 200,  # 列求和行数上限
    "numeric_max": 1000.0,       # 数值上限(防止异常值)
}

# 规则配置
OCR_RULES = [
    {
        "id": "规则1",
        "name": "工时结算单(多人)",
        "priority": 3,           # 优先级(数字越大优先级越高)
        "enabled": True          # 是否启用
    },
    {
        "id": "规则2",
        "name": "外派工时单(单人)",
        "priority": 2,
        "enabled": True
    },
    {
        "id": "规则3",
        "name": "外派人员工时表(单人)",
        "priority": 1,
        "enabled": True
    },
]

规则执行流程

通过
不通过
OCR识别结果
规则1: 多人表
规则2: 单人表A
规则3: 单人表B
有效性校验
评分排序
跳过
选择最佳结果
输出统一格式

规则1:工时结算单(多人表)

适用场景:包含多行员工数据的表格,有明确的表头行。

在这里插入图片描述

提取逻辑

  1. 在前 N 行内定位表头(“姓名”、“标准”、“加班”、"实际"等)
  2. 建立列索引映射
  3. 逐行提取数据
  4. 如果缺少"实际出勤",用"标准工时 + 加班"计算

伪代码示例

def extract_rule1_jiesuandan(tables, filename):
    # 1. 定位表头行和列索引
    table, header_row, cols = locate_header(
        row_keywords=['姓名'],
        col_keywords=['标准', '加班', '实际', '结算']
    )
    
    results = []
    # 2. 从表头下一行开始逐行提取
    for r in range(header_row + 1, len(table)):
        name = clean_name(table[r][cols['姓名']])
        std = to_float(table[r][cols['标准']]) or 0
        ot = to_float(table[r][cols['加班']]) or 0
        act = to_float(table[r][cols.get('实际')]) or (std + ot)  # 兜底计算
        
        if name:
            results.append({
                'name': name,
                'standard_hours': std,
                'total_overtime': ot,
                'actual_hours': act,
                'month': infer_month(tables),
                'data_source': filename,
                'original_file': filename
            })
    
    return results

规则2:外派工时单(单人表,文本优先)

适用场景:单人员工数据,可能以文本形式呈现(如 “152+53.5+5.5=211”)。
在这里插入图片描述

提取逻辑

  1. 姓名提取:优先从文本标签扫描,其次正则匹配,最后从文件名推断
  2. 数值提取:优先解析文本公式,失败则标签扫描
  3. 兜底策略:已知两项可推出第三项(如:actual = standard + overtime)

伪代码示例

def extract_rule2_paigongdan(tables, filename, metadata=None):
    # 1. 提取姓名(多种方式尝试)
    name = (
        scan_text_by_labels(['姓名', '员工姓名']) or
        regex_find_name(metadata) or
        filename_to_name(filename)
    )
    
    # 2. 优先从文本解析公式(如 "152+53.5+5.5=211")
    std, basic_ot, extra_ot, act = parse_text_equation(metadata)
    
    # 3. 如果文本解析失败,使用标签扫描
    if not any([std, basic_ot, extra_ot, act]):
        std = scan_numeric_by_labels(['标准工时', '正常工时'])
        act = scan_numeric_by_labels(['结算工时', '实际出勤', '总工时'])
        basic_ot = scan_numeric_by_labels(['基础加班', '平时加班'])
        extra_ot = scan_numeric_by_labels(['超时加班'])
    
    # 4. 兜底计算
    act = act or sum_filter(std, basic_ot, extra_ot)
    
    return [{
        'name': name or '',
        'standard_hours': std or 0,
        'total_overtime': (basic_ot or 0) + (extra_ot or 0),
        'actual_hours': act or 0,
        'month': infer_month(tables),
        'data_source': filename,
        'original_file': filename
    }]

规则3:外派人员工时表(行列交叉定位)

在这里插入图片描述

适用逻辑

  1. 通过列头定位列索引
  2. 在包含关键行头(如"合计"或姓名行)的位置取值
  3. 如果取不到值,在表头下方对该列进行 N 行求和

伪代码示例

def extract_rule3_paiyuan(tables, filename):
    # 1. 找到数据行(包含"合计"或姓名)
    row = find_data_row(['合计', '姓名'])
    
    # 2. 定位列索引
    col_std = find_col(['标准工时', '正常工时'])
    col_ot = find_col(['加班', '总加班'])
    col_act = find_col(['实际出勤', '结算工时', '总工时'])
    
    # 3. 读取值,如果为空则对列求和
    std = read_or_sum(row, col_std)
    ot = read_or_sum(row, col_ot)
    act = read_or_sum(row, col_act)
    
    return [{
        'name': infer_name(row) or '',
        'standard_hours': std or 0,
        'total_overtime': ot or 0,
        'actual_hours': act or 0,
        'month': infer_month(tables),
        'data_source': filename,
        'original_file': filename
    }]

评分与择优机制

系统会同时执行所有启用的规则,然后按以下标准选择最佳结果:

  1. 是否有有效值:优先选择 actual_hours > 0 的结果
  2. 实际值大小:在都有值的情况下,选择 actual_hours 更大的
  3. 规则优先级:如果前两项相同,按配置的 priority 选择
def score(rule_id, records):
    if not records:
        return (-1, 0, 0)  # 无效结果
    
    first = records[0]
    act = float(first.get('actual_hours') or 0)
    pri = get_rule_priority(rule_id, 0)  # 从配置读取优先级
    
    return (
        1 if act > 0 else 0,  # 是否有值
        act,                  # 实际值大小
        pri                   # 规则优先级
    )

# 选择最佳结果
candidates = [
    ("规则1", results1),
    ("规则2", results2),
    ("规则3", results3)
]
candidates.sort(key=lambda x: score(x[0], x[1]), reverse=True)
best_result = candidates[0][1]

有效性校验

在评分前,系统会对每条规则的结果进行有效性校验:

def is_valid(record):
    std = float(record.get("standard_hours") or 0)
    act = float(record.get("actual_hours") or 0)
    ot = float(record.get("total_overtime") or 0)
    tol = OCR_THRESHOLDS["sum_tolerance"]
    
    # 情况1:actual ≈ standard + overtime(允许容差)
    if act > 0 and abs(act - (std + ot)) <= tol:
        return True
    
    # 情况2:三者全为0(可能是空记录)
    if std == 0 and act == 0 and ot == 0:
        return True
    
    # 情况3:仅有actual有值(也视为有效)
    if act > 0 and std == 0 and ot == 0:
        return True
    
    return False

常见问题排查

1. 401/403 错误(认证失败)

可能原因

  • 环境变量配置错误
  • API 密钥过期或无效
  • 请求头缺少 X-Token

解决方法

# 检查环境变量
echo $IFLYTEK_APP_ID
echo $IFLYTEK_API_KEY

# 检查 .env 文件
cat .env | grep IFLYTEK

# 确保请求头正确
curl -H 'X-Token: financial' ...

2. 上传成功但表格为空

可能原因

  • 图片质量不佳(模糊、倾斜、光线不足)
  • 表格边框不完整
  • 规则阈值设置过严

解决方法

# 1. 检查 all_tables(可能有其他候选表格)
result = client.extract_text(path)
all_tables = result.metadata.get("all_tables", [])
print(f"识别到 {len(all_tables)} 张表格")

# 2. 调整阈值(config/ocr_rules.py)
OCR_THRESHOLDS = {
    "header_scan_rows": 15,  # 增加表头扫描行数
    "max_search_steps": 8,  # 增加搜索步数
}

# 3. 检查图片质量
# - 确保图片清晰、正对拍摄
# - 避免强光和阴影
# - 表格边框完整

3. 返回格式不是 JSON(result_format=plain)

说明:当 OCR 无法识别为结构化表格时,会返回纯文本。

处理方法

if result.metadata.get("result_format") == "plain":
    # 使用文本回退策略
    raw_text = result.metadata.get("raw_text", "")
    # 可以用正则表达式或关键词扫描提取信息
    extracted = parse_text_fallback(raw_text)

4. 超时或限频(429 错误)

解决方法

  • 避免并发请求过多
  • 实现重试机制(指数退避)
  • 检查配额和 QPS 限制
import time
from tenacity import retry, stop_after_attempt, wait_exponential

@retry(
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=1, min=2, max=10)
)
def call_ocr_with_retry(client, path):
    return client.extract_text(path)

5. 规则选择不正确

解决方法

# 在 config/ocr_rules.py 中调整优先级
OCR_RULES = [
    {"id": "规则1", "priority": 3, "enabled": True},  # 提高优先级
    {"id": "规则2", "priority": 2, "enabled": True},
    {"id": "规则3", "priority": 1, "enabled": False},  # 临时关闭
]

进阶扩展

如何新增自定义规则

场景:你遇到了一种新的表格样式,需要添加专用解析规则。

步骤

  1. 在配置中添加规则config/ocr_rules.py):
OCR_RULES = [
    # ... 现有规则 ...
    {
        "id": "规则4",
        "name": "新样式表格",
        "priority": 2,
        "enabled": True
    },
]
  1. 实现规则函数backend/extractors/ocr_image.py):
def extract_rule4_newstyle(tables, filename, metadata=None):
    """
    新规则实现
    输入:tables(三维数组)、filename(文件名)、metadata(元数据)
    输出:list[dict],字段包含 name/standard_hours/total_overtime/actual_hours/month 等
    """
    results = []
    
    # 1. 定位表头/行头/列头
    # 2. 提取数据
    # 3. 返回统一格式
    
    return [{
        'name': name,
        'standard_hours': std or 0,
        'total_overtime': ot or 0,
        'actual_hours': act or 0,
        'month': infer_month(tables),
        'data_source': filename,
        'original_file': filename
    }]
  1. 在择优函数中调用extract_image_data_with_multiple_rules):
def extract_image_data_with_multiple_rules(tables, filename, metadata=None):
    results1 = extract_rule1_jiesuandan(tables, filename)
    results2 = extract_rule2_paigongdan(tables, filename, metadata)
    results3 = extract_rule3_paiyuan(tables, filename)
    results4 = extract_rule4_newstyle(tables, filename, metadata)  # 新增
    
    candidates = [
        ("规则1", results1),
        ("规则2", results2),
        ("规则3", results3),
        ("规则4", results4),  # 新增
    ]
    # ... 择优逻辑 ...
  1. 调整阈值(如需要):
OCR_THRESHOLDS = {
    "header_scan_rows": 12,  # 根据新表格调整
    "max_search_steps": 7,
}
  1. 编写测试用例
def test_rule4_newstyle():
    tables = [[["表头1", "表头2"], ["数据1", "数据2"]]]
    result = extract_rule4_newstyle(tables, "test.png")
    assert len(result) > 0
    assert result[0]["name"] == "预期值"

阈值调优建议

阈值参数 说明 调优建议
sum_tolerance 数值校验容差 如果数据精度要求高,设为 0.01;一般设为 0.1
header_scan_rows 表头扫描行数 表格表头在下方时,增加此值(如 15-20)
max_search_steps 标签搜索步数 表格单元格间距大时,增加此值(如 8-10)
sum_column_max_rows 列求和行数上限 数据行很多时,适当增加(如 300-500)
numeric_max 数值上限 防止异常值,根据业务调整

关键词词表外置(推荐)

将规则中使用的关键词抽取到配置文件,便于维护:

# config/ocr_keywords.py
KEYWORDS = {
    "name": ["姓名", "员工姓名", "员工"],
    "standard": ["标准工时", "正常工时", "应出勤"],
    "overtime": ["加班", "总加班", "加班合计"],
    "actual": ["实际出勤", "结算工时", "总工时", "实际"],
    "month": ["月份", "年月", "结算月份"],
}

# 在规则中使用
from config.ocr_keywords import KEYWORDS

name_col = find_col(KEYWORDS["name"])

生产环境部署

Docker Compose 配置

# docker-compose.prod.yml
services:
  backend:
    environment:
      - IFLYTEK_APP_ID=${IFLYTEK_APP_ID}
      - IFLYTEK_API_KEY=${IFLYTEK_API_KEY}
      - IFLYTEK_API_SECRET=${IFLYTEK_API_SECRET}
      - IFLYTEK_API_BASE_URL=${IFLYTEK_API_BASE_URL}
      - IFLYTEK_FUNCTION_ID=${IFLYTEK_FUNCTION_ID}

CI/CD 配置(Bitbucket Pipelines):

  • 在仓库设置中添加加密环境变量
  • 推送到主分支后自动构建和部署

总结

通过本文档,你已经学会了:

  1. 配置和接入:如何获取密钥、配置环境、启动服务
  2. 基本使用:如何调用 API、查看返回数据
  3. 规则编写:理解三种规则的工作原理和适用场景
  4. 问题排查:常见错误的定位和解决方法
  5. 进阶扩展:如何新增规则、调优阈值

下一步建议

  • 尝试识别不同类型的图片,观察识别效果
  • 根据实际业务需求调整规则和阈值
  • 为常用表格样式编写专用规则
  • 在生产环境中部署并监控识别准确率

相关资源

  • 讯飞开放平台文档
  • 项目代码:backend/infrastructure/iflytek.py(OCR 客户端)
  • 规则实现:backend/extractors/ocr_rules/(规则模块)
  • 配置示例:config/ocr_rules.py(规则和阈值配置)
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐