文档数据通用处理思维框架
从数据整理项目实践中提炼的文档数据处理方法论。 不绑定具体技术栈,适用于任何"多格式文档 → 结构化信息提取"场景。
一、核心理念:读取与写入严格分离
原则:处理管道中的任何环节都不得修改原始文件。所有输出写入独立的目标位置。
这不是简单的"不要覆盖"——它塑造了整个系统的架构:
读取层可以是无状态的纯函数,输入路径、输出内容,易于测试和组合
写入层集中管理,天然支持断点续跑(读目标已有行数即可知进度)
回滚零成本:删掉输出重跑即可,原始数据永远安全
并发安全:多个读取进程不互相干扰,写入串行化即可
违反此原则的典型场景:在原 xlsx 上直接修改单元格。一旦中途崩溃,已修改的部分无法区分于未修改的,既不能续跑也不能回滚。
二、多格式读取的分层策略
2.1 三层提取优先级
面对任何文件格式,按以下优先级尝试提取:
第1层:结构化解析(按格式规范精准提取)
↓ 失败
第2层:通用文本提取(剥离二进制,保留可读文本)
↓ 失败
第3层:元信息兜底(仅使用文件名、路径、扩展名)实际映射:
关键:第3层永远不失败。即便文件损坏到完全无法读取,路径和文件名本身也是信息。一个返回空字符串的函数不如一个返回 [格式: 无法提取] 的函数有用——后者至少告诉下游"我尽力了"。
2.2 文本与图片的双重提取
文档信息不只存在于文本中。结构化提取应同时返回两个维度:
文本:结构化可搜索、低 token 消耗、适合精确匹配
图片:承载版式、签名、印章、手写内容等文本无法表达的信息
提取结果统一为 (text, images) 二元组,由下游决定如何使用。这避免了读取层提前做"只用文本"或"只用图片"的决策。
2.3 内容截断的必要性
任何格式都可能产生超大内容(一个 100MB 的 .doc 理论上包含数百万字符)。截断不是偷懒,而是保护:
保护 API 调用不超 token 限制
保护内存不被单个文件撑爆
保护处理时间不被异常文件拖垮
截断阈值的选择:足够覆盖 95% 的正常文件,同时阻止 5% 的异常文件拖垮系统。截断时必须标注原始大小,让下游知道信息是否完整。
三、LLM 结构化提取的防御性设计
3.1 输出格式不可控 → 多层容错解析
LLM 的 JSON 输出有三种典型"不规矩":
直接输出合法 JSON(理想情况)
在 JSON 外包裹 Markdown 围栏
```json ... ```在 JSON 前后添加解释性文字
对应的三层解析策略:
第1层:直接 json.loads() → 成功即返回
第2层:正则提取 Markdown 围栏内容 → json.loads() → 成功即返回
第3层:定位首个 { 和末个 } → json.loads() → 成功即返回
第4层:全部失败 → 返回全空默认值(而非抛异常)核心思维:解析函数的职责是"尽力从混乱输入中提取结构化数据",而非"验证输入是否合法"。验证是调用方的事,提取函数不应因格式问题导致整个流程中断。
3.2 单次提取不可靠 → 多次调用 + 投票
LLM 对同一输入的多次调用结果存在随机性(temperature > 0 时)。对于取值空间有限的结构化字段(类别、编号、金额),投票取众数是有效的去噪手段。
适用条件判断:
投票次数的选择:奇数次(3/5/7),3 次是最低可行方案,5 次在准确率和成本间取得平衡。
3.3 开放提取不可靠 → 约束选择 + 注入候选
让 LLM 自由输出项目名称,准确率低且无法验证。更好的方式:
首次提取:让 LLM 自由输出,作为候选
验证:候选是否在已知项目列表中
重试时注入候选:将最可能匹配的 N 个项目名注入 prompt,让 LLM 从列表中选择
这把"开放式生成"转变为"受限选择",大幅提升准确率和可控性。
关键:注入的候选列表要精而不是多。top-128 比 "全部项目名" 效果更好——选项越少,LLM 选择越准确。
四、匹配策略的渐进精确原则
4.1 四级匹配框架
任何"用户输入 ↔ 标准列表"的匹配场景,都应遵循从精确到模糊的渐进策略:
第1级:精确匹配 — O(1) 或 O(n) 的确定性比较
↓ 失败
第2级:规范化匹配 — 去除干扰因素后精确比较(空格、大小写、标点)
↓ 失败
第3级:子串匹配 — 互为子串即可,容忍增减词
↓ 失败
第4级:语义匹配 — 向量相似度或编辑距离,容忍同义改写为什么不能直接用语义匹配?
语义匹配有误匹配风险——"项目A"和"项目B"可能语义相近但完全不同
语义匹配有延迟——向量检索虽快,仍比字符串比较慢几个数量级
语义匹配有依赖——需要 Embedding 服务可用
实测数据:前三级纯字符串匹配覆盖 ~75% 的情况,只有 ~25% 需要语义匹配
经验法则:简单确定性方法优先使用,语义/概率方法作为兜底。确定性方法不会"猜错",概率方法一定会"有时猜错"。
4.2 中文文本的规范化细节
中英文混排场景中,规范化匹配尤其重要:
中文全角空格(U+3000)与英文半角空格(U+0020)混用
中英文标点混用(
,vs,、、vs/)大小写不一致(项目编号、英文缩写)
规范化匹配必须同时处理全角和半角空格,否则约 15% 的本应匹配的项目会被遗漏。
五、外部依赖的降级兜底设计
5.1 降级不是可选功能,是核心架构
任何依赖外部服务的系统(API、数据库、向量引擎),都必须回答:服务不可用时系统怎么办?
data-collation 的答案是一个三级降级链:
FAISS 向量检索(最快、最准)
↓ 不可用(未安装 / API 故障 / 维度不匹配)
SequenceMatcher 字符级匹配(较慢、较准)
↓ (无更底层的降级,此层总能工作)降级设计的关键约束:
降级路径必须总能工作:最底层不能依赖任何外部服务
降级是自动的:调用方不应感知到降级的发生
降级可观测:日志记录降级事件,方便事后分析
5.2 空输入的特殊处理
当 LLM 无法提取出任何候选名称时(输入文件内容与项目无关、文件损坏等),常规的重试注入候选策略失效。此时需要一条替代路径:
从文件路径和已有标签中提取关键词
用关键词做向量检索,获取候选项目列表
将候选列表注入 prompt,让 LLM 做"受限选择"
思维:当主要通道(内容提取)走不通时,从辅助信息(文件名、标签、路径)中挖掘线索。路径本身是人类组织的,文件名通常包含项目相关信息。
六、处理管道的健壮性设计
6.1 断点续跑:生产环境的刚需
长时间运行的处理管道(数百至数千文件 × 多次 API 调用)必须支持断点续跑。实现方式:
输出写入独立的目标区域(sheet / 文件 / 数据库表)
启动时读取目标区域已有记录数,作为起始位置
跳过已处理的输入,只处理增量
关键约束:任何修改输出逻辑的代码都必须保持此行为。一个破坏断点续跑的"优化"比没有优化更糟——它会导致重复处理或数据丢失。
6.2 批量保存:平衡数据安全与 I/O 开销
每处理一条就保存 → I/O 开销大;全部处理完再保存 → 中途崩溃全丢。
折中方案:每 N 条保存一次。N 的选择取决于:
单条处理耗时(API 调用约 1-5 秒,N=100 意味着最多丢失 100 条的进度)
保存操作耗时(写入 xlsx 约 1-3 秒,100 条保存一次几乎无感)
6.3 异常隔离:一个文件的失败不影响其他
处理管道中,单文件级别的异常(读取失败、API 超时、JSON 解析失败)不应导致整体中断。每条记录的处理应是独立的 try-except 块,失败时记录错误信息到专用字段,继续处理下一条。
错误信息应包含:异常类型 + 简要描述,写入输出的"读取失败"字段。这比静默跳过更好——用户可以事后筛选出失败记录,针对性重跑。
6.4 进度可见:用户不应盲等
长时间运行的管道必须提供进度反馈。但日志输出和进度条可能互相干扰(进度条被日志打断会换行错乱)。
解决方案:解耦进度显示与日志输出。业务代码只管写日志(logger.info/debug/error),Handler 层负责将日志输出路由到进度条的 write() 方法,避免打断进度条刷新。业务代码完全不知道进度条的存在。
七、图片处理的成本控制
7.1 压缩 → 传入 → 丢弃 的流水线
图片在处理管道中只是中间载体,不需要保留原始质量:
压缩:长边限制 1024px + JPEG quality 75,在视觉识别质量和 token 消耗间取得平衡
传入:base64 编码后作为 VLM 输入
丢弃:不缓存 base64,用完即弃,避免内存堆积
7.2 图片数量限制
每个文件最多传入 5 张图片(PPT 限 2 张)。原因:
超过 5 张后 VLM 的信息利用效率递减
每张图片消耗的 token 是文本的数十倍
5 张足以覆盖文档首页、签名页、金额页等关键信息
选择策略:如果文件内嵌图片超过 5 张,按出现顺序取前 5 张。不做"智能选图"——这个决策的复杂度不值得收益。
八、配置管理:环境变量优于硬编码
8.1 敏感信息绝不作为默认值
# 错误:API Key 作为默认值硬编码
API_KEY = os.environ.get("OPENAI_API_KEY", "sk-xxx")
# 正确:仅从环境变量读取,缺失时明确报错
API_KEY = os.environ.get("OPENAI_API_KEY")
if not API_KEY:
raise ValueError("OPENAI_API_KEY 环境变量未设置")8.2 可调参数暴露为环境变量
所有可能需要调整的参数都应支持环境变量覆盖:
API 模型名称(LLM_MODEL / VLM_MODEL)
Embedding 模型和维度(EMBEDDING_MODEL / EMBEDDING_DIMENSION)
相似度阈值(SIMILARITY_THRESHOLD)
批次大小和重试次数
这允许不修改代码即可适配不同环境(开发 / 测试 / 生产)和不同模型。
九、思维框架速查表
十、反面教训
不要假设 LLM 输出格式——三层容错不是过度设计,是实战必须
不要跳过字符串匹配直接上语义匹配——75% 的情况不需要,且确定性方法不会误匹配
不要在读取层做写入——一旦破坏只读原则,断点续跑和数据安全都无从谈起
不要忽略文件路径本身的信息——文件名、目录结构是人类组织的,包含大量隐含信息
不要缓存大量 base64 图片——每个图片约 100-500KB 的 base64,千个文件就是百 MB 级内存占用
不要让日志和进度条互相干扰——解耦不是锦上添花,是长时运行管道的基本要求
不要让一个文件的失败中断整体——异常隔离 + 错误记录字段,让管道永远跑完
不要在三版并存时不提取公共代码——复制粘贴的维护成本是指数级的