Skip to content

借鉴 pdf-inspector 补齐原生 PDF 布局、语义与表格质量 - #6

Merged
caichuanwang merged 2 commits into
masterfrom
feat/pdf-inspector-optimizations
Aug 5, 2026
Merged

借鉴 pdf-inspector 补齐原生 PDF 布局、语义与表格质量#6
caichuanwang merged 2 commits into
masterfrom
feat/pdf-inspector-optimizations

Conversation

@caichuanwang

Copy link
Copy Markdown
Owner

背景

对照 pdf-inspector(Firecrawl 的纯本地 PDF 分类/提取/转 Markdown 库)分析后,OpenDocs 原生 PDF 路径存在四类可落地差距:逐词输出导致段落碎片化与双栏交错、字体元数据采集后丢失导致无法表达标题/列表/代码、页眉页脚与断词原样泄漏、无边框表格与编码质量问题无本地兜底。

改动范围(均在私有 src/opendocs/parsers/pdf/ 内,无公共 API/依赖变更)

模块 内容
extract.py 词级候选 → 行重建 → 双栏/跨栏阅读顺序(有界聚类);拒绝一维伪表格;文本不同的嵌套表保留;Unicode 非字符/代理项编码质量检测
semantics.py(新增) 文档级正文基准字号 → H1–H3 标题、无序/有序列表、代码块(动态围栏防逃逸)、段落合并与保守断词;重复页眉/页脚/页码过滤
analyze.py 移除不可控的 pdfplumber text/text 回退,改为 ≤500 词的有界本地无边框表格启发式(3 行 × 2 列、数字占比 ≥40%)
merge.py 表格/视觉区域按词中心点所有权(轻微擦边不再误删);hybrid 视觉元素按所属栏插入,保持列阅读顺序;所有权裁决后再重建行
models.py NativeTextCandidate 携带 font_size/font_name 并贯通 native worker wire
parser.py _has_semantic_blocks 识别标题/列表/代码块,避免误报 NoUsableContent

验证

pytest 全量        634 passed, 9 skipped(基线 616,新增 18 个测试)
ruff check .       通过
ruff format --check 通过(112 文件)
ty check src tests 通过
git diff --check   通过

新增测试覆盖:真实合成 PDF 反例(对齐段落不误判表格、无边框数字表格可恢复)、双栏/跨栏/右栏视觉顺序、断词正反例、1–2 页页码过滤、Unicode 各平面边界、完整 PageFacts wire 往返、列表 identity/ordinal、代码围栏与语义-only 文档判定。

残余风险 / 未覆盖

  • 原生路由 PDF 的输出从逐词 TextBlock 变为语义块;hybrid/full-vision 页面输出不变
  • 跨栏表格与视觉元素插入顺序为新启发式,需私有验收语料进一步校准
  • pdf-inspector 的快速预分类层(TextBased/Scanned 路由)与逐页 OCR 回退未引入
  • 私有验收语料与真实多页复杂版式未在本次验证(Not-tested)

PDF 原生路径此前逐词输出且仅按 (top,left) 排序,字体元数据采集后即丢失,
导致双栏交错、段落碎片化、标题/列表/代码不可表达,页眉页脚与断词也原样泄漏。

- extract.py: 词级候选 + 行重建 + 双栏/跨栏阅读顺序,行分割与列聚类有界
- semantics.py: 文档级正文基准字号、标题/列表/代码块/段落语义、保守断词、
  重复页眉页脚与页码过滤;代码围栏动态加长防逃逸
- analyze.py: 移除不可控 pdfplumber text/text 回退,改为 ≤500 词的有界本地
  无边框表格启发式(3 行 ×2 列、数字占比 ≥40%)
- extract.py: 拒绝一维伪表格;文本不同的嵌套表不再被去重吞掉;
  Unicode 非字符/代理项纳入编码质量检测
- merge.py: 表格/视觉区域按词中心点所有权,hybrid 元素按所属栏插入,
  保持列阅读顺序;所有权裁决后再重建行
- models.py: NativeTextCandidate 携带 font_size/font_name 并贯通 wire
- parser.py: _has_semantic_blocks 识别标题/列表/代码块,避免误报无内容

Tested: 全量 pytest 634 passed/9 skipped;ruff check/format、ty check、
git diff --check 全部通过;新增真实合成 PDF 反例(对齐段落不误判表格、
无边框数字表格可恢复)及双栏/跨栏/断词/页码/Unicode 边界测试
Scope-risk: 原生路由 PDF 的输出从逐词 TextBlock 变为语义块,依赖视觉的
hybrid/full-vision 页面输出不变;跨栏表格与视觉插入顺序为新启发式
Not-tested: 私有验收语料与真实多页复杂版式未在本次验证;pdf-inspector
预分类层与 OCR 回退路由暂未引入
路由耗时此前被两个因素主导:worker 冷启动时顶层 import pdfplumber
(约 100 ms+),以及无文本扫描件仍走 pdfplumber 全量解析;同时
3 MiB 的 wire 估算上限使约 2000 词以上的真实文档(如 22 页商业
计划书)直接解析失败,尽管实际帧仅约为估算的四分之一。

- analyze.py: 快速分类层——用 pdfminer 的 xref/页树层轻量扫描内容流
  操作符,全文无文本操作符时直接构造扫描件/空白页 facts,跳过
  pdfplumber;pdfplumber 改为函数内惰性导入,冷启动不再加载
- analyze.py: wire 估算上限 3 MiB -> 8 MiB,与 native 协议内联上限对齐,
  22 页真实语料从直接报错变为可完整解析
- analyze.py: 空白页(无文本无图像)保持原有 BLANK 路由,不被误判扫描件
- tests: 快速路径跳过 pdfplumber、旋转几何保留、页数上限、文本 PDF 回退、
  空白页 BLANK、22 页中型文档 wire 放宽,共 5 个新测试

Tested: 全量 pytest 639 passed/9 skipped;ruff/format/ty 通过;
扫描件路由 129 ms -> 110 ms(新进程),热调用 4.7 ms(文本型的 1/37)
Scope-risk: 快速分类仅命中全文无文本操作符的文档;任何含文本或无法
解码的流均回退原路径,行为与之前一致
Not-tested: worker 常驻复用(跨文档热调用 4.7 ms 的收益需架构级支持)
@caichuanwang
caichuanwang merged commit b70cda1 into master Aug 5, 2026
14 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant