2290 字
约 7 分钟
18
S20-A24 · PDF 里的表格:切分器为什么把"一行"拆成"三段"

一个"合同要素抽取"的 RAG 系统,从 PDF 里抽条款。上线后发现"付款条款"的抽取总是错位——因为"付款条款"在一张两列表格里,而文本切分器按"阅读顺序"输出,把"表格行"变成了"项目名 换行 内容 换行 项目名 换行 内容"——于是"语义被拉平"。

PDF 解析有三层难点:文字流(无结构)、版面(有结构但需推断)、以及语义(表格要按行列理解)。这个"层"的错位很典型:切分器假设"输入是线性文本",而 PDF 是"二维版面"——于是"它做的每一件事(按段落、按字数)都建立在错误的假设上"。
而"切分器"通常只处理第一层。

1. 三种"PDF 内容"的解析难度

类型 难点
电子版(有文字层) 顺序与列错乱
扫描版(图片) 需 OCR,错误率
混合(部分图片) 两部分要拼

第一项"看起来简单":有文字就能提——而"提出来是一堆按坐标排的文字",顺序由"内容流"决定,不由"视觉"决定。这个"内容流"是"生成 PDF 时的绘制顺序":它可能是"先画所有标题,再画所有正文"——于是"提取出来的文字"与"眼睛看到的顺序"完全不同。

2. "表格被拉平"的三种表现

表现 后果
行列关系丢失 "金额"与"项目"错配
单元格合并丢失 "跨行"的内容只出现一次
跨页表格断裂 "后半张表"成了"另一段"

第一项的"后果"在抽取任务上很具体:"付款方式:电汇"会被拆成"两条事实",而模型可能把它们"分别引用到不同结论"里——于是"答案错得很自然"。
第一项最致命:"项目名 换行 内容"这种输出,"列"的信息完全没了——于是"付款方式:电汇"可能被读成"付款方式"与"电汇"两条独立事实。

"版面感知"的最小实现是"按坐标聚类",而它比"按换行"可靠:

// 伪代码:把"同一行的文字"按 y 坐标聚在一起
lines = {}
for ch in chars:                    # 每个字符带 (x, y)
    key = round(ch.y / 2)           # 2pt 容差
    lines.setdefault(key, []).append(ch)
rows = [sorted(v, key=lambda c: c.x) for k, v in sorted(lines.items())]
// 收益:得到"视觉上的行",而不是"内容流里的行"

3. "保住表格"的三种做法

做法 效果
用版面感知的解析器(如按坐标聚类) 好(能重建行列)
先解析成 HTML/CSV 再切 最好(下游可结构化)
用模型"看图"(多模态) 灵活(成本高)

第二项是"工程上最稳":**把每页转成"结构化块"(段落/表格/标题)**第二项的"代价"是"多一个中间层":要维护"块模型"与"块 → chunk"的映射——而它换来"切分不再依赖文字流"这件根本性改善。
,表格转成"行列结构"——于是"切分"发生在"结构之后",而不是"文字流上".

"表格 → chunk"的转换要"保语义",而它有个"最小模板":

// 一条"自解释"的表格 chunk(四行)
表名:付款条款(第 2 页)
表头:项目 | 内容
本行:账期 | 验收后 30 日
来源:合同 A-2024-11.pdf#page=2
// 判据:把这条 chunk 单独给一个"不知道上下文的人",他能读懂

4. "跨页表格"的三种处理

做法 说明
按"表头重复"拼接 依赖"表头在每页重复"
按"列的坐标一致"拼接 更通用(需版面)
不拼(标注"续表") 最保守(但语义断裂)

第二项的"前提"是"版式稳定":同一份合同"前几页与后几页"用同一模板——而这在"合并了多份文档的 PDF"里不成立,于是"跨文档"要"先分段"。
第二项是"最通用"的:同一张表的"列边界"在页间通常一致——于是"坐标对齐"能把"后半张"接回去.

"表格抽取"的验收要"对齐行数",否则"少几行"没人发现:

