walson.wang 2a06946699 1st pubulished 4 minggu lalu
..
README.md 2a06946699 1st pubulished 4 minggu lalu
__init__.py 2a06946699 1st pubulished 4 minggu lalu
__main__.py 2a06946699 1st pubulished 4 minggu lalu
cli.py 2a06946699 1st pubulished 4 minggu lalu
docx_writer.py 2a06946699 1st pubulished 4 minggu lalu
extractor.py 2a06946699 1st pubulished 4 minggu lalu
table_parser.py 2a06946699 1st pubulished 4 minggu lalu
text_scorer.py 2a06946699 1st pubulished 4 minggu lalu

README.md

pdf_table_to_docx

从文字型 PDF 中提取表格并生成 DOCX 文件,保留合并单元格、处理跨页表格、利用 NLP 模型优化文本合理性。

快速开始

# 安装依赖
pip install pdfplumber python-docx onnxruntime tokenizers

# 单 DOCX 模式(所有表格依次排列)
python -m pdf_table_to_docx document.pdf

# 目录模式(JSON 元数据 + 每表独立 DOCX)
python -m pdf_table_to_docx document.pdf --dir

# 指定输出
python -m pdf_table_to_docx document.pdf --dir -o output_dir/

处理流程

PDF 输入
    │
    ├── 1. 逐页扫描 ─────────────────────────────────────────┐
    │      │  pdfplumber.find_tables()                        │
    │      ▼                                                  │
    │   提取表格网格 grid[r][c]                                │
    │   (每个单元格 = bbox 四元组 or None)                     │
    │                                                          │
    ├── 2. 合并单元格检测 ─────────────────────────────────────┤
    │      │                                                   │
    │      ├─ 扫描连续的 None → 初测跨行/跨列跨度               │
    │      ├─ bbox 验证 → 消除假阳性纵向合并                    │
    │      │  (单元格底部必须延伸到目标行底部)                  │
    │      └─ T 形冲突解决 → 按 row-major 顺序认领区域          │
    │                                                          │
    ├── 3. 文本清洗 ───────────────────────────────────────────┤
    │      │                                                   │
    │      └─ NLP 辅助换行符清理                               │
    │         规则 + GPT-2 评分对比 → 去包装换行,留结构换行     │
    │                                                          │
    ├── 4. 跨页表格合并 ───────────────────────────────────────┤
    │      │                                                   │
    │      ├─ 阶段一(严格匹配):列数+列宽+页边距+表头比对     │
    │      └─ 阶段二(宽松匹配):列数+页边距+连续性信号        │
    │          ├─ 空首列延续                                   │
    │          ├─ NLP 文本延续(GPT-2 评分拼接提升)            │
    │          └─ 纵向合并语义断层修复                          │
    │                                                          │
    ├── 5. DOCX 生成 ──────────────────────────────────────────┤
    │      │                                                   │
    │      ├─ python-docx 重建表格网格                          │
    │      ├─ gridSpan / vMerge 写入 XML                        │
    │      ├─ 列宽比例、字体、对齐、边框                        │
    │      └─ JSON 元数据(页号、跨页标记、行列数、预览)       │
    │                                                          │
    └── DOCX 输出                                              │
       (单文件 or 每表独立)                                    │

关键步骤详解

1. 合并单元格检测

PDF 没有"合并单元格"的原生概念。pdfplumber 将表格解析为网格 grid[r][c],其中被合并覆盖的网格位置为 None

三步检测法

原始网格:
  [0,0]=CellA  [0,1]=None    [0,2]=CellB  [0,3]=None
  [1,0]=None   [1,1]=CellC   [1,2]=None   [1,3]=CellD

第一步:扫描连续的 None
  CellA 在 [0,0]→ 右扫发现 [0,1]=None → col_span=2
               → 下扫发现 [1,0]=None → row_span=2
  CellA 实际合并范围 = 2×2(覆盖 [0,0]~[1,1])
  
第二步:bbox 验证纵向合并
  对 row_span>1 的候选,检查单元格底部是否 ≥ 目标行底部
  防止「合计」行跨越导致的假阳性合并
  
第三步:T 形冲突处理
  按 row-major 顺序认领区域,冲突时自动缩减
  如纵向合并先认领了 [17,2],横向合并到 [17,0] 时
  检测到冲突 → 缩减至 col_span=1

2. 跨页表格检测与合并

两阶段判断

阶段一(严格匹配):
  列数相同 + 列宽分布相似(|w1-w2|<10%)→ 肯定是跨页

阶段二(宽松匹配)—— 三个连续性信号:
  
  信号 A(空首列延续):
    表格 4 末行: ['分项服务方案', ..., '4']
    表格 5 首行: ['' , '节能管理', ..., '4']
                  ↑ 首列为空 → t4 的分类延续到 t5

  信号 B(NLP 文本延续):
    「资源投入」(0.013) + 「和管理」(0.005)
    → 拼接「资源投入和管理」(0.056) → 提升 4.3 倍 → 是延续

  信号 C(全表空首列):
    t5 前 5 行首列全部为空 → 续表模式

