## 三份合成PDF的表格提取实测:默认配置在哪些地方失败

这是一批研究样板页面里的第一篇,做的是一个独立开发者动手写代码之前本该做的事:先看真实发布的需求,再实际去测那些声称能完成这个任务的工具,把结果如实报出来——包括跑不通的部分。

### 1. 谁遇到了这个问题,他们实际在要求什么?

在目录里搜索"PDF表格提取、PDF转Excel、扫描件PDF"这类需求,找到**11个由真实、带预算发布的外包任务构成的已发布特性**(Freelancer.com来源,公开发布,已脱敏)。各特性的任务数与代表性来源链接(下面不是每条关联任务的完整列表):

- **PDF Tables to Spreadsheet**(9条关联任务)——客户的PDF包含"6到15张数据表,且含合并单元格"、跨多页的财务报表、以及分散在"十几份以上"独立PDF里的调查结果。来源示例:[PDF Tables Spreadsheet](https://www.freelancer.com/projects/google-sheets/PDF-Tables-Spreadsheet)、[Accurate PDF-Excel Conversion](https://www.freelancer.com/projects/adobe-acrobat/Accurate-PDF-Excel-Conversion-40632143)、[Survey PDF Tables Excel](https://www.freelancer.com/projects/adobe-acrobat/Survey-PDF-Tables-Excel)。 - **PDF Table Data Extraction and Excel Formatting**(6条)——既有干净的单表PDF,也有"正式表格混杂叙述性段落和项目符号列表"的更乱情况。示例:[PDF Tables Formatted Excel](https://www.freelancer.com/projects/data-analysis/PDF-Tables-Formatted-Excel-40629929)。 - **50-Page PDF Document Formatting and Word Conversion**(12条)——相邻需求:整份文档重新录入,不只是表格。示例:[Accurate PDF Word Conversion](https://www.freelancer.com/projects/microsoft-word/Accurate-PDF-Word-Conversion-40627872)。 - **High-Accuracy Document Transcription and PDF/Image Data Entry**(8条)——更泛化的数据录入需求,PDF只是众多来源格式之一,还包括扫描图片和手写稿。示例:[Typing Scanned Manuscript](https://www.freelancer.com/projects/typing/Typing-Scanned-Manuscript)。

原始任务文本里有两点是"只看有没有需求"这种粗略判断会漏掉的:

- **相当一部分需求明确要的是人工,不是工具。** 多条任务原话写着"不用爬虫或自动化工具"、"必须全部手工录入",或者本身读起来更像是自由职业者自己的服务推销,而不是客户的需求(比如标题写成"jasa data entry..."/"我提供这项服务"这种模式)。这部分需求不是自动化的潜在需求——是恰好经过PDF这一步的廉价人力需求;卖家在推销自己的服务,和买家在发布真实需求,不是同一种信号。 - **Automated PDF Data Extraction to Spreadsheet 下的一批"PDF Tables to Excel"类任务措辞高度相似、预算档位也接近。** 相似措辞加相同价格区间、分布在不同项目标题下,符合"少数几个客户"或"某种模板化发帖习惯"的模式,但本次研究没有对发帖账号做去重/身份核对,所以**这批任务背后独立买家的具体数量未经核实,不能直接说是"少数几个"。** 任务数量不等于买家数量,本次研究目前也说不出真实的买家数量是多少。

这不代表需求是假的——这些带预算的任务是真实的、有来源链接、已经脱敏。但如果直接读成"N条任务提到PDF表格,所以有N个买家想要一个每月X美元的工具",会高估这个信号。

### 2. 这些需求里,哪些其实是同一件事?

把11个特性按实际诉求归类:

1. **干净的、原生数字化表格 → 表格软件**(比如财务报表、调查表格)。这是现有工具处理得最好的情况。 2. **跨多页、带重复表头的表格。** 一个独立的子场景——多条任务明确提到表格"跨越多页"。 3. **扫描件/图片PDF与手写稿 → 结构化输出。** 更难的一个子场景,反复出现("扫描图片"、"手写稿……超过100页")。 4. **整份文档重新排版**(PDF转可编辑Word,保留原始版式)——跟表格提取相邻但是不同的产品:要保的是全文档排版保真度,不是提取结构化的行列数据。

子场景1-3本质上是同一件事在不同难度下的表现;子场景4是相邻但不同的产品。

### 3. 现有工具为什么不够用?

我们没有假设开源PDF库"基本够用"(这是最常见的通用说法),而是实际安装并运行了几种真实的提取方案——`pdfplumber`、`pymupdf`、`camelot`(lattice和stream两种模式)、`tabula-py`,以及针对扫描件场景的PyMuPDF+OCR流水线——分别对三份构造出来、有精确已知标准答案的测试文档进行测试(一份干净的发票表格、一份跨两页共46行的表格、一份完全没有数字文本层的扫描仓库清单表)。并不是每个工具在每个场景下都真的跑过——完整矩阵如下,区分"实测"和因环境问题(缺Java)失败或根本没跑的情况:

| 场景 | pdfplumber | pymupdf | camelot (lattice) | camelot (stream) | tabula-py | OCR流水线 | |---|---|---|---|---|---|---| | 单页干净表格 | 100% (54/54),61.6ms | 100% (54/54),70.8ms | 100% (54/54),548.7ms | 3.7% (2/54),34.3ms | 环境失败(无Java) | 不适用 | | 跨页表格(2页) | 96.0% (265/276),372ms | 96.0% (265/276),395ms | 100% (276/276),1050ms | **本场景未测试** | 环境失败(无Java) | 未测试 | | 扫描/栅格化表格 | 静默返回空结果,3ms | 静默返回空结果,4ms | 空结果+UserWarning,3ms | 空结果+UserWarning,2ms | 环境失败(无Java) | 91.7% (22/24),4444ms |

**实际发生了什么,精确地说:**

- 在干净的单页表格上,`pdfplumber`和`pymupdf`都完美(100%单元格准确率),分别耗时61.6毫秒和70.8毫秒,都比`camelot`的lattice模式(同样100%准确,耗时548.7毫秒)快。**`camelot`的stream模式把表格结构读错了**:它把文档标题行("Monthly Financial Summary - Q3 2026")当成了多出来的一行吸进表格,导致提取出的形状是10行而不是9行。严格按绝对行列位置比较,这产生了3.7%的"准确率"(54个单元格里对了2个)——但这个数字反映的是**多出这一行导致的位置错位,不是说96.3%的文字被识别错了。** 我们没有再做一次去掉这行伪表头、按内容重新对齐的比较,所以这里不给出一个"修正后的准确率"数字。

| 行 | 列 | 期望 | 实际(stream模式) | |---|---|---|---| | 0 | 0 | Invoice # | (空) | | 0 | 2 | Date | Monthly Financial Summary - Q3 2026 | | 0 | 4 | Amount | (空) |

- 在跨页表格上,真正跑完这个场景的三个方案(`pdfplumber`、`pymupdf`、`camelot` lattice——`camelot`的stream模式在这个场景没有测试,`tabula-py`因缺Java在全部场景都失败)没有一个能自动把跨页表格拼接起来:每一种都返回两个独立的页面级表格对象,需要开发者自己检测并合并,还要处理直接拼接时会在中间冒出来的重复表头行。合并之后,`pdfplumber`和`pymupdf`各自产生了相同的11处截断——每一处都是同一种模式,"Needs Improvement"被截成"Needs Improve"。根因是打开源PDF直接就能看到的:`test2_multipage.pdf`里"Rating"这一列在生成脚本里被固定设置成60点宽(`generate_samples.py`里的`colWidths=[65, 110, 95, 130, 80, 60]`),而8pt Helvetica字体下"Needs Improvement"这个最长取值实际需要的宽度明显超过60点——文字在源PDF本身里就已经画到了单元格边框外面,不是一个"溢出几个点"的边缘情况。`pdfplumber`和`pymupdf`是按位置提取文本的,画到单元格标称边界之外的内容会被切在那条边界上。`camelot`的lattice模式在这个场景276个单元格全部正确——可能与它处理单元格边界的方式不同有关,但本次没有做边界容差参数的对照实验来确认具体是这个机制。 - 在扫描表格上,`pdfplumber`和`pymupdf`各自静默返回了0个表格(空结果,没有报错或警告);两种`camelot`模式同样返回0个表格,但打印了`UserWarning`说明该页是图片格式。加上一步OCR(PyMuPDF渲染+RapidOCR+手写的空间聚类逻辑把文字框拼成行列)之后,表格形状对了,24个单元格里对了22个,耗时4.44秒。这明显比干净数字文本场景观察到的约60-70毫秒慢,但这不是同一输入下的受控对照——OCR处理的是另一份(扫描)文档,所以这里不给出一个"慢多少倍"的具体倍数。OCR输出还丢了一处内部空格("Unit Cost ($)"变成了"UnitCost ($)")。 - `tabula-py`在这个环境里三个场景全部失败,因为它依赖Java运行时——这是一个真实的部署成本(还会让Docker镜像变大),跟提取质量本身无关。

诚实的结论是:**对于本次测试的干净单页数字表格,`pdfplumber`和`pymupdf`在低延迟下完美处理了它。** 对于另外几个场景——跨页拼接、扫描文档、边界溢出截断——这几个库在默认配置下,在我们构造的这几份文档上,以具体的、大多是静默的、不易察觉的方式失败了。这个结论能不能推广到这三份合成文档之外,本次没有测试。

### 4. 一个软件产品实际需要做到什么?

直接对应第3节在这三份文档上跑出来的问题,一个站得住的产品至少需要解决:

- **模态路由器**:在选提取策略之前先检测文本层密度,让扫描页自动路由到OCR,而不是静默返回空结果。 - **版式模式选择器**(lattice / stream或等效逻辑),有合理的默认值,并且能识别出选错模式后的明显失败迹象(比如页面标题跑到了第0行)。 - **边界容差逻辑**:让画到过窄列(比如上面"Rating"这一列)之外的文字不被静默截断。这是基于上面观察到的失败模式提出的一个假设,**不是已验证的修复方案**——我们没有用调整过边界参数重新跑一遍跨页场景来确认它真的能解决那11处截断。[Camelot 官方的高级用法文档](https://camelot-py.readthedocs.io/en/stable/user/advanced.html)记录了可配置的表格区域和列边界参数,一个真实实现需要真的去测这些参数能不能解决这个失败模式,而不是假设库在默认配置之外"就是做不到"。 - **跨页表格拼接**:比较相邻页面级表格的列几何结构和表头文字,自动合并并去掉重复表头。 - **带网格重建的OCR兜底**,不只是原始OCR——文字框需要被聚类成行列,而这在多行单元格、空单元格或倾斜扫描的情况下会进一步失效(这些情况本次测试甚至都还没覆盖)。

这些都不是什么高深的东西——它们是本次测试暴露出来的具体工程缺口,不是"接一个大模型API然后祈祷"。这些方案在更真实、更多样的目标文档上够不够用,是另一个没有测试过的问题。

### 5. 动手之前该怎么验证?

- 从你打算切入的细分场景(财务报表 / 调查导出 / 扫描档案)里拉一批更大规模的真实PDF样本,而不是每类只测一份合成文件——第3节里的每一个数字都来自三份构造文档,在真正拿去用之前应该在真实目标文档上重新测一遍。 - 找几个正在发布"PDF转Excel"任务的人聊聊,直接问清楚:什么样的准确率和交付速度真的能让他们放弃找人。第1节里那些明确偏好人工的任务提示,对这部分需求来说,便宜的人工选项可能已经"够用"了,跟工具做得好不好无关——这是一个真实存在、还没有答案的问题,不是已经定论的事。 - 优先做第4节里的边界容差修复——这是值得优先验证的一个候选方案,因为11处截断是跨页场景里观察到的最可重复的失败模式——再决定要不要投入OCR;OCR是本次测试里最慢的路径(4.44秒),耗时是在另一份文档上测出来的,跟其他场景不是同一输入,不能直接类比;OCR 91.7%的准确率我们也不拿去跟其他场景的数字做排名,因为测的不是同一份文档。 - 提前定好停止条件,但实际的准确率/成本门槛要从真实目标文档的试点数据里来,不能照搬本次实验这三份合成文件的数字——本次研究并没有确定真实买家实际能接受的准确率门槛或单文档成本是多少。

### 6. 那么——值得做吗?

**不是一个简单的是或否,要看具体切哪一片,而且本次研究只测试了三份合成文档。** 对本次测试的干净单页数字表格这个场景,现成的库在默认设置下已经完美处理了,这会限制一个付费产品单靠这一片能收多少钱。更站得住脚的产品切入点是"边界溢出处理+跨页拼接+诚实的OCR兜底"这个组合——现成的库在这几份文档上默认都没做到,但Camelot文档里记录的区域/参数选项能不能补上这个缺口,本次没有测试。本次研究**不支持**把"把pdfplumber包一层做成SaaS,按页收费"当作一个已验证的说法,也不支持把任何具体的准确率或定价门槛当作已经过市场验证的结论——两者都需要在真实目标文档上做一次真实试点(见第5节)之后才能作商业判断。

*证据来源:11个由真实、带预算发布、已脱敏的Freelancer.com任务构成的已发布需求信号页面(上文按特性关联;措辞高度相似那一批任务背后的独立买家数量未经核实)。提取基准测试:pdfplumber 0.11.10、pymupdf 1.28.2、camelot-py 2.0.0、tabula-py 2.10.0、rapidocr-onnxruntime 1.4.4,于2026-09-09针对三份有精确已知标准答案的构造文档实测;完整的工具×场景矩阵、脚本与原始JSON结果已归档至 docs/low-value-2026-09-05/pdf-extraction-scripts/(含说明复现步骤的README),可作为受控附件提供给试读读者。这篇文章和背后的基准测试尚未经过外部开发者试读;上面的结论应被视为待验证的工作假设,不是最终定论。*