首页 / 资讯中心 / 文章详情

使用Python实现对比两个PDF文档的差异

使用Python实现对比两个PDF文档的差异 ★ FEATURED ARTICLE
前言对比两个 PDF 的差异听起来像一个小功能实际上是个需要先想清楚的问题你要比的是什么是两个文件的字节完全一样是两份文档的文字内容有何增删还是两页看起来的版式差别这三种问法的答案、代价和实现路线完全不同混在一起就会写出能跑但结果不对的代码。最常见的误解是PDF 对比 把两个文件读成字符串再 diff。行不通——PDF 是二进制格式内部有压缩流、对象引用、交叉引用表哪怕只改一个字整个文件的字节几乎全变。要得到人类可读的差异必须先把差异落到文本或像素这两个层面。本文按这三个层次讲清各自的做法。纯标准库能做的那部分哈希、文本 diff给可运行的代码涉及 PDF 读写与渲染的第三方库只讲思路以各库官方文档为准。一、先定层次你要比的是哪一层层次问题适用代价字节级两个文件是否逐字节相同校验、查重复最低但只给同/不同文本级文字内容增删改了哪些合同、文稿校对需要抽取文本丢版式视觉级页面上哪块像素变了版式比对、扫描件核对需要渲染慢且受渲染分辨率影响先问清楚需求属于哪一层再选工具——这是本文最重要的一句。下面逐层展开。二、字节级用哈希判断是否完全相同如果只想知道两个文件是不是一模一样算哈希就够了标准库即可完成而且对超大文件也能边读边算不用一次性读进内存。# 适用于 Python 3.8import hashlibdef file_hash(path):h hashlib.sha256()with open(path, rb) as f:# 分块读取避免把整个文件读进内存for chunk in iter(lambda: f.read(65536), b):h.update(chunk)return h.hexdigest()print(file_hash(a.pdf) file_hash(b.pdf))哈希相同可以认为文件内容一致同一算法下碰撞概率极低哈希不同只能说明文件不一样给不出哪里不一样。而且要注意PDF 只要被不同的软件重新保存过或者元数据、生成时间变了字节就会不同——这时哈希判为不同但文字内容可能一个字没改。三、文本级抽文本再用 difflib 比对这是最常用的一层。做法分两步先用 PDF 库把每页文字抽出来再用标准库difflib做差异比对。第一步依赖第三方库PyMuPDF 或 pypdf 都能抽取文本具体用法以各库官方文档为准第二步只用标准库。# 适用于 Python 3.8import difflibdef text_diff(old_text, new_text):返回逐行的差异格式类似 diff 命令的输出。old_lines old_text.splitlines()new_lines new_text.splitlines()return list(difflib.unified_diff(old_lines, new_lines,fromfile旧版, tofile新版, lineterm,))def similarity(a, b):返回 0~1 的相似度1 表示完全一致。return difflib.SequenceMatcher(None, a, b).ratio()抽取文本时通常按页拼接得到一个每页一段文字的列表再逐页比对才能定位差异在第几页。difflib.unified_diff接受两个行序列所以要先splitlines()它返回的是生成器需要list()落地才能反复使用。# 适用于 Python 3.8old 第一行\n第二行\n第三行new 第一行\n第二行改了\n第三行for line in difflib.unified_diff(old.splitlines(), new.splitlines(),fromfile旧, tofile新, lineterm):print(line)这一层的损失抽取文本会丢掉版式信息——文字顺序可能因分栏、表格、浮动元素而错乱扫描件如果没有文本层抽出来是空的。所以对比扫描件要走视觉层。四、视觉级渲染成图再逐像素比对如果两份 PDF 是扫描件或者你要核对版式有没有变就得把每一页渲染成位图再逐像素比较。渲染用 PDF 库完成像素比较可以用 Pillow第三方的ImageChops也可以自己按字节算差值。库的具体 API 以官方文档为准。视觉对比的流程示意不是可运行代码1. 把两份 PDF 的同一页分别按相同 dpi 渲染成位图2. 确保两张图尺寸一致尺寸不同要先对齐或判定为不同3. 逐像素求差得到一张差异图4. 统计差异像素占比或者把差异图保存下来供人工查看这里有个关键的分辨率陷阱dpi 定得低微小的文字差别可能被渲染抹平、比不出来dpi 定得高又会被抗锯齿、字体栅格化的细微抖动影响把其实一样的两页判成不同。所以视觉比对通常要设一个容差阈值而不是要求像素完全相同。五、怎么选按需求落到层次你的需求选的层次用什么文件是不是同一个字节级hashlib标准库文字改了哪些文本级PDF 库抽文本 difflib标准库版式/扫描件是否一致视觉级PDF 库渲染 图像库比像素只要粗略像不像文本级SequenceMatcher.ratio()看相似度实际项目里常常是组合先用字节级哈希快速筛掉完全一样的文件剩下不同的再走文本级定位到页和行版式敏感的再补一道视觉级。常见坑点坑 1把 PDF 当文本文件直接比对。❌open(a.pdf).read()然后 diff —— PDF 是二进制读到的是乱码和各种对象流。✅ 用 PDF 库抽取文本再对文本做 diff。坑 2以为哈希不同就代表内容改了。❌ 两个文件文字完全一样、只是元数据变了就判定内容有差异。✅ 哈希只回答文件是否完全相同判断内容差异要走文本层。坑 3抽取文本后忘了处理分页。❌ 把整篇拼成一个大字符串再 diff —— 差异定位到第几个字符没法告诉你在哪一页。✅ 按页抽取、按页比对输出时带上页码。坑 4unified_diff结果直接用一次就没了。❌ 拿到生成器就遍历之后再想用发现是空的。✅ 用list()落地成列表或每次重新生成。坑 5拿中文字符直接做逐字符 diff 却按字节切。❌ 把文本编成bytes再按字节 diff中文一个字三字节差异会碎成无意义的片段。✅ 保持str层面比对difflib处理的是序列元素字符串的序列元素就是字符。坑 6视觉比对不容错。❌ 要求两张渲染图逐像素完全相同结果抗锯齿、字体栅格化差异让其实一样的页被判为不同。✅ 设差异像素比例阈值渲染参数dpi、色彩模式两页保持一致。坑 7扫描件没做 OCR 就去抽文本。❌ 对扫描 PDF 抽文本得到空串误以为两篇都没内容、所以一样。✅ 扫描件先 OCR 再加文本层或直接改用视觉级比对。总结做法标准库可行给出什么结果hashlib比哈希可以相同 / 不同difflib比文本可以增删改的行PDF 抽取文本需第三方库供 diff 的纯文本渲染成图比像素需第三方库变化的区域SequenceMatcher.ratio()可以0~1 的相似度一句话总结PDF 对比的难点不在 diff而在先定义差异是哪一层。字节级用哈希回答是不是同一个文件文本级用抽取文本加difflib回答文字改了什么视觉级用渲染加像素比较回答版面变没变。标准库覆盖了哈希与 diff 两环PDF 的读写与渲染交给第三方库其 API 一律以官方文档为准三者组合使用才能既快又准地得到想要的结果。
阅读完成 · 觉得有帮助?
咨询建站