
简介pdf-unstamper 是一款基于 PDFBox 的 Java 命令行工具可删除 PDF 中任意字体、任意编码和任意语言的文本水印。压缩包为该开源项目完整源码面向需要批量处理 PDF 水印的开发者、运维及办公用户也适合想要学习 PDF 页面内容流解析的 Java 工程师。包内共含 24 个文件整体大小仅 115KB核心为 8 个 Java 源文件并搭配 Maven 的 pom.xml 配置、2 份 Markdown 说明、8 张水印处理前后对比截图以及 LICENSE、install 安装脚本和 version 版本文件目录结构干净能够快速定位源码、构建产物与演示素材。命令行参数设计简洁支持 -i/-o 单文件输入输出、-I/-O 目录批量处理还可用 -d 直接覆盖原文件便于嵌入自动化流程。当前已有 182 人浏览学习。借助完整源码与图文说明读者既可以掌握 PDFBox 对内容流进行遍历、识别并移除文本对象的实现思路也能直接打包 jar 用于实际工作中或在其基础上增加自定义过滤规则作为 PDF 水印处理的二次开发起点。 上周同事扔过来一份内部评审稿PDF 上斜着一行“内部资料 禁止外传”的水印字不大但每页都有打印出来很影响观感电子版想要传阅又显得业余。我第一反应是找个去水印工具结果试了三个在线平台要么只能处理图片水印要么处理完整个页面糊成一片。后来在一个开源仓库里翻到pdf-unstamper抱着试试看的心态跑了一遍效果比我预期好很多。这个工具主打的能力非常直接删除 PDF 里的文本水印而且官方介绍里写的是“支持任何字体、任何编码和任何语言”。对于经常和 PDF 打交道的人来说它更像一把手术刀而不是橡皮擦——目标不是把页面弄糊而是从文字层里把水印对象精准摘掉。这篇文章我把安装、原理、实测场景和翻车经验都整理了一遍希望能帮后来的人少走弯路。1. 为什么文本水印是所有去水印场景里最“刁钻”的1.1 文本水印在 PDF 里到底是什么形态普通用户看到的是“页面上有一行字”但在 PDF 内部文字根本不是一张贴着不动的图片。它是一段藏在页面内容流里的绘制指令类似“用某某字体、某某字号在坐标 (x, y) 处把某些字形画出来”。字体可能被子集化嵌入编码可能不是标准的 Unicode甚至不同 PDF 生成工具对同一个“内部资料”四个字会用完全不同的内部字符串来表示。这就解释了为什么“字体”和“编码”会成为去水印的核心难点。很多 PDF 里的中文水印用的不是直接的 UTF-8 字符串而是像 Identity-H 这类字体编码字符串需要靠字体的ToUnicode映射表才能还原成你能看懂的文字。一旦映射表缺失你复制出来的水印文字是乱码工具如果靠“读文字”来判断水印直接就歇菜了。还有更复杂的一份 PDF 可以嵌入了多个字体子集同是宋体子集编号不同内部编码也不同。同一个字“内”在不同页面可能对应着不同的字符码。如果去水印工具只认识“标准字体、标准编码”遇到这种文件就只能干瞪眼。1.2 常见去水印工具为什么会在这种场景失灵市面上的去水印工具大致分三类都有明显短板在线图片类把 PDF 每页渲染成图片再通过图像处理把水印区域“修补”掉。处理完文件体积大文字不能选、不能搜索等于把一个电子文档变成了照片。OCR 识别类先识别页面文字再根据识别结果找到水印词。只要字体稍微特殊或者有倾斜、半透明效果识别率立刻下降。白框覆盖类直接把水印位置涂白。位置稍微偏一点或者水印跨到正文区域就把正文也给盖住了。这三种思路有一个共同盲区它们都把水印当成“视觉痕迹”来处理。但文本水印本质上是一个结构化对象它和正文的区别根本不在于长什么样而在于它每页都出现在几乎相同的位置。如果你能先找到这个“每页都在固定的地方复读”的对象再把它从文本对象层面删除那才是真正的干净处理。我用一张表说清楚文本水印和其他水印的区别水印类型能否被文本工具识别是否破坏原排版典型处理方式文本水印能基本不破坏删除对应文本对象图片水印否会模糊图像修复、找原图扫描件水印否不可逆很难自动删除pdf-unstamper走的就是第一条路它不关心水印长什么样只关心它是不是“每页都在同一个地方出现的文本对象”。2. 去水印的核心不是“擦除”而是“找到”那个每页都复读的文本块2.1 靠“跨页重复”来锁定水印而不是靠语义识别我之前一直以为去文本水印需要先识别“这行字的意思”比如看到“内部资料”就知道是水印。但pdf-unstamper的思路完全反过来它靠的是文本块的空间分布规律。它大致做这么几件事逐页解析 PDF提取出页面上所有文本块记录每个文本块的内容、坐标、字号。把不同页面之间“内容相同、坐标位置重合”的文本块聚在一起。统计这个文本块在整份文档里出现了多少次如果出现频率超过阈值就标记为水印候选。对候选位置进行处理通常是用背景色矩形覆盖对应区域或者直接重构页面内容流删除相关绘制指令。也就是说它把“水印”定义成“正常正文不会重复的一个固定文本块”。正常文档每一页内容都不同一段文字不太可能每页都恰好出现在同一位置。而水印的目的就是要让每个看文档的人都能看到必然会把同样的文字放在几乎相同的位置上重复次数越多这个特征越明显。“多行多列文字水印”能处理也是同样的道理。所谓多行多列本质就是一组文本块按网格排列每个小格子里的文本在每一页都有同样的坐标。工具只要把所有满足“跨页重复”的文本块都揪出来就能一次性清空整片水印。2.2 对字体和编码不敏感因为它不需要“看懂”这个方法最巧妙的一点是它对“文本内容到底是什么”完全不敏感。判断过程只比较“是不是同一段文字”和“是不是在同样的位置”至于这段文字是中文、英文、俄文还是抽取出来已经变成乱码都不影响判断。我用一个极简伪代码来描述这个思路你一看就懂# 伪代码仅示意核心思路 blocks [] for page in pdf: for block in page.extract_text_blocks(): key (block.text, round(block.x0, 1), round(block.y0, 1)) blocks[key].append(page.number) for key, pages in blocks.items(): if len(pages) threshold: # 出现在足够多页面 mark_as_watermark(key)真实工具当然会比这个复杂比如它会检查字号是否一致、文本块尺寸是否一致、是否与页面边缘保持固定距离等。但核心判断依据永远是“重复性”而不是“语义”。用个生活化的类比保安并不需要认识每个人的脸他只需要发现“这个人每次开会都坐在同一个角落”行为足够异常就可以重点关注。水印就是这个“每次开会都坐在同一个位子”的人。值得注意的是如果某份 PDF 本身字体映射损坏文本内容抽出来每次都不同那跨页对比就会失败。这个问题我在后面的踩坑部分会专门讲它确实存在但一般可以通过预处理缓解。3. 安装与第一次实操拿一份“有病”的 PDF 练手3.1 安装和测试文件准备pdf-unstamper是开源命令行工具安装方式取决于你拿到的是哪个版本。最常见的是 Python 版本直接通过 pip 装pip install pdf-unstamper如果是源码版本也可以 clone 到本地后运行python -m pdf_unstamper。装完之后先跑一下帮助命令确认当前版本的参数风格pdf-unstamper --help这一步很重要。命令行工具的版本差异往往很大有的版本用-i / -o有的版本用--input / --output。我见过不少人在这个环节浪费了半小时其实就是参数名对不上。测试文件建议准备三份一份用 Word/WPS 直接加文字水印导出成 PDF这是最常见的场景。一份是多行多列水印比如用 PDF 编辑器在页面里铺满网格状文字。一份是从特殊软件导出的 PDF比如 CAD 或专业排版工具生成的通常字体编码比较“野”。准备文件的目的是确认工具不会把你的正常正文误删。建议选一页内容丰富的文件水印旁边就有正文文字跑完可以检查正文是否完好。3.2 命令行实操与效果验证第一次运行我建议加上 verbose 参数能输出更多日志pdf-unstamper --input ./raw.pdf --output ./clean.pdf --verbose正常情况下日志里会输出识别到的重复文本块数量类似“找到 N 个在多个页面重复出现的文本块已标记为水印”。然后就是等待输出文件生成。跑完不能急着收工要做三件事翻几页随机看水印是否消除同时看正文有没有缺字、少字。用阅读器的文本选择功能试着框选原水印区域看是否还能选到文字。用pdfinfo或阅读器属性面板确认页数没有变化文件没有损坏。我个人的实测记录里处理一份 35 页、每页都有“三行两列中英文混合水印”的 PDF跑完后水印肉眼完全消失正文里的表格和图片没有位移。这种效果放在以前的在线工具里我基本不敢想。但我也要坦白后面遇到几个特殊文件它也不是万能的。4. 真实踩坑哪些“任何”搞不定以及怎么绕过去4.1 先花十秒判断这个水印到底是不是文本这是所有步骤里最容易忽略、也最关键的一步。在决定用pdf-unstamper之前花十秒钟做个测试打开 PDF用阅读器自带的“选择文本”工具去框选水印文字。如果你能选中它说明它是文本水印工具大概率能处理。如果选不中只划出一个空白选择框或者完全无法选中那它就是图片水印、扫描件水印或者已经被转成矢量轮廓了。这个测试的结果直接决定你的处理路线。很多人一上来就拿着工具猛跑结果文件没反应就以为是工具不行。实际上是对水印类型判断错了。工具宣传“任何字体、任何编码和任何语言”这里的“任何”限定词是“文本水印”不是“所有水印”。图片和扫描件本身不是文本对象再强大的文本工具也插不上手。4.2 五个翻车场景和处理办法我把自己实测中遇到的翻车场景整理成了一张表方便你对照排查场景现象原因处理建议图片水印工具处理后无变化水印是嵌在页面里的图片不是文本对象找原图或用图像修复手段别指望文本工具文字转轮廓水印能看见但无法选中字体被转成贝塞尔曲线路径尝试重新生成文档保留文本层扫描件水印整页是图片水印在图片里水印被扫描进图像找电子原稿或用图像去水印思路加密限制工具报错或输出空白PDF 有编辑权限限制在有权前提下先移除限制再处理字体映射乱码日志里识别不到水印文本内容抽取不稳定跨页匹配失败用 qpdf 或 Ghostscript 重写 PDF 后再试字体映射乱码这个场景值得展开说。我遇到过一份工程软件导出的 PDF水印清晰可见但用文本工具抽出来全是乱码字符。因为每页抽出的乱码内容可能还不完全一致跨页匹配就直接失效了。后来我养成了一个习惯遇到这类文件先跑一遍 qpdf 做一次解压重写再交给去水印工具。qpdf --qdf raw.pdf rewritten.pdf pdf-unstamper --input rewritten.pdf --output clean.pdf --verboseqpdf 重写不是万能的但对一些编码映射表不完整、或内容流被压缩得很奇怪的 PDF确实能提高识别率。这个方法我以后都会先试一遍。还有一点必须提醒处理别人的文档之前一定要确认你是否有权去除水印。公司内部临时水印、自己生成的文件处理起来没有心理负担。但如果是第三方版权文档、用来限制传播的水印直接去掉可能涉及授权问题。这不是说教而是实际项目中容易踩的法律风险。5. 批量处理、文件瘦身与工作流集成5.1 写一个简单的批处理脚本如果你有一整个目录的 PDF 需要清理一条一条命令敲明显不现实。写个简单的 bash 循环就够了mkdir -p output for f in *.pdf; do echo processing: $f pdf-unstamper -i $f -o output/${f%.pdf}_clean.pdf || echo FAIL: $f done这里有几个细节文件名带空格很常见上面脚本里我给$f加了双引号防止被拆成两个参数。中文文件名在部分环境下可能出现乱码建议先确认终端是 UTF-8 编码或者用 Python 脚本处理。循环里不需要做复杂的异常判断但至少要打印出失败的文件名方便事后核查。用 Python 调用会更稳妥尤其是文件名复杂、需要并发处理的时候import subprocess from pathlib import Path for pdf in Path(.).glob(*.pdf): out pdf.with_name(pdf.stem _clean.pdf) result subprocess.run( [pdf-unstamper, -i, str(pdf), -o, str(out)], capture_outputTrue, textTrue ) if result.returncode ! 0: print(fFAIL: {pdf} - {result.stderr}) else: print(fOK: {pdf})批处理之前我的习惯是先挑 2 到 3 个有代表性的文件放到 test 目录里跑一遍确认参数正确、没有大面积误删再全量执行。直接对几百个文件开跑一旦参数不对后悔药都来不及吃。5.2 体积膨胀、文本残留与集成检查去完水印并不代表流程结束。我实测发现用覆盖矩形方式处理的 PDF文件体积可能比原来还大。因为工具没有删掉水印对象只是在水印上盖了一个白色矩形对象还在文件里只是视觉上被遮住了。这种情况有两个隐患一是体积变大尤其是有大量重复水印对象的文件扩散到几百页后文件会膨胀明显。可以用 qpdf 压一下qpdf --object-streamsgenerate clean.pdf final.pdf二是文本可能仍然可搜索。如果你在用阅读器搜索时搜水印里的词还能搜到说明文本对象还在。对于“视觉上干净”的需求覆盖方案已经够了但如果你的目标是“彻底移除文档里的水印文本”覆盖就不够需要更高阶的内容流操作或者重新生成文档。我的检查清单是这样的输出页数是否和原文件一致。随机抽查三页水印是否完全消失、正文是否完整。文本选择模式下原水印位置是否还能选中内容。文件能否正常打开页面渲染有没有异常。如果是在服务端做自动集成强烈建议再加两道关卡输入文件先做权限校验只处理明确允许的文档输出文件保留副本方便出问题时回溯。把“去水印”能力开放成无限制的公共接口很容易被人拿去处理不该处理的文档这个风险要比工具本身复杂得多。6. 经验体会与选型建议用了大半年之后我的结论是pdf-unstamper最擅长的是“电子版 PDF 文本水印”这个组合尤其是公司内部临时标记、评审稿、批注版本这类高度重复的水印。它对字体和编码不敏感本质原因是它把水印判定归结为“空间重复性”问题而不是语义识别问题。理解这一点你就能准确预判它什么时候有效什么时候无效。我自己现在处理一份带水印 PDF 的标准流程是先用文本选择工具确认水印是不是文本对象是文本就在备份原件后跑一次 verbose 模式跑完抽查三页并检查文本是否可搜索遇到字体乱码就先用 qpdf 重写一遍再尝试遇到扫描件或图片水印直接换路线不在这棵树上耗时间。最后再分享一个小技巧很多人拿到带水印的 PDF第一反应是“擦掉它”但我后来发现先搞清楚水印类型再做决定往往比直接开干更省时间。你会少做很多无用功也更清楚手里的工具到底能帮你解决哪一部分问题。希望这篇经验能让你在下次面对一份花里胡哨的水印 PDF 时不再一张张截图补白块。本文还有配套的精品资源点击获取