在文档解析领域,嵌套表格(一个单元格里套着另一个完整的表格)是绝对的“劝退”场景。传统的 OCR 方案(比如 Tesseract 或 简单的 PaddleOCR 流程)通常会把它们压扁成一堆乱序的文本,因为它们只看“行”和“列”,看不懂“层级”。
Docling 之所以能把嵌套表格完美拆解成 Markdown 或 HTML,是因为它采用了一套 “自顶向下检测 + 自底向上重构” 的混合机制。
1. 核心架构:不仅仅是 Object Detection
Docling 的 Pipeline 不是线性的,它是一个多层叠加的过程。处理复杂页面时,它主要依赖两个核心组件的协作:
- DocLayNet 模型(视觉层):负责“看”出哪里是表格。
- TableFormer / TableMaster(结构层):负责“理解”表格内部的逻辑。
但针对嵌套表格,Docling 引入了一个关键逻辑:层级感知的区域建议(Hierarchical Region Proposal)。
当一张页面进入 Docling 时:
- 宏观检测:首先,基于 ResNet/Mask R-CNN 的布局模型会扫视全图,框出“最外层”的表格边界。
- 递归扫描:如果模型在某个单元格(Cell)内部检测到了高密度的网格线或对齐的文本块,它会触发递归(Recursion),将该单元格视为一个新的“子页面”,再次运行布局分析。
2. 难点攻克:Cell 内的“微观世界”
嵌套表格最难的地方在于坐标对齐。外层表格的边框和内层表格的边框往往靠得非常近,单纯靠 IoU(交并比)很容易误判。
Docling 在底层做了一个非常工程化的处理:逻辑坐标与物理坐标的解耦。
它不直接生成最终的 HTML,而是先生成一个中间表示(Intermediate Representation, IR)。这个 IR 是一棵树(Tree),而不是一张网(Grid)。
- Root Table
- Row 1
- Row 2
- Cell A
- Cell B (Container) -> Child Table
- Row i
- Row ii
这种树状结构保证了,即使内层表格把布局撑坏了,外层表格的逻辑结构依然是闭合的。
3. 代码实战:手动干预表格解析流程
虽然 DocumentConverter 封装得很好,但作为技术人员,我们需要知道怎么通过代码去“看见”这个嵌套结构,甚至在解析出错时进行干预。
Docling 暴露了 TableStructure 对象,我们可以遍历它来看看底层的树状逻辑。
以下代码展示了如何加载一个 PDF,并专门提取出表格的层级结构数据,而不是仅仅导出 CSV。
Python
from docling.document_converter import DocumentConverter
from docling.datamodel.base_models import InputFormat
from docling.datamodel.pipeline_options import PdfPipelineOptions, TableStructureOptions
def inspect_nested_tables(pdf_path):
# 1. 配置 Pipeline,开启增强的表格结构识别
pipeline_options = PdfPipelineOptions()
pipeline_options.do_table_structure = True
# 针对复杂嵌套表格,提高 OCR 和 结构分析的精细度
pipeline_options.table_structure_options = TableStructureOptions(
do_cell_matching=True, # 强制匹配单元格内容
mode="accurate" # 使用更耗时但更准的模型
)
converter = DocumentConverter(
format_options={
InputFormat.PDF: PdfPipelineOptions(pipeline_options=pipeline_options)
}
)
print(f"正在解析嵌套结构: {pdf_path}...")
doc = converter.convert(pdf_path).document
# 2. 遍历检测到的表格
for i, table in enumerate(doc.tables):
print(f"\n=== Table #{i+1} ===")
print(f"表格物理坐标 (BBox): {table.prov[0].bbox}")
# 3. 深入单元格 (Grid) 层面
# Docling 将表格解析为 DataGrid 对象
grid = table.data.grid
# 遍历每一行
for row_idx, row in enumerate(grid):
for col_idx, cell in enumerate(row):
# cell.text 是单元格内的纯文本
# 但我们需要检查这个 cell 是否包含了子结构
content = cell.text.strip()
if not content:
continue
# 在底层,如果单元格内有复杂的子布局,
# Docling 会在 structure item 里标记
# 这里我们简单打印出单元格的跨度,这是判断嵌套的重要依据
# 嵌套表格通常会导致外层单元格出现巨大的 rowspan/colspan 或者奇怪的 bbox 比例
if cell.row_span > 1 or cell.col_span > 1:
print(f" [Row {row_idx}, Col {col_idx}] 复合单元格: '{content[:20]}...' (Span: {cell.row_span}x{cell.col_span})")
# 如果要完美复现嵌套,建议导出为 HTML 而不是 Markdown
# 因为 Markdown 原生不支持嵌套表格
# 4. 导出为 HTML 以观察 DOM 树结构
html_output = doc.export_to_html()
with open("nested_debug.html", "w", encoding="utf-8") as f:
f.write(html_output)
print("\n已导出 nested_debug.html,请用浏览器打开查看 DOM 树的 <table> 嵌套情况。")
if __name__ == "__main__":
# 找一个带有“表格里套表格”的 PDF 跑一下
inspect_nested_tables("complex_layout.pdf")
4. 为什么混合模型比纯 CV 模型强?
你可能会问,为什么不直接用 GPT-4V 或者 LLaVA 这种多模态大模型直接“看”图写 HTML?
在工程落地上,纯 CV 大模型有两个致命弱点:
- 幻觉:它可能把表格里的数字
100.00看成1000.00,这在财务场景是灾难。 - 对齐丢失:对于没有边框的隐形表格(Implicit Tables),纯视觉模型很难对齐列。
Docling 的混合模型厉害在:它用 CV 模型定“骨架”,用 OCR 文字坐标做“校准”。
当处理嵌套表格时,视觉模型给出一个大概的子表格区域,然后算法会拿 OCR 抓到的具体文字坐标(Bounding Box)去反向修正这个区域的网格线。这种Pixel-level Alignment(像素级对齐) 是保证嵌套表格数据不串行的关键。
总结
Docling 处理嵌套表格的秘诀不在于一个超级厉害的端到端模型,而在于递归的版面分析策略和严格的坐标树重构。
作为开发者,如果你在做 PDF 解析,遇到复杂的嵌套表格搞不定,不要试图用 Regex 去修补文本,直接切换到 Docling 的 HTML 导出模式,去解析它的 DOM 树,那才是最稳健的路径。