AI 读 Word 表格总读错?先查合并单元格和表头这 3 处
上周帮朋友处理一份 18 页的项目验收文档,里面有 11 张表。我让模型把「设备清单」抽成 JSON,字段名都对,行数总少两行。把同一页截图发过去,它又能读对。
问题不在模型,在我喂进去的文本:表格一转换,合并单元格把列号顶歪了,后面每一行的值都跟着错位。
先确认表格是不是「矩形」
python-docx 读表格很快,但它不会告诉你这张表是不是规整的。先打印前几行,看竖线数量:
python
from docx import Document
doc = Document("验收文档.docx")
for ti, tbl in enumerate(doc.tables):
print("table", ti, "rows", len(tbl.rows), "cols", len(tbl.columns))
for row in tbl.rows[:3]:
cells = [c.text.strip().replace("\n", " ") for c in row.cells]
print(len(cells), " | ".join(cells))
如果某一行的 len(cells) 和其他行不一样,或者第一行只有一列、文字却横跨整页,基本可以判定有合并单元格。此时按位置取列,一定会错。
合并单元格会重复返回同一个单元格
Word 里横向合并(gridSpan)和纵向合并(vMerge)在 python-docx 里表现不同:横向合并后,row.cells 会把同一个单元格对象重复返回;纵向合并后,被合并的位置可能返回空文本。两种情况下,cells[3] 都不再等于「第 4 列」。
我的处理顺序是:
- 先读出
len(tbl.columns)作为目标列数; - 逐行遍历,遇到数量不足就补空字符串,遇到重复对象就跳过;
- 把纵向合并的值向下填充,直到下一个非空值出现。
python
target = len(tbl.columns)
rows = []
for row in tbl.rows:
vals, seen = [], set()
for c in row.cells:
key = id(c._tc)
if key in seen:
continue
seen.add(key)
vals.append(c.text.strip())
while len(vals) < target:
vals.append("")
rows.append(vals[:target])
这段代码不优雅,但能把大部分「列错位」挡住。真正复杂的表,比如带斜线表头、跨页续表,我会直接放弃自动转换,改成人工确认后再喂给模型。
表头不一定在第一行
很多正式文档的表格第一行是标题,横跨所有列,比如「表 3-1 设备清单」。真正的字段名在第二行。如果直接把第一行当表头,模型拿到的字段名会全部变成「表 3-1 设备清单」。
我的判断方法:从上往下找第一行「非空单元格数量等于列数」的行,把它当表头;如果前一行只有一格,就把它当表标题单独存起来。这样后面做字段映射时,不会把标题误当字段。
转成 Markdown 后,对三个数
表格转成 Markdown 之后,我会先做一次不依赖模型的复核:
- 行数:Markdown 的行数是否等于
len(tbl.rows); - 列数:每行
|分隔后的段数是否一致; - 合计:如果原表有「合计」行,把数值列加一遍,看是否等于原表合计。
三个数里有一个对不上,就不把这张表交给模型。因为一旦行列错位,模型会把错误的值编得很合理,人工反而更难发现。
提交前我会看的 5 个点
- 表格是否为矩形,有没有某一行少列;
- 表头行是否唯一,表标题有没有被当成字段名;
- 合并单元格是填平还是跳过,规则是否写进了脚本;
- 数值列的单位是否保留(元 / 万元最容易丢);
- 脚注和「续表」有没有被截断。
这套检查不复杂,但我现在每次处理带表格的文档都会先跑一遍。模型读错表格,十有八九不是理解能力问题,而是输入在转换阶段就已经变形了。