Docling 序列化体系详解:从 DoclingDocument 多格式导出到自定义 Serializer
【免费下载链接】doclingGet your documents ready for gen AI项目地址: https://gitcode.com/GitHub_Trending/do/docling
Docling(Get your documents ready for gen AI)在完成 PDF、DOCX 等文档解析后,会产出统一的结构化中间表示DoclingDocument。而如何把这份中间表示落回 Markdown、HTML、DocTags、JSON 等文本形态,则由一套独立的序列化(serialization)抽象承担。本文基于仓库文档 docs/concepts/serialization.md 及其配套示例 docs/examples/serialization.ipynb,系统讲解 Docling 序列化器的抽象层次、DoclingDocument导出方法、各格式对表格跨单元格(span)的处理差异,以及如何配置和编写自定义 serializer,帮助你在 RAG、批量转换、文档入库等场景中精确控制导出结果。
1. 序列化抽象:从文档级到组件级
Docling 把「把文档变成文本」这件事拆成了三层抽象:
- 文档序列化器(document serializer):以一个
DoclingDocument实例初始化,负责产出整篇文档的文本表示(textual representation)。这是最常用的入口; - 组件序列化器(component serializers):面向文档的子构件,例如text serializer、table serializer、picture serializer、list serializer、inline serializer等。文档级序列化器内部会按组件类型委派给对应的组件序列化器;
- 序列化器提供者(serializer provider):进一步把「序列化策略」与「文档实例」解耦的包装层,便于下游应用统一替换序列化行为。
这一分层直接决定了后文的两个能力:你可以只替换某一种组件(比如把 Markdown 输出的表格换成 triplet 形式),也可以整体替换文档级序列化器,而不必触碰转换管线本身。
2. 基类体系与 serialize() 契约
为了兼顾下游应用的灵活性与开箱即用的便利,Docling 定义了一组序列化类层次(实现位于 docling-core 依赖包中,本仓库 pyproject.toml 声明其版本约束为docling-core>=2.91.0,<3.0.0):
- 各抽象的基类:
BaseDocSerializer、BaseTextSerializer、BaseTableSerializer等组件基类,以及BaseSerializerProvider; - 上述基类之外的具体实现子类,例如
MarkdownDocSerializer、HTMLDocSerializer。
从客户端视角看,最核心的契约是BaseDocSerializer.serialize():它返回该文档的文本表示,同时附带哪些文档组件实际参与(贡献)了这次序列化的元数据。这一点在做导出审计或调试「某段文本为什么没出现」时非常有用——你可以通过序列化结果中的 span 来源信息定位到具体组件。
3.DoclingDocument的导出方法:序列化器的用户级快捷方式
Docling 预置了 Markdown、HTML、DocTags 等序列化器,并在DoclingDocument上以导出方法的形式直接暴露。文档说明得很明确:像export_to_markdown()这样的导出方法本质上是用户快捷方式(user shorthands),其内部就是直接实例化并委派给相应的 serializer。
在当前仓库中,可以观察到以下导出方法的真实使用分布(对docling/与tests/下 Python 代码的统计):
| 导出方法 | 使用频次(仓库内) | 典型场景 |
|---|---|---|
export_to_markdown() | 138 次 | 绝大多数示例、CLI、测试的默认输出 |
export_to_indented_text() | 16 次 | 纯文本/缩进结构输出(.itxt 地面真值校验) |
export_to_dict() | 9 次 | 稳定 schema 的结构化导出 |
export_to_html() | 4 次 | 保留表格 rowspan/colspan 等结构 |
export_to_doclang()/export_to_doctags() | 各 2 / 1 次 | Doclang 文本、DocTags 训练格式 |
例如 docs/examples/batch_convert.py 展示了批量转换时按目标格式分发导出:YAML 落地export_to_dict()结果、.doctags文件用export_to_doctags()、Markdown 则同时演示了export_to_markdown()与export_to_markdown(strict_text=True)两种形态;docs/examples/custom_convert.py 也以同样方式把 JSON、Markdown、DocTags 三种导出写入独立文件。而 docling/datamodel/document.py 在需要「保证稳定 schema」的场景下显式使用export_to_dict()做文档序列化,注释写明了其动机——这正是选择 dict 导出而非文本导出的一个代表性理由。
DoclingDocument本体(及其全部export_to_*方法)定义在 docling-core 包中,本仓库通过 docling/datamodel/document.py 重新导出DoclingDocument等类型,方便用户统一从docling.datamodel.document引用。
4. 格式特定行为:表格单元格跨行跨列(span)如何处理
这是文档中最重要的「格式权衡」章节。Docling 的内部表格模型(TableData.grid)为每个单元格保留了完整的 span 元数据:row_span、col_span、start_row_offset_idx、start_col_offset_idx。不同输出格式对这些元数据的渲染策略如下(完整继承自原文档):
| 格式 | Span 处理 |
|---|---|
| JSON | 保留。完整的TableData模型无损序列化,包含全部 span 字段。 |
| Doclang | 保留。表格通过 OTSL 序列化,使用显式续格 token:跨列用LCEL、跨行用UCEL、两者兼有则XCEL。 |
| DocTags | 保留。表格经 OTSL 序列化,OTSL 天然编码 span 结构。 |
| HTML | 保留。单元格直接输出原生rowspan/colspan属性。 |
| Markdown | 拍平(Flattened)。Markdown 表格没有 span 语法,序列化器只在原点位置写入单元格文本,span 覆盖的其他网格位置渲染为空单元格。 |
| LaTeX | 拍平。tabular环境暂不输出\multirow/\multicolumn命令。 |
| WebVTT | 不适用。WebVTT 是字幕/说明格式,表格不会被序列化。 |
实践结论:如果下游流程依赖准确的表格结构(例如合并的表头单元格),应优先使用export_to_html()或export_to_dict(),而不是export_to_markdown()。当然,如果你确实需要 Markdown 中承载跨格信息,也可以子类化BaseTableSerializer实现自定义逻辑,并在实例化文档序列化器时传入(见第 7 节的配置示例,table_serializer参数正是为这种替换设计的)。
5. 实战一:直接应用预置 Serializer
以下代码整理自 docs/examples/serialization.ipynb,演示「转换 + 序列化」的完整闭环。先转换得到DoclingDocument,再对其施加任意BaseDocSerializer:
from docling.document_converter import DocumentConverter DOC_SOURCE = "https://arxiv.org/pdf/2311.18481" converter = DocumentConverter() doc = converter.convert(source=DOC_SOURCE).documentHTML 序列化——表格会以<table>结构输出(span 完整保留),图片以<figure>/<figcaption>呈现:
from docling_core.transforms.serializer.html import HTMLDocSerializer serializer = HTMLDocSerializer(doc=doc) ser_result = serializer.serialize() ser_text = ser_result.text # 序列化结果同时携带组件贡献元数据Markdown 序列化——表格转为标准管道表格(span 被拍平),图片按MarkdownParams配置输出占位符(默认<!-- image -->)或引用/嵌入形式:
from docling_core.transforms.serializer.markdown import MarkdownDocSerializer serializer = MarkdownDocSerializer(doc=doc) ser_text = serializer.serialize().text6. 实战二:配置 Serializer——替换表格序列化器与参数
同一 Notebook 演示了如何「重配置」Markdown 序列化,满足两类诉求:
- 使用不同的组件序列化器:例如把表格输出改为 triplet 形式(
行1, 列A = 值1. 行1, 列B = 值2. ...),笔记指出这种扁平键值对形式有助于向量检索场景下的表格表示; - 使用用户自定义参数:例如替换默认的图片占位符文本。
from docling_core.transforms.chunker.hierarchical_chunker import TripletTableSerializer from docling_core.transforms.serializer.markdown import MarkdownParams serializer = MarkdownDocSerializer( doc=doc, table_serializer=TripletTableSerializer(), # 替换组件级序列化器 params=MarkdownParams( image_placeholder="<!-- demo picture placeholder -->", # ... 其余 MarkdownParams 参数按需提供 ), ) ser_text = serializer.serialize().text从 Notebook 的实际输出对比可以验证两处差异:同一张 IBM/Starbucks ESG 表格,默认配置下是| Report | Question | Answer |管道表格;换成TripletTableSerializer后变为IBM 2022, Question = ...?. IBM 2022, Answer = ...的连续文本串;同时图片占位符也从默认的<!-- image -->变成了配置中的<!-- demo picture placeholder -->。这直接印证了第 1 节所说的组件级替换机制:只动一个子序列化器,文档其余部分的输出完全不受影响。
7. 实战三:编写自定义 Serializer
当现有实现都不满足时,你可以定义自定义序列化逻辑。Notebook 给出的例子是:让图片序列化额外带上 picture description(图像描述注解)。前提是转换管线开启了 picture description enrichment(通过PdfPipelineOptions(do_picture_description=True, picture_description_options=PictureDescriptionVlmOptions(...), generate_picture_images=True, images_scale=2),并配合PictureDescriptionVlmOptions指定 VLM 模型与 prompt)。
自定义组件序列化器——继承MarkdownPictureSerializer并重写serialize(),先调用父类拿到基础输出,再追加注解:
from docling_core.transforms.serializer.base import BaseDocSerializer, SerializationResult from docling_core.transforms.serializer.common import create_ser_result from docling_core.transforms.serializer.markdown import MarkdownPictureSerializer from docling_core.types.doc.document import DoclingDocument, PictureItem class AnnotationPictureSerializer(MarkdownPictureSerializer): def serialize(self, *, item, doc_serializer, doc, separator=None, **kwargs): text_parts = [] # 复用父类结果(占位符/图片引用部分) parent_res = super().serialize(item=item, doc_serializer=doc_serializer, doc=doc, **kwargs) text_parts.append(parent_res.text) # 追加 picture description 注解 if item.meta is not None and item.meta.description is not None: text_parts.append(f"<!-- Picture description: {item.meta.description.text} -->") text_res = (separator or "\n").join(text_parts) return create_ser_result(text=text_res, span_source=item)注意create_ser_result(text=..., span_source=item):它把序列出的文本与源组件(item)绑定,形成serialize()契约中「哪些组件贡献了输出」的元数据。然后把自定义图片序列化器挂到文档级序列化器上:
serializer = MarkdownDocSerializer( doc=doc, picture_serializer=AnnotationPictureSerializer(), params=MarkdownParams(image_mode=ImageRefMode.PLACEHOLDER, image_placeholder=""), ) ser_text = serializer.serialize().text运行后,Markdown 输出中每个图片占位处都会带上一条<!-- Picture description: ... -->注释,内容即管线阶段由 VLM 生成的图片描述。
Notebook 还覆盖了另一个常见诉求——为每张图片生成唯一标识以便下游与原始DoclingDocument匹配:自定义_serialize_image_part(),从item.self_ref解析出图片索引,并对image_placeholder中的{index}占位 token 做替换,配合params=MarkdownParams(image_mode=ImageRefMode.PLACEHOLDER, image_placeholder="<!-- image_{index} -->"),即可让每张图在导出文本中拥有形如<!-- image_2 -->的独立编号。
8. 小结与延伸阅读
- 序列化是 Docling 转换管线之后的独立层:
DoclingDocument的export_to_*方法只是预置 serializer 的快捷封装,需要精细控制时直接实例化MarkdownDocSerializer/HTMLDocSerializer等并传入组件级序列化器与Params; - 选择导出格式的核心判据之一是表格 span 保真度:JSON/Doclang/DocTags/HTML 保留完整结构,Markdown/LaTeX 当前会拍平;
- 组件级 serializer 可任意替换(table、picture、list……),文档级策略可整体替换,扩展点是子类化而非修改管线。
进一步阅读:docs/concepts/docling_document.md(DoclingDocument数据结构)、docs/examples/serialization.ipynb(完整可运行示例,含上述全部代码)、docs/examples/batch_convert.py 与 docs/examples/custom_convert.py(批量/自定义转换中的多格式导出落地)、docs/examples/export_figures.py 与 docs/examples/export_tables.py(图片与表格的独立导出)。
【免费下载链接】doclingGet your documents ready for gen AI项目地址: https://gitcode.com/GitHub_Trending/do/docling
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考