用 ComPDF DocSlight 提取 PDF 表格:三种样本实测与 Python 接入

采购明细、对账单里的表格要进入业务系统,关键是让编号、项目、金额和备注保持对应。得到一段文字或一个 JSON 文件,只是开始;还要回到原文检查漏行、错列和空白。

下面使用 ComPDF DocSlight 云端 SDK,演示有线框、无边框和图片 PDF 的表格解析,提供 Python 接入代码、结果核对方法和可复现的教学样例。

1. 用三份可核对的 PDF 开始

想从头复现,先运行文末的 make_samples.py,生成三份 PDF 和核对用的 expected.json;再按第 2、3 节安装 SDK 并提交文件。也可以直接使用一份可用于云端测试的 PDF。

三份文件使用同一组教学示例采购数据,包含 6 条明细:

序号 项目 金额(元) 备注
A-001 零件A 1,200.00
A-002 零件B 300.00 含运费
A-003 零件C 85.50
A-004 零件D 1,200.00 待复核
A-005 零件E 234.50
A-006 零件F 80.00

ruled.pdf 有文本层和表格线,明细分在两页;borderless.pdf 去掉表格线,6 行在一页;image_only.pdf 把有线框样本的页面转成图片,再封装为两页 PDF,没有原来的文本层。

图片版本用于检查图片内容的识别,不包含真实扫描件的倾斜、噪点、模糊或手写。金额合计为 3,100.00 元,但合计一致不能单独证明每行正确,必须同时核对行列关系。

2. 准备云端 SDK 环境

本次使用 Python 3.12.14、docslight==0.1.5,实际调用的类是 DocSlight,输出方法是 to_markdown() 和 to_json()。

以下安装方式适用于本文的 Python 3.12 云端接入:先安装云端需要的基础依赖,再安装 SDK。这样可以避免同时安装本次不使用的本地 OCR、Office 和 Web 依赖。它不是本地解析模式的完整安装方式。

复制代码
python -m pip install requests==2.34.2 pydantic==2.14.0 typing-extensions==4.16.0
python -m pip install --no-deps docslight==0.1.5

在 ComPDF 控制台 的"API → 密钥与项目"取得项目公钥 publicKey,设置当前终端的 DOCSLIGHT_API_KEY。不要将真实凭据写进公开代码、文章或截图。

macOS/Linux 终端:

复制代码
export DOCSLIGHT_API_KEY="替换为你自己的项目公钥"

Windows PowerShell:

复制代码
$env:DOCSLIGHT_API_KEY="替换为你自己的项目公钥"

云端模式会上传文件处理。先用可用于云端测试的文件,并查看账号当前额度与计费说明。

3. 提交 PDF,保留原始解析结果

将下面代码保存为 parse_with_docslight.py。它调用自家 SDK 的云端解析入口,保存 Markdown 和 SDK JSON;响应带有原始 ZIP 时也一并保留。

图 1:DocSlight 云端接入流程示意。先保留解析输出,再按原文核验。

复制代码
import argparse
import json
import os
from pathlib import Path

from docslight import DocSlight


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("pdf")
    parser.add_argument("--output-dir", default="parsed")
    args = parser.parse_args()
    api_key = os.environ.get("DOCSLIGHT_API_KEY")
    if not api_key:
        raise SystemExit("请先在当前终端设置 DOCSLIGHT_API_KEY。")

    client = DocSlight(mode="cloud", api_key=api_key, timeout=120)
    result = client.parse(Path(args.pdf))
    output = Path(args.output_dir)
    output.mkdir(parents=True, exist_ok=True)
    (output / "result.md").write_text(result.to_markdown(), encoding="utf-8")
    (output / "result.json").write_text(
        json.dumps(result.to_json(), ensure_ascii=False, indent=2), encoding="utf-8"
    )
    if result.raw_archive is not None:
        (output / "raw_result.zip").write_bytes(result.raw_archive)
    print(f"解析结果已保存到 {output}")


if __name__ == "__main__":
    main()

依次运行三份样本:

复制代码
python parse_with_docslight.py ruled.pdf --output-dir parsed/ruled
python parse_with_docslight.py borderless.pdf --output-dir parsed/borderless
python parse_with_docslight.py image_only.pdf --output-dir parsed/image_only
文件 用途
result.md 查看文字、标题及表格结构
result.json 保存 SDK 的解析结果,供程序读取
raw_result.zip 响应包含归档时保留原始输出