语义断层修复

跨页合并后,纵向合并组在分页处断裂,导致上一页的分类标签无法覆盖下一页:

合并前:
  行4-7: [分项服务方案, ...]      ← page 23
  行8-12: ['' , ...]              ← page 24(空标签)

修复后:
  行4-12: [分项服务方案, ...]     ← 扩展合并覆盖

实现方式:合并后扫描各列,若 t1 末行有纵向合并且延伸到 t1 底部、t2 对应位置为空,则扩展 t1 的 row_span 覆盖到 t2 区域。

3. 文字合理性(NLP 辅助清洗)

使用中文 GPT-2 模型(ONNX 格式,约 473MB)计算文本的自然度评分

评分原理

score(text) = 1 / (1 + PPL / 10)

其中 PPL = exp(mean(loss)),loss 为模型对每个 token 的交叉熵。

应用一:跨页文本延续检测

当列宽不匹配时,用 NLP 判断两段文本是否应该拼接:

文本片段 单独评分 拼接评分 提升 结论
「资源投入」 0.013 0.056 4.3× 应拼接
「和管理」 0.005
「采购」 0.071 0.088 1.25× 应拼接
「需求。」 0.003

应用二:换行符清理

PDF 表格中常因列宽不足产生包装换行。混合规则+NLP 判断:

规则系统 → 4 级判断
  1. 单行 ≤1 字符 → 包装换行,删
  2. 括号分裂 "(万\n元)" → 删  
  3. 分值模式 "文字\n数字" → 保留
  4. NLP 评分提升 > 1.2× → 删(合并后更自然)
  5. 默认 → 保留(保守策略)

示例结果:

原文 处理后 原因
★采购预算金额(万\n元) ★采购预算金额(万元) 括号分裂
内容\n部门 内容\n部门 两行标题,保留
绿化整洁\n20 绿化整洁\n20 分值模式,保留
被考核\n单位意\n见 被考核单位意见 NLP 提升显著
会场清洁卫\n生\n20 会场清洁卫生20 碎片合并

模型文件

模型位置:models/gpt2-chinese-cluecorpussmall-onnx/

  • 模型来源:uer/gpt2-chinese-cluecorpussmall
  • 导出格式:ONNX(CPU 推理,单次约 21ms)
  • 评分特性:BERT WordPiece 分词器,会对空白字符做了标准化处理

如未下载模型,不影响表格提取和 DOCX 生成功能,仅跳过 NLP 相关的文本优化步骤。

文件结构

pdf_table_to_docx/
├── __init__.py          # 包入口
├── __main__.py          # python -m 调用
├── cli.py               # 命令行界面(单文件/目录模式)
├── table_parser.py      # 表格解析 + 合并单元格检测 + 换行清理
├── docx_writer.py       # DOCX 文档生成
├── extractor.py         # 主协调器 + 跨页检测合并
└── text_scorer.py       # NLP 文本评分(GPT-2 ONNX)

输出示例

目录模式结构

output_dir/
├── tables.json                # 全局元数据
│   ├── pdf_file               # 源文件名
│   ├── total_tables           # 表格总数
│   └── tables[]               # 每个表格的详细信息
│       ├── id                 # 序号
│       ├── file               # 对应 DOCX 路径
│       ├── pages              # 所在页码(1-indexed)
│       ├── rows, cols         # 行列数
│       ├── cross_page         # 是否跨页
│       └── preview            # 首行前 5 列预览
└── tables/
    ├── table_001.docx
    ├── table_002.docx
    └── ...

JSON 元数据示例

{
  "pdf_file": "招标文件.pdf",
  "total_tables": 36,
  "tables": [
    {
      "id": 1,
      "file": "tables/table_001.docx",
      "pages": [5],
      "rows": 3,
      "cols": 3,
      "cross_page": false,
      "preview": ["包件号", "包件名称", "★采购预算金额(万元)"]
    },
    {
      "id": 4,
      "file": "tables/table_004.docx",
      "pages": [23, 24],
      "rows": 26,
      "cols": 5,
      "cross_page": true,
      "preview": ["评审内容", "评审因素", "类型", "评审标准", "分值"]
    }
  ]
}

CLI 参考

python -m pdf_table_to_docx <pdf_path> [options]

选项:
  -o, --output PATH     输出路径(单文件 .docx / 目录模式文件夹)
  --dir, --directory    目录模式(JSON + 每表独立 DOCX)
  -v, --verbose         详细日志
  --page-width W        页面宽度 cm(默认 21.0 = A4)
  --page-height H       页面高度 cm(默认 29.7 = A4)
  --margin M            页边距 cm(默认 2.0)

依赖

  • pdfplumber — PDF 文字提取和表格检测
  • python-docx — DOCX 文档生成
  • onnxruntime — GPT-2 模型推理(可选,用于 NLP 文本优化)
  • tokenizers — HuggingFace 分词器(可选)