根据阅读者身份变化的PDF
摘要
本文介绍了一种技术,利用PDF规范中的替换文本属性,在PDF内部嵌入隐藏的Markdown结构,使得LLMs能够提取干净、结构化的数据,而人类看到的仍然是相同的视觉文档。
暂无内容
查看缓存全文
缓存时间: 2026/06/12 17:55
# 自适应PDF
来源:https://sgaud.com/texts/pdf
PDF 是一种视觉格式,它存储了如何在页面上绘制字符的指令。规范中其实支持“带标签 PDF”(Tagged PDF)——一种用结构树标记标题、段落、列表的方式。某些领域会使用它,比如政府的无障碍访问要求、企业出版流水线。但你实际遇到的大多数 PDF 都是未加标签的。LaTeX、Chrome 的「打印为 PDF」以及大多数导出工具都不会生成标签。所以你得到的只有坐标和字体大小。文本提取器从左到右、从上到下读取绘制指令,然后祈祷结果正确。
这在只有人类读者的时候无关紧要。但现在大多数 PDF 最终都流向了 LLM。我们把它上传到 ChatGPT,让 Claude 总结,或者扔给解析器处理。而每一款这样的工具都在与同一个问题作斗争:从一个从未承载过结构的格式中重建结构。LLM 看到的是 `Project Alpha\nLed a team of 5 engineers\nto deliver the`,然后得猜测标题在哪里结束、句子从哪里继续。有时它能猜对,更多时候则不能。
我想做一种 PDF:人类看到的是格式化文档,机器提取出的则是干净的 Markdown。同一个文件,没有新扩展名,就是一个 `.pdf`。
## 它是如何工作的
PDF 规范中(自 PDF 1.4,2001 年起)有一个属性,允许你为标记内容定义替代文本。渲染器会忽略它——它们按照内容流绘制任何内容。但支持该属性的文本提取器会返回替代文本,而不是视觉文本。在我的测试中,PyMuPDF 和 Poppler 都支持它。不同工具和版本的支持情况有所差异,但主流开源提取器都能处理。
这个属性原本是为连字以及无法自然映射到 Unicode 的字符设计的。比如视觉连字 "fi" 应该被提取为两个字符 "f" 和 "i"。它从未被用于更大的范围。
我们将其用于文档级别。通过标记内容序列(marked-content sequences)为内容流附加替代文本,因此支持该属性的提取器会返回结构化的 Markdown,而不是原始的视觉文本。PDF 渲染效果完全一样——同一个文件,根据读取者的不同,产生两套完全不同的输出。
## 提取器实际看到的内容
同一个 PDF,同一份视觉外观。以下是 PyMuPDF 分别从两种版本中提取出的内容。
**普通 PDF:**
```
Quarterly Infrastructure Report
Overview
Cloud migration completed ahead of sch
edule. Three critical services were
moved to the new cluster.
Key Metrics
Uptime: 99.97%
Latency: 42ms avg (down from 68ms)
Cost: $12,400/mo (down 34%)
Action Items
Migrate remaining batch jobs by Q3
Set up automated failover for db-west
Review cost allocation per team
```
**Smart PDF:**
```
# Quarterly Infrastructure Report
## Overview
Cloud migration completed ahead of schedule. Three critical services were moved to the new cluster.
## Key Metrics
| Metric | Value |
|---------|---------------------------|
| Uptime | 99.97% |
| Latency | 42ms avg (down from 68ms) |
| Cost | $12,400/mo (down 34%) |
## Action Items
- Migrate remaining batch jobs by Q3
- Set up automated failover for db-west
- Review cost allocation per team
```
两个文件在预览、Adobe 或任何 PDF 查看器中看起来完全相同。但普通提取没有层级结构,行会在句子中间断开,项目符号与段落无从区分,表格被压平成零散的行。智能提取则带有 `#` 标题、Markdown 表格、`-` 项目符号,以及不会在单词中间断开的句子。LLM 无需猜测「Key Metrics」是不是段落标题,也不需要猜测那三行是不是列表——这些都是显式给出的。
## 基准测试
使用我们的工具将若干 PDF 转换为 Smart PDF,然后用 PyMuPDF 的 `get_text()` 以及 [pdf2go.com](https://www.pdf2go.com/seaparately) 分别从两个版本中提取文本,两者都返回了 Markdown。Token 计数使用 tiktoken(cl100k_base)。基准测试脚本在仓库中。
| 文档 | 页数 | 大小差异 | 普通 Token | Smart Token |
|--------------------|------|----------|------------|-------------|
| 简历 | 1 | +15.7% | 650 | 668 |
| 教科书 | 417 | -8.5% | 193,064 | 195,858 |
| 小说章节 | 38 | +4.7% | 16,472 | 15,958 |
| 研究论文 | 18 | +2.5% | 8,082 | 7,897 |
Token 计数大致相当。优势不在于更少的 token,而在于同样的 token 现在携带了结构。`## Overview` 与 `Overview` 代价相同,但前者告诉机器它看到的是什么。在不增加 token 数量的前提下,每个 token 的信息密度提升了。
对于大多数文件,体积开销在个位数百分比。教科书体积反而缩小了,这是因为 PyMuPDF 在保存时使用了 `garbage=3` 参数移除了无用的 PDF 对象——这是一种常规优化,并非特定于本技术。
将 Smart PDF 分别上传到 ChatGPT 和 Claude,要求它们逐字符复制它们看到的原始文本。两者都返回了 Markdown:`#`、`##`、`-` 项目符号。仅凭这个结果还不能完全下定论,因为 LLM 自身具备结构推断能力,像 Docling 这类工具也可以通过布局分析从普通 PDF 生成 Markdown。但输出与我们嵌入的层完全一致,包括一些格式选择——这些选择是任何布局启发式算法都无法精确复现的。
## 自适应文档
最终你得到的是这样一种文档:它能根据读取者进行自适应。人类打开它,看到的是他们熟悉的格式化 PDF——字体、布局、间距,一切正常。机器读取它,得到的是干净的 Markdown——标题、列表、结构。同一个文件,不需要单独维护两个版本,不需要转换步骤。它只是根据谁在看而呈现不同的内容。
你不需要管理什么,也不需要维护两份副本。文档自身会根据被消费的方式决定呈现什么。
我正积极进一步探索这个方向,并计划为 Google Docs 开发一个扩展来简化流程。这是我对这个想法的第一次迭代。
相似文章
自适应 Markdown
自适应 Markdown 是一种开源文档格式/查看器,利用编码智能体使文档具有交互性,为学术阅读、笔记记录和自动化工作流等任务提供实时工作空间。
@tom_doerr:Obsidian PDF++ 将 PDF 反向链接转换为高亮注释。https://github.com/RyotaUshio/obsidian-pdf-plus…
Obsidian PDF++ 是一个 Obsidian 插件,可将 PDF 反向链接转换为高亮注释,实现基于 Markdown 的 PDF 注释,并改善内置 PDF 查看器的使用体验。
MEMORY.md 在文件交接中无法保留,所以我将决策轨迹放入文件内部
一位开发者介绍了 Proofpress,这是一个原型,它将可移植的修订/决策轨迹嵌入 Markdown 和静态 HTML 工件中,使代理(agent)之间的交接能够保留上下文,并为 DOCX 提供 sidecar 完整性证据。
@freeCodeCamp:PDF 文件常包含敏感信息,分享前需要隐藏。在本教程中,@allinonetools 将向您展示如何……
一篇关于使用 JavaScript、PDF.js、Canvas 和 PDF-lib 构建基于浏览器的 PDF 模糊工具以隐藏 PDF 中敏感信息而无需上传到服务器的教程。
生成可选中文本的客户端PDF出乎意料的复杂过程
本文探讨了在客户端生成可选中文本PDF的技术挑战,介绍了SDocs作为解决方案,并提供了其CLI工具的安装说明。