本次 result.md 的表格以 HTML <table> 表达,并非所有内容都使用 Markdown 竖线表格。result.json 包含 markdown、pages、metadata 等内容;页面的 parsing_res_list 中可查看 block_type、block_content 和 block_bbox。

这些是本次版本和响应中的结构。写业务映射前应先检查自己拿到的输出;不要把自行整理的业务 JSON 误认为产品原生格式。位置字段也应结合该响应的页面宽高解释,不能直接套用其他 PDF 工具的坐标单位。

4. 本次实际得到什么

图 2:ComPDF 在线演示的实际处理界面,左侧为教学 PDF,右侧为解析结果。

三份 PDF 均由上述 SDK 云端解析流程处理,逐单元格与原样本核对:

文件 SDK 返回页数 结果中的表格片段 明细核对结果
ruled.pdf 2 2 6 行、24 个数据单元格与样本一致,空白备注保留
borderless.pdf 1 1 6 行、24 个数据单元格与样本一致,空白备注保留
image_only.pdf 2 2 6 行、24 个数据单元格与样本一致,空白备注保留

每份结果的金额合计为 3,100.00 元。这里的"24 个数据单元格"是 6 行 × 4 列,不包含表头;我们检查了每行的编号、项目、金额和备注,不只检查行数或合计。

上述结果来自这三份教学样本。评估实际业务文件时,建议增加倾斜、噪点、合并单元格等有代表性的样本,并按下节的检查清单逐项核对。

例如 A-002 对应的金额仍是 300.00,备注仍是"含运费";A-003 对应 85.50,备注为空。空白没有被补成 0,也没有导致后面的内容向左移位。

两份两页文件的输出仍有两个表格片段,不能据此宣称本次已自动合并跨页表格。接入业务系统时,应先保存页级来源,再依据单据标识、表头含义、单位和续表关系决定是否合并。

我们也在 ComPDF AI 文档解析演示 上传相同文件核对。图片样本开启"图片文字识别(OCR)"后首次显示"解析失败,请重试",重试后成功,表格明细与样本一致;SDK 的本次图片样本调用成功。两种入口的设置和执行过程分别记录,不能把一次成功理解为任何文件、任何设置下都会成功。

5. 换成自己的文件,先检查这六项

检查项 具体检查
行数 有没有漏行、重复行,或把标题当成明细
列关系 编号、项目、金额是否属于同一行
金额 千分位、小数、负号、币种和单位是否正确
空白 空值是否仍在原列,没有擅自填补或继承
表头与跨页 多级表头、重复表头和续表关系是否需要处理
原文对应 能否回到来源文件与页面核对有疑问的内容

先保留原始字符串,例如 "1,200.00"。确认单位和业务规则后,再转换为金额类型。不要一开始就删除空单元格或自动拼接所有同名表头。

如果失败,先用一页文件缩小范围,记录使用的 SDK 版本、处理模式、错误提示和文件特征。区分请求或凭据问题、解析失败,以及"解析成功但关键值不正确";这三种情况的处理方法不同。

需要保留文档内容和结构时,可从文档解析开始;只需要合同号、日期、金额等指定信息时,再评估字段抽取。本文实测的是解析流程,没有执行字段抽取,也没有验证检索或问答效果。

接入资料:DocSlight 官方项目、ComPDF 文档解析 API 文档、ComPDF AI 文档解析说明。

6. 下一步:用自己的样本验证,再决定如何接入

想先看处理结果,可以在电脑端打开 ComPDF 官网文档解析演示,登录后上传一页脱敏样本,核对编号、金额、空白和表头。图片 PDF 可在设置中开启"图片文字识别(OCR)",再查看结果。

确认输出符合业务要求后,再用上面的 SDK 代码接入。需要批量处理、私有化部署或特殊版式评估时,可以通过 ComPDF 官网咨询入口说明文件类型、处理量、部署要求和已发现的问题,便于评估适合的方案。

附:生成本文教学样本

以下代码用于生成教学样例 PDF 和核对数据。表格解析由前面的 ComPDF DocSlight 完成。

复制代码
python -m pip install reportlab==4.4.9 pypdfium2==5.13.0
python make_samples.py

将下面代码保存为 make_samples.py:

复制代码
import json
from pathlib import Path

import pypdfium2 as pdfium
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.cidfonts import UnicodeCIDFont
from reportlab.pdfgen import canvas

