PDF 里明明只有两列价格,导出后总金额还对,单价却落进了“数量”列。把 CSV 交给 AI 润色,错误可能变得更像一张正式报表。用 Docling 提取表格时,值得建立的是一份能回看原页的核对材料,而不是直接接入商品库或财务系统。
下面从 Windows 本地练习开始,只处理一份两页 PDF,输出 Markdown、结构 JSON 和逐表 CSV。适合要批量处理报价单、产品目录的运营,也适合为知识库准备数据的开发者。本文核对了官方接口,并对自写脚本做了 14 项内存替身检查;没有安装 Docling、运行 OCR 或转换真实 PDF,不能据此声称识别准确率。
练习文件要能看出错列,不能只有一排数字
在自己的文档软件里做一份无敏感信息的两页练习材料并导出 PDF,命名为 sample.pdf。第一页放表格,列为 SKU、品名、数量、单价、金额:AX001、Cup、3、19.90、59.70;AX002、Plate、2、45.00、90.00,合计 149.70。第二页写“币种 CNY,价格含税,编号保留原样”。这些是人为设定的核对基准,不是本站提取出的结果。
再给表格加一个跨列标题“示例报价”。它能帮助暴露合并单元格在导出后的变化。不要把原件唯一副本当输入,也不要拿未获授权的客户报价测试。保留 PDF 页码,纸面脚注的币种和税务口径即使不在表格里,也不能丢。
选用 Docling,是为了保留检测到的文档结构并逐表导出,而不是让聊天模型猜表格。代码开源、本地处理不按页向厂商买额度,但计算、存储和模型下载仍有成本;模型本身的授权另查。只处理偶尔一页时,人工复制再核对也可能更省事。
Windows 环境放在独立文件夹,不改系统 Python
准备 Python 3.10 或更新的兼容版本,在一个新建的练习文件夹打开 PowerShell。这里固定 Docling 2.145.0,避免未来默认安装的新版本改变接口;这不是保证任意 Python、显卡或驱动组合都能安装。先按官方安装文档核对自己的环境。
python -m venv .venv
& .\.venv\Scripts\python.exe -m pip install "docling==2.145.0" pandas
直接调用虚拟环境里的解释器,不需要修改 PowerShell 执行策略或全局依赖。安装报错就先记录 Python 版本和完整错误,不要跟着 AI 使用管理员权限重装系统包。公司电脑应遵守软件安装政策。
模型可能在首次运行时联网下载;“本地执行”不等于首次启动就完全离线。隔离网络环境应先按官方离线说明准备依赖和权重。本文不启用远程服务或第三方插件,也不要求输入云端 API 密钥;另加远程模型、插件或 URL 输入后,要重新确认数据会去哪里。
一次导出三种材料,已有目录就停止
把下面内容保存为 export_review.py,与 sample.pdf 放在一起。它限制到第 1—2 页;没有检测到表格就报错,输出目录已存在也不覆盖。它只是初步提取,不负责把每一张表解释成业务字段。
import json
from pathlib import Path
from docling.document_converter import DocumentConverter
def export_review(source=Path("sample.pdf"), out=Path("export-review")):
if not source.is_file() or source.suffix.lower() != ".pdf":
raise ValueError("Provide a local sample.pdf")
if out.exists():
raise FileExistsError("Output already exists; choose a new directory")
result = DocumentConverter().convert(source, page_range=(1, 2))
doc = result.document
if not doc.tables:
raise RuntimeError("No tables detected; inspect the source pages")
out.mkdir()
(out / "document.json").write_text(
json.dumps(doc.export_to_dict(), ensure_ascii=False, indent=2),
encoding="utf-8",
)
(out / "preview.md").write_text(
doc.export_to_markdown(), encoding="utf-8"
)
for number, table in enumerate(doc.tables, start=1):
table.export_to_dataframe(doc=doc).to_csv(
out / f"table-{number:02d}.csv",
index=False,
encoding="utf-8-sig",
)
print(f"Exported {len(doc.tables)} detected tables. Review required.")
if __name__ == "__main__":
export_review()
在同一文件夹执行 & .\.venv\Scripts\python.exe .\export_review.py。如果成功,export-review 中应有 preview.md、document.json 和按检测顺序编号的 CSV。编号不是 PDF 页码,也不是业务表名;真实检测表数可能与肉眼看到的不同。脚本没有承诺只有一张表,更不会自动把跨页表拼成一张。
14 项检查只验证语法、控制流、文件命名、导出参数和拒绝覆盖等路径,使用的是内存替身。真实包导入、模型下载、表格检测与文件转换仍需在你的环境验证。看到“Exported”也只表示导出流程走完,不表示数据通过验收。
Markdown 空了一格,先回到结构和原页
Markdown 表格没有合并单元格的表达能力。Docling 的序列化说明指出,跨行跨列信息在 Markdown 中会被展平,被覆盖位置可能显示为空;JSON 或 HTML 更适合查看检测到的合并结构。因此,空白不一定是漏识别,也不能反过来假定每个空格都继承上一行。
打开原 PDF,对照 CSV 查 AX001 和 AX002 各自的品名、数量、单价、金额,再看币种及含税脚注。核算 3×19.90=59.70、2×45.00=90.00、合计149.70。合计匹配仍不能证明列都对:错列、重复行或缺行有时也会碰巧抵消。
JSON 保留的是 Docling 已检测到的结构,不是对原始页面的真实性担保。OCR 可能把 O 和 0、负号和短横线识别错,编号前导零也可能在后续表格软件里被改掉。CSV 使用 UTF-8 BOM 是为了兼顾常见软件读中文,不能修复这些语义错误。
交给 AI 的是一份对照任务,不是自动补表许可
只摘取有权分享的几行,并附上人工核对的原页字段。可以让你已有的 Claude 等工具列差异;账号额度和上传政策按所用服务核实,不要求购买新套餐,也不要把整份商业文件当成默认输入。
你只核对给定信息,不联网,不补字段。
人工原页记录:AX001,Cup,数量3,单价19.90,金额59.70;
AX002,Plate,数量2,单价45.00,金额90.00;
合计149.70,币种CNY,含税。
下面另附提取结果。
按SKU比较品名、数量、单价、金额及缺行、重复行。
输出:原页值、提取值、匹配或冲突、需要人工检查的位置。
空白、跨页或合并单元格不自行推断;缺证据写“待查”。
不得直接生成可导入生产系统的修正版。
模型指出差异后,人再回到相应原页确认。原页本身不清楚,就记待查,不让模型按常识补一个价格。需要改正的数据另存审核版本,保留原提取件及修改理由,避免第二个人看不到错误从哪里来。
没有表、目录已存在、中文不对,各有处理办法
没有检测到表格时,先确认练习的两页确实有目标表,文件不是损坏或加密状态,再查看日志与 OCR 配置。若为中文扫描件,按官方 OCR 文档选择对应引擎及语言模型;不要仅换一个提示词就宣布解决。文字可选中的 PDF 也可能有错乱的阅读顺序。
目录已存在说明防覆盖检查生效。先查看里面是否为上次成功结果或失败中间件,保留排查资料;确认后换一个新的输出目录名,而不是让 AI 自动递归删除。导出中途失败可能留下部分文件,这些不能一起标成成功。
CSV 在表格软件里显示异常时,先检查编码和导入列类型;SKU、长编号按文本处理,金额、日期和千分位分别核对。不要只靠双击打开后的显示判断原文件,也不要对所有空格一键填充、对所有相同 SKU 一键去重。
交付时附一张核对状态表
- 注明源文件版本、处理页范围、Docling 版本和处理日期。
- 保留原 PDF、结构 JSON、预览和原始 CSV,不覆盖业务原表。
- 每张表标明对应原页,记录表头、单位、合并单元格、缺行和重复行的检查结果。
- 把已核对、冲突、待查分开;只有明确审核通过的数据才进入下一步。
- 确认接收人知道这些是提取结果,不是已获业务批准的入库文件。
可进一步查阅官方逐表导出示例和序列化说明。当一张表无法解释清楚时,停在待查比交出一份“看起来完整”的 CSV 更可靠。