1. 为什么你的文档解析总在“排版”上翻车做过文档智能处理的人大概率都遇到过这种场景一份结构清晰的合同、一份排版规整的技术白皮书丢进解析工具之后出来的结果惨不忍睹。标题和正文黏在一起表格被拆成了散落的文字碎片页眉页脚混进了正文段落多栏排版的文档直接读成了“左右横跳”的乱序文本。你以为是模型能力不行换了个更大的模型结果还是一样。问题出在哪里出在我们对“文档解析”这件事的默认假设上。大多数人下意识觉得一份文档就是“一串文字”解析就是把文字提取出来。但实际上一份排版精美的文档它的信息密度远不止文字本身。字体大小、加粗、缩进、行间距、分栏、表格线、页边距这些视觉排版信号承载了大量语义信息。标题之所以是标题不只是因为它写了“第一章”而是因为它字号更大、加粗、居中、上下有留白。这些排版特征才是文档结构的真正骨架。我拿一份典型的多栏学术文档做过测试。直接提取纯文本得到的是一段完全无法阅读的乱码式内容因为多栏排版在纯文本层面会把左栏和右栏的内容交替输出。但如果先做版面分析识别出分栏边界再按栏内阅读顺序重组输出的文本就完全可读了。这个差异不是模型大小能弥补的而是处理流程设计的问题。所以标题里说的“AI 读不懂 Word”本质上不是 AI 的理解能力不够而是我们喂给它的东西本身就是错的。我们把一份富含版面信息的文档压扁成了一串丢失了空间关系的字符流然后指望模型从这串字符里还原出原始结构。这就像把一栋房子的砖拆下来堆成一堆然后问为什么看不出房间布局。这篇文章想聊的就是这件事文档解析中版面分析和结构还原到底该怎么做为什么很多方案在排版上栽跟头以及一套可落地的处理思路应该长什么样。不管你是做 RAG 文档入库、合同信息抽取、还是报表自动化处理这套逻辑都用得上。2. 文档解析的核心矛盾文字流与版面流的分离2.1 纯文本提取为什么必然丢失结构绝大多数文档格式无论是 Word 的 docx、PDF 还是扫描件它们的底层存储逻辑和人类阅读逻辑是两套东西。以 PDF 为例它本质上是一堆绘制指令的集合在坐标 (x1, y1) 画一个字符在 (x2, y2) 画一条线在某个区域填充一个矩形色块。PDF 里没有“段落”这个概念也没有“标题”这个概念有的只是字符和图形在页面上的绝对位置。当你用常规的文本提取工具去读 PDF 时工具做的事情是按字符的坐标顺序从上到下、从左到右把字符拼成字符串。这个过程中字符之间的空间关系被丢弃了。两个字符之间隔了 2 个像素还是 200 个像素在纯文本输出里都变成了“有没有空格”这一个二值判断。而版面结构恰恰藏在这些空间关系里。Word 的 docx 稍微好一点它底层是 XML有段落标签和样式标签。但问题在于很多 docx 文档的样式是“假样式”——作者手动调了字号和加粗但没有用 Heading 1、Heading 2 这些语义样式。这种情况下XML 里记录的只是“这段文字是 16pt 加粗”而不是“这是一个二级标题”。解析工具如果只看标签不看视觉特征照样会把标题当正文处理。2.2 版面分析到底在分析什么版面分析的核心任务是把页面上的视觉元素按照语义角色进行分类和分组。具体来说它要回答几个问题页面上哪些区域是正文哪些是标题哪些是表格哪些是图片哪些是页眉页脚哪些是脚注。这些区域之间的层级关系是什么阅读顺序应该怎么排。一个完整的版面分析流程通常包含这几个步骤。首先是页面元素检测把页面上的文字块、图片块、表格块、线条等基础元素识别出来。然后是区域分类判断每个文字块的角色是标题、正文、图注还是页脚。接着是阅读顺序推断确定这些块应该按什么顺序阅读尤其是多栏排版和图文混排的情况。最后是结构重组把分类后的块按照层级关系组织成树状结构还原出文档的逻辑骨架。这里面最容易被低估的是阅读顺序推断。我见过不少方案在单栏文档上表现很好一遇到双栏排版就彻底崩掉。原因在于双栏文档的阅读顺序不是简单的从上到下而是先读完左栏再读右栏。如果按 y 坐标排序会把左栏第一段和右栏第一段交替输出读起来完全不通。正确的做法是先检测出分栏边界然后在每个栏内独立排序最后按栏的顺序拼接。2.3 排版信号如何转化为语义信号版面分析的价值在于它把视觉排版信号翻译成了语义结构信号。字号大、加粗、居中的文字块大概率是标题字号小、位于页面底部、有分隔线的文字块大概率是脚注有规则网格线、单元格对齐的文字块大概率是表格。这些映射关系不是绝对的但统计上非常可靠。我做过一个简单的实验拿 200 份不同来源的文档只用三个特征——字号、是否加粗、上下留白——来分类标题和正文准确率能到 85% 以上。如果把位置信息是否居中、是否在页面顶部也加进去准确率能到 92%。这说明排版信号本身就是极强的语义线索只是大多数解析流程把它们丢掉了。实操心得在做版面分析之前先花十分钟看看你的文档样本。如果文档来源单一、排版规范规则驱动的方案就够用。如果来源多样、排版混乱才需要考虑模型驱动的方案。不要一上来就上大模型很多时候规则方案更快更稳。3. 从页面到结构一套可落地的解析流程3.1 文档预处理与格式归一化不管原始文档是什么格式第一步都是把它转成统一的中间表示。对于 PDF我通常用 PDF 解析库把每一页转成图像加文字块坐标的混合表示。文字块坐标是关键它记录了每个字符或每个文字块在页面上的位置和大小。对于 Word 文档可以用文档处理库读取 XML提取每个段落的样式信息和文本内容。对于扫描件需要先做 OCR得到文字和对应的边界框。这一步的产出应该是一个结构化的页面对象包含页面尺寸、文字块列表每个块有文本、坐标、字号、字体、是否加粗、图片列表、表格候选区域等。这个中间表示是后续所有处理的基础它的质量直接决定了最终解析效果。格式归一化的时候有个坑要注意不同工具输出的坐标系统可能不一样。有的以左上角为原点有的以左下角为原点有的单位是像素有的单位是点。如果不做统一后续的版面分析逻辑会完全错乱。我的做法是统一转成左上角原点、单位为点的坐标系这样和大多数文档处理库的默认输出一致。3.2 版面区域检测与分类有了页面对象之后下一步是区域检测。规则方案的做法是基于文字块的坐标和样式做聚类。比如把 y 坐标接近、字号相同的文字块聚成一个段落把字号明显大于正文、且上下留白较大的块标记为标题候选把有规则网格线或单元格对齐特征的区域标记为表格候选。模型方案的做法是用版面分析模型输入页面图像输出每个区域的类别和边界框。这类模型通常基于目标检测或语义分割架构能识别标题、正文、表格、图片、页眉页脚等类别。模型方案的优势是泛化能力强能处理规则方案搞不定的复杂版面劣势是需要标注数据、推理速度慢、对扫描质量敏感。我的建议是混合使用先用规则方案做快速预分类把明显是正文和明显是页眉页脚的块分出来对规则方案不确定的区域再用模型做精细分类。这样既能保证速度又能保证准确率。3.3 阅读顺序推断与结构重组区域分类完成之后需要确定阅读顺序。对于单栏文档按 y 坐标从上到下、x 坐标从左到右排序即可。对于多栏文档需要先检测分栏边界。检测方法可以是基于文字块 x 坐标的分布做聚类找到栏与栏之间的空白间隙也可以基于页面投影分析找到垂直方向上的空白带。阅读顺序确定之后就是结构重组。把标题块和它下面的正文块关联起来形成层级结构。把表格块单独提取出来做表格结构识别。把图片块和它的图注关联起来。最终输出应该是一棵文档树每个节点有类型标题、正文、表格、图片、内容、层级关系。这里有个细节容易被忽略跨页结构。一份文档的章节可能跨页表格可能跨页列表可能跨页。如果按页独立处理跨页结构就会断裂。我的做法是在页面级处理完成之后加一个跨页合并步骤根据块的类型和内容连续性判断是否需要合并。# 简化的阅读顺序推断示例 def infer_reading_order(blocks, page_width): # 检测分栏基于 x 坐标分布找空白带 x_coords sorted([b.x for b in blocks]) gaps [(x_coords[i1] - x_coords[i], x_coords[i]) for i in range(len(x_coords)-1)] # 找到最大的间隙作为分栏边界 max_gap, split_x max(gaps, keylambda g: g[0]) if max_gap page_width * 0.1: # 间隙足够大认为是双栏 left_blocks [b for b in blocks if b.x split_x] right_blocks [b for b in blocks if b.x split_x] left_sorted sorted(left_blocks, keylambda b: (b.y, b.x)) right_sorted sorted(right_blocks, keylambda b: (b.y, b.x)) return left_sorted right_sorted else: return sorted(blocks, keylambda b: (b.y, b.x))3.4 表格与图片的特殊处理表格是文档解析里最棘手的部分之一。表格的难点在于它既有版面结构行列网格又有语义结构表头、数据行、合并单元格。纯文本提取会把表格拆成散落的文字丢失行列关系。正确的做法是先检测表格区域然后用表格结构识别模型或规则方法还原行列网格再把每个单元格的文字填进去。图片的处理相对简单主要是检测图片区域、提取图片、关联图注。但有个细节有些文档里的“图片”其实是矢量图形或公式这些需要特殊处理。公式识别是另一个专门的领域这里不展开。注意表格跨页是常见情况处理时不要假设表格一定在单页内。跨页表格需要先合并再识别结构否则会得到两个残缺的表格。4. 实操中踩过的坑与排查技巧4.1 常见问题速查表问题现象可能原因排查方法解决方案标题和正文黏连未做版面分析纯文本提取检查输出是否有换行和分段加入字号和留白特征做标题检测多栏文档阅读顺序错乱按 y 坐标全局排序检查输出是否左右栏交替先检测分栏边界栏内独立排序表格内容散落未做表格结构识别检查表格区域输出加入表格检测和单元格还原页眉页脚混入正文未做区域分类检查首尾段落基于位置和字号过滤页眉页脚跨页结构断裂按页独立处理检查章节和表格是否完整加入跨页合并逻辑扫描件识别率低OCR 质量差或版面倾斜检查 OCR 置信度和页面角度先做图像预处理和倾斜校正4.2 版面分析的三个反直觉经验第一个经验不是所有文档都需要版面分析。如果你的文档来源单一、排版规范、都是单栏纯文字那纯文本提取加简单的段落合并就够了。版面分析会增加处理时间和复杂度没必要为了“完整”而过度设计。我一般会先抽样看文档如果 80% 以上的文档是简单排版就先不做版面分析只对复杂文档走完整流程。第二个经验字号阈值不要写死。不同文档的正文字号可能不一样有的 10pt有的 12pt。如果写死“大于 14pt 是标题”换个文档就失效了。我的做法是动态计算先统计文档中所有文字块的字号分布取出现频率最高的字号作为正文字号比它大 2pt 以上的作为标题候选。这样能自适应不同文档。第三个经验阅读顺序推断比区域分类更容易出错。区域分类错了顶多是把一个正文块标成了标题影响有限。但阅读顺序错了整篇文档的逻辑就乱了后续所有处理都会受影响。所以在阅读顺序推断上要多花精力多做验证。4.3 模型方案与规则方案的取舍模型方案和规则方案不是对立的而是互补的。规则方案快、可解释、不需要训练数据但对复杂版面泛化能力差。模型方案泛化能力强但慢、需要标注数据、对图像质量敏感。我的实际做法是分层处理第一层用规则做快速筛选把明显简单的页面直接处理掉第二层用模型处理规则搞不定的页面第三层用规则做后处理修正模型输出的明显错误。这样能在速度和准确率之间取得平衡。具体来说如果一个页面的文字块数量少、字号分布集中、没有复杂图形就判定为简单页面走规则流程。如果文字块数量多、字号分布分散、有表格或图片就判定为复杂页面走模型流程。这个判定逻辑本身也是规则但它是元规则用来调度不同方案。4.4 评估与迭代文档解析的效果评估不能只看“提取了多少文字”要看结构还原的准确率。我通常用这几个指标标题识别准确率、阅读顺序正确率、表格结构还原准确率、跨页合并正确率。评估方法可以是人工抽检也可以是构造带标注的测试集。迭代的时候不要一次性改太多东西。每次只改一个环节观察指标变化。比如这周只优化标题检测下周只优化表格识别。这样能清楚知道每个改动的效果避免多个改动相互干扰导致无法归因。实操心得建一个“错题本”把每次解析失败的文档存下来标注失败原因。每隔一段时间回顾一次看看哪些问题是高频的优先解决。这比盲目优化所有环节效率高得多。5. 不同场景下的方案选型建议5.1 合同与法律文档解析合同文档的特点是结构规范、条款层级深、表格多、有大量编号。解析的重点是准确还原条款层级和表格内容。我的建议是先用版面分析识别标题和正文再用编号模式如“第一条”“1.1”“一”辅助层级判断。表格部分要特别处理因为合同里的表格往往包含关键信息如金额、日期、责任条款。合同文档还有一个特殊点修订痕迹。很多合同有修订模式删除线和插入标记会干扰解析。处理时要么先接受所有修订要么单独识别修订标记并做特殊处理。我一般建议先接受修订再解析这样结构更干净。5.2 学术论文与报告解析学术论文的典型结构是标题、摘要、章节、图表、参考文献。解析难点在于多栏排版、公式、图表混排。我的建议是分栏检测必须做否则阅读顺序全乱。公式区域单独标记不要试图解析公式内容只保留位置和编号。图表和正文的关联要处理好图注通常在图的下方表注通常在表的上方。参考文献部分通常有固定的格式可以用正则辅助解析。但要注意不同期刊的参考文献格式不一样不要写死一种格式。5.3 报表与财务文档解析报表文档的核心是表格而且往往是复杂表格合并单元格、嵌套表头、多级分类。解析这类文档表格结构识别是重中之重。我的建议是先用表格检测模型定位表格区域再用单元格分割方法还原网格最后用表头识别方法确定表头层级。财务文档还有个特点数字多、格式统一。可以利用这个特点做校验比如检查每列数字的格式是否一致如果不一致可能是解析错了。这种交叉校验能发现很多隐藏问题。5.4 扫描件与图像文档解析扫描件的解析流程和电子文档不一样多了图像预处理和 OCR 两个环节。图像预处理包括去噪、二值化、倾斜校正、边界裁剪。OCR 的质量直接决定了后续所有环节的效果。我的建议是如果扫描质量差先花时间做图像增强不要指望 OCR 能自动处理一切。OCR 输出的文字块通常带有置信度低置信度的块要特别关注。我的做法是把置信度低于阈值的块标记出来人工复核或走二次识别。另外OCR 对表格和公式的处理通常不好这两类区域最好单独处理。6. 工具链与代码实现要点6.1 常用工具与库的选型文档解析的工具链可以分几个层次。底层是文档读取库PDF 可以用 PDF 解析库读取文字和坐标Word 可以用文档处理库读取 XML。中间层是版面分析规则方案可以用坐标聚类和样式分析模型方案可以用版面分析模型。上层是结构重组这部分通常需要自己写逻辑因为不同文档的结构差异太大没有通用工具。OCR 方面开源方案和商业方案都有。开源方案灵活但需要调优商业方案省事但有成本。我的建议是先用开源方案跑通流程如果效果不达标再考虑商业方案。6.2 关键代码片段# 基于字号分布的标题检测 from collections import Counter def detect_headings(blocks): # 统计字号分布 font_sizes [b.font_size for b in blocks if b.text.strip()] size_counter Counter(font_sizes) body_size size_counter.most_common(1)[0][0] headings [] for block in blocks: # 字号大于正文 2pt 以上且文本较短判定为标题 if block.font_size body_size 2 and len(block.text) 50: headings.append(block) return headings # 页眉页脚过滤 def filter_header_footer(blocks, page_height): filtered [] for block in blocks: # 位于页面顶部 5% 或底部 5% 区域且字号较小 if block.y page_height * 0.05 or block.y page_height * 0.95: if block.font_size 10: continue # 跳过页眉页脚 filtered.append(block) return filtered6.3 性能优化建议文档解析通常是批量处理性能很重要。几个优化点第一并行处理不同页面可以并行解析。第二缓存中间结果版面分析的结果可以缓存避免重复计算。第三按需处理简单页面走快速流程复杂页面走完整流程。第四模型推理可以用批处理把多个页面的图像拼成一个批次一起推理提高 GPU 利用率。注意并行处理时要注意内存占用尤其是处理大文档时。我的做法是限制并行度根据可用内存动态调整。7. 写在最后的一些个人体会做文档解析这几年最大的体会是不要和排版对抗要利用排版。排版不是噪音是信号。一份文档之所以排版成那样是因为作者想表达某种结构。标题大字号是为了突出层级表格网格是为了表达行列关系分栏是为了节省空间。这些排版决策背后都有语义意图解析的任务就是把这些意图还原出来。另一个体会是没有万能方案。不同来源、不同格式、不同用途的文档最优解析方案是不一样的。与其追求一个通用方案不如针对自己的文档特点做定制。先抽样分析文档特征再设计针对性的流程最后用评估指标验证效果。这个循环比套用现成方案有效得多。最后说个小事。我见过很多人把文档解析当成一个“预处理”步骤随便搞搞就丢给下游模型。但实际上文档解析的质量直接决定了下游任务的上限。如果解析出来的文本结构是乱的再强的模型也救不回来。所以在文档解析上多花时间是值得的。