OUT = Path(".")
OUT.mkdir(parents=True, exist_ok=True)
pdfmetrics.registerFont(UnicodeCIDFont("STSong-Light"))
HEADERS = ["序号", "项目", "金额(元)", "备注"]
ROWS = [
    ["A-001", "零件A", "1,200.00", ""],
    ["A-002", "零件B", "300.00", "含运费"],
    ["A-003", "零件C", "85.50", ""],
    ["A-004", "零件D", "1,200.00", "待复核"],
    ["A-005", "零件E", "234.50", ""],
    ["A-006", "零件F", "80.00", ""],
]
WIDTH, HEIGHT = 595, 842
XS = [48, 138, 298, 423, 547]


def page(c, rows, page_number, ruled):
    c.setFillColorRGB(0.12, 0.20, 0.28)
    c.setFont("STSong-Light", 20)
    c.drawString(48, 783, "教学采购明细")
    c.setFont("STSong-Light", 10)
    c.drawString(48, 758, "采购数据用于教学演示与结果核对。")
    c.drawString(48, 738, f"采购单 DEMO-001 / 第 {page_number} 页")
    top = 706
    row_height = 30
    if ruled:
        c.setFillColorRGB(0.91, 0.95, 0.97)
        c.rect(XS[0], top - row_height, XS[-1] - XS[0], row_height, fill=1, stroke=0)
        c.setStrokeColorRGB(0.55, 0.63, 0.68)
        c.setLineWidth(0.6)
        bottom = top - row_height * (len(rows) + 1)
        for x in XS:
            c.line(x, bottom, x, top)
        for i in range(len(rows) + 2):
            y = top - i * row_height
            c.line(XS[0], y, XS[-1], y)
    c.setFillColorRGB(0.12, 0.20, 0.28)
    c.setFont("STSong-Light", 11)
    for i, row in enumerate([HEADERS, *rows]):
        for x, value in zip(XS, row):
            c.drawString(x + 10, top - 20 - i * row_height, value)
    c.setFont("STSong-Light", 10)
    c.drawString(48, top - row_height * (len(rows) + 1) - 30, "单位:人民币元;空白备注按原样保留。")
    c.drawString(48, 52, "本样本由 ReportLab 生成;页面编号供结果核对使用。")
    c.showPage()


c = canvas.Canvas(str(OUT / "ruled.pdf"), pagesize=(WIDTH, HEIGHT))
page(c, ROWS[:4], 1, True)
page(c, ROWS[4:], 2, True)
c.save()
c = canvas.Canvas(str(OUT / "borderless.pdf"), pagesize=(WIDTH, HEIGHT))
page(c, ROWS, 1, False)
c.save()
doc = pdfium.PdfDocument(str(OUT / "ruled.pdf"))
c = canvas.Canvas(str(OUT / "image_only.pdf"), pagesize=(WIDTH, HEIGHT))
for i in range(len(doc)):
    image = doc[i].render(scale=2).to_pil()
    c.drawInlineImage(image, 0, 0, width=WIDTH, height=HEIGHT)
    c.showPage()
c.save()
(OUT / "expected.json").write_text(json.dumps(
    {"headers": HEADERS, "rows": ROWS, "total_cny": "3100.00",
     "crop_bbox": [48, 136, 547, 346]},
    ensure_ascii=False, indent=2), encoding="utf-8")
print(f"Created three teaching PDFs in {OUT}")
相关推荐
蜗牛互联网1 小时前
Java Agent 工具调用的 allowlist、参数校验与调用预算
java·开发语言·人工智能·后端·oracle
大文说跨境1 小时前
多账号环境隔离方案技术选型:指纹浏览器、VPS 与云手机的三种架构对比
java·开发语言·前端
92year1 小时前
我用运筹学穷举了麦当劳全菜单:30 块预算,怎么吃到全局最优?
python·agent·mcp
用户7624117567051 小时前
第三章 流程控制语句
python
chenbingjie_c1 小时前
手撕 C++ priority_queue:二叉堆 + 模板仿函数 + 模板特化(保姆级逐函数拆解)
开发语言·c++
ttwuai1 小时前
Go后台管理系统开源项目有哪些?如何按交付形态筛掉不合适的仓库
开发语言·golang·开源
东莞市云毅网络有限公司1 小时前
结构化标注缺失怎么查:JSON-LD抽取与每日巡检脚本实现
python·sqlite·自动化运维·geo·数据监测
yuniko-n1 小时前
【Java】关于容器选型:Deque、PriorityQueue、LinkedList
java·开发语言
扶风ff1 小时前
练题簿在线免费刷题:创建个人任务,把刷题、听题和模考目标放到每天的学习中
开发语言·javascript·学习·小程序