停止在渲染时解析 PDF
大多数开发者构建 PDF 提取工具的方式都是错误的。
他们试图从视觉输出中猜测文档结构。他们将页面渲染到画布上,并观察像素位置。他们使用计算机视觉来寻找列或表格。
这种方法是本末倒置的。
PDF 的操作符流(operator stream)中已经包含了你所需要的结构。
表格不仅仅是一组像素。它是一组路径操作符,例如 moveTo、lineTo 和 rectangle。区域边界被编码在 CTM 栈中。你不需要去重建那些已经存在的东西。
停止使用视觉启发式方法。请使用源数据。
我之前尝试过使用 De Casteljau 细分算法来处理边界框(bounding boxes)。但在测试过程中我放弃了它。
De Casteljau 是一种细分算法。你不断分割曲线,直到分段足够小。这对于渲染是有效的,但对于边界框来说效果很差。
你必须选择一个容差。如果容差太大,框就不准确;如果容差太小,你就会在递归上浪费资源。有一种更好的方法:使用二次方程的解析解(analytical solution)是精确的。它不需要递归,也不需要分配分段。
同样的逻辑也适用于区域检测。
许多工具通过寻找两个文本组之间的中点来计算区域边界。这是一种视觉上的猜测,而不是结构性的。
如果你使用中点,亚像素舍入(sub-pixel rounding)会将区域放置在错误的区域中。
解决方法很简单。使用边界框的上边缘。一个区域根据其起始位置来确定其所属的区域。请使用上边缘的实际 Y 坐标。
构建一个真正的 PDF 提取器更难。你必须:
- 读取操作符流,而不仅仅是文本内容。
- 构建 CTM 栈以跟踪矩阵状态。
- 从几何角度对子路径进行分类。
- 输出带有溯源信息的片段。
这比基于像素的猜测要费力得多,但它能产生确定性的结果。
基于像素的工具在 100% 缩放和 150% 缩放时会给出不同的结果。它是在进行视觉伪影的模式匹配,而不是在提取结构。
如果你不解析操作符流,你只是在做一个演示 Demo。它在你的测试文件上可能有效,但在处理用户真实上传的文件时会失败。
解析操作符流这条路很难走。你必须理解填充(fill)和描边(stroke)状态机以及 PDF 规范。但你只需要学习一次,之后它就能适用于每一个 PDF。
不要再在渲染时解析 PDF:一种更好的结构化提取架构
如果你曾经尝试构建一个从 PDF 中提取数据并在 Web 应用中展示的功能,你可能遇到过同样的难题:解析 PDF 非常困难。
“渲染时解析”反模式
许多开发者在处理 PDF 时,会采用一种直观但错误的方法:在用户请求查看数据时,实时解析 PDF 并将其转换为结构化格式。
这种方法看起来很合理:用户上传 PDF $\rightarrow$ 后端解析 $\rightarrow$ 前端渲染。
为什么这是一个问题?
1. PDF 的本质
PDF(便携式文档格式)的设计初衷是视觉保真度,而不是数据结构。PDF 文件本质上是一系列绘图指令(例如:“在坐标 $(x, y)$ 处绘制字符 'A'”),而不是像 HTML 或 JSON 那样的结构化文档。
2. 延迟与性能
解析 PDF(尤其是涉及 OCR 或复杂的布局分析时)是一个计算密集型任务。如果在渲染路径中进行解析,用户必须等待漫长的处理过程,这会导致极高的延迟。
3. 不确定性与脆弱性
由于 PDF 缺乏语义结构,解析逻辑往往非常脆弱。微小的布局变化(如字体大小、间距或列宽的变化)都可能导致解析失败或数据错误。
“提取优先”架构
为了解决这些问题,我们需要将数据提取与数据渲染解耦。
与其在渲染时解析,不如采用一种“提取优先”的架构。在这种架构中,解析过程发生在数据进入系统时,而不是在用户请求它时。
推荐的架构流程
- 摄取 (Ingestion): 用户上传 PDF。
- 提取流水线 (Extraction Pipeline):
- 布局分析 (Layout Analysis): 识别标题、段落、表格和图像。
- OCR (光学字符识别): 如果是扫描件,将图像转换为文本。
- 结构化提取 (Structured Extraction): 使用 LLM(大语言模型)或启发式规则将文本转换为 JSON。
- 结构化存储 (Structured Storage): 将提取出的 JSON 数据存储在数据库中。
- 渲染 (Rendering): 前端直接从数据库读取 JSON 并渲染。
这种架构的优势
- 极速响应: 渲染过程只需读取简单的 JSON,几乎是瞬时的。
- 可靠性: 提取逻辑在后台异步运行,即使失败也不会影响用户体验。
- 可搜索性: 存储在数据库中的结构化数据可以轻松进行全文搜索和分析。
- 成本可控: 可以对提取任务进行限流和队列管理,避免在高峰期压垮服务器。
结论
停止在渲染路径中处理复杂的 PDF 解析任务。通过构建一个异步的、以提取为中心的流水线,你可以构建出更快速、更可靠且更具扩展性的应用程序。