// 三步验收(每份文档)
1 行数:抽取出的行数 = PDF 里的行数(人工数一遍)
2 关键列:金额/账期等"必须有值",不能为 null
3 交叉校验:明细合计 = 表里的合计行
// 判据:三项都过,才算"表格抽取可用"

5. "切分粒度"与"表格"的冲突

粒度 问题
按字数(400 字) 切断表格
按段落 表格是"一个块"(太大)
按"行 + 表头" 最好(每行都带表头)

第三项是"RAG 里最实用"的:"每一行 + 表头"作为一条 chunk第三项的"收益"是"检索命中即自解释":用户搜"账期",命中的那条"自带表名与列名"——于是"模型不必再猜'这个 30 日是什么'"。
——于是"付款方式:电汇"是一条"自解释"的记录。

// 一个"表格行 chunk"的样子(自解释)
表:付款条款
| 项目 | 内容 |
| 付款方式 | 电汇 |
| 账期 | 验收后 30 日 |
// 切分结果:每行一条,且"带上表头与表名"

"扫描件"要"先判有没有文字层",否则"OCR 与提取"会互相打架:

// 三步判"是电子版还是扫描版"
1 用 pdftotext 抽一页:有内容 → 电子版
2 只有零星字符 → 混合(部分页是图)
3 完全空 → 扫描版(走 OCR)
// 判据:混合文档要"按页决定"用哪条路径

6. 排错:五种"PDF 表格问题"

现象 成因 处理方向
行列错配 文字流顺序 版面解析
金额对不上 合并单元格丢失 保留合并信息
后半张表丢失 跨页断裂 坐标拼接
抽取"少了条款" 切分切断 按行 + 表头
扫描件乱码 OCR 质量 提高清晰度/换引擎

7. 边界:什么情况"文字流"就够

场景 为什么 做法
纯文字报告(无表格) 无行列语义 直接切
只做"全文检索"(不需精确抽取) 错位影响可控 直接切
表格很小(2×2) 拉平也能读懂 直接切
已有"结构化导出"(如系统导出的 Excel) 不必从 PDF 取 换数据源

最后一条与"数据源"有关的边界:"能从系统拿结构化数据,就不要从 PDF 抠"——PDF 是"展示格式",不是"数据格式";而"用 PDF 当唯一数据源"往往是"历史遗留",不是"设计选择"。
判断口径一句话:"表格里的'列'会不会改变语义"。会(金额/账期/责任方),就必须"保住行列";不会("备注"这类自由文本),文字流也够。

补一条"成本"现实:版面感知解析器"慢",OCR 更慢——一份 100 页的合同,跑一遍可能要几分钟。所以"解析"应当是"离线批处理 + 缓存"(按文件哈希缓存解析结果),而不是"每次查询都解析"。这条在"用户等着答案"的 RAG 里尤其关键:把"分钟级"的活放到"入库时",而不是"提问时"。

而"缓存键"要用"文件哈希 + 解析器版本":换了版本要重解析,否则"新旧结果混用",而"表格错位"会以"偶发"的形式出现。

最后一个"判断"经验:"PDF 里的表格能不能被结构化"取决于"这张表是不是"由系统生成的"——由系统生成的(对账单、发票、报表)通常"列边界清晰、可稳定解析";而"人画的表"(合并单元格、跨行、手写批注)几乎总要人工兜底。所以"自动化程度"要按"表格来源"分档,而不是"一刀切"。

8. 自测

  1. 三种 PDF 内容的解析难度分别是什么?为什么"电子版"看起来简单却容易错?
  2. "表格被拉平"的三种表现里,哪一种最致命?为什么?
  3. "先解析成 HTML/CSV 再切"为什么最稳?它把切分放到了哪一步之后?
  4. "跨页表格"的三种处理里,哪一种最通用?
  5. "每一行 + 表头"作为 chunk 解决了什么?
  6. 判断"文字流是否够用"的那个问题是什么?
S20-A24 · PDF 里的表格:切分器为什么把"一行"拆成"三段"
https://www.clxhxhhr.top/posts/6666/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。