由于不同的出版商使用不同的“标记” PDF方法,因此需要确保在不考虑标记的情况下进行比较。
您还需要一种有效的方法来将新的PDF与所有已经下载的PDF进行比较,以防您重复下载相同的PDF,例如,按照您的建议将其标记为IP和/或日期时间戳。您不想使用费时的比较机制来将每个新PDF与许多已经下载的PDF进行比较
您需要一个实用程序,该实用程序将剥离每个可能的标记并生成剩余数据的哈希。您将需要保留一个散列→文件名映射,该映射可以在一个简单文件中,并且如果文件中已经存在计算的散列,则您有一个副本(并删除它或执行所需的任何操作),并且该散列还没有在那里,您添加哈希和文件名。该文件如下所示:
6fcb6969835d2db7742e81267437c432 /home/anthon/Downloads/explanation.pdf
fa24fed8ca824976673a51803934d6b9 /home/anthon/orders/your_order_20150320.pdf
与原始PDF相比,该文件很小。如果您有数百万个PDF,则可以考虑将这些数据存储在数据库中。为了提高效率,您可能需要在其中包含文件大小和页数(pdfinfo | egrep -E '^Pages:' | grep -Eo '[0-9]*'
)。
以上将问题推到了删除标记并生成哈希的问题。如果您在调用哈希生成例程时知道PDF的来源(即,如果您以编程方式进行下载),则可以基于此微调哈希生成。但是即使没有,也有几种生成哈希的可能性:
- 如果标题和作者的元数据是非空的,并且不包括非特定的字符串(例如“ Acrobat”或“ PDF”),则可以仅基于作者和标题信息来生成哈希。使用
pdfinfo -E file.pdf | grep -E '^(Author:)|(Title:) | md5sum
得到的哈希值。您也可以在计算哈希值时包括页面数(输出中为“ Pages:
” pdfinfo
)。
- 如果以前的规则不起作用并且PDF包含图像,则提取图像并在组合的图像数据上生成哈希。如果图像在页脚或页眉中包含“授权给Joe用户”之类的文本,请在计算哈希值之前从顶部或底部删除X线。如果该标记位于带有大字母灰色背景的文本中,则这当然将不起作用,除非您滤除了并非完全为黑色的像素(为此可以使用
imagemagick
)。您可以pdfimages
用来将图像信息提取到一个临时文件中。
- 如果以前的规则不起作用(因为没有图像),则可以
pdftext
用来提取文本,过滤掉标记(如果过滤得很少,这没问题),然后根据那。
另外,您可以比较通过散列找到的旧文件的文件大小,并查看新文件是否在一定范围内。字符串中的压缩和ifferences(IP /日期时间标记)应仅导致小于百分之一的差异。
如果您知道发布者确定哈希值时使用的方法,则可以直接应用上述方法中的“正确”方法,但是即使没有这种方法,您也可以检查元数据并应用启发式方法,或者确定文件中的图像数量并将其与页数进行比较(如果关闭的话,您可能拥有包含扫描件的文档)。pdftext
扫描图像上的PDF也具有可识别的输出。
作为工作的基础,我创建了一个位于bitbucket上的python程序包,并且/或者可以使用PyPI安装该程序包pip install ruamel.pdfdouble
。这为您提供了pdfdbl
执行如上所述的元数据,提取的图像或文本上的扫描的命令。
它还没有对标记进行任何过滤(但是),但是自述文件描述了要增强的(两个)方法来添加标记。
随附的自述文件:
ruamel.pdfdouble
该软件包提供了以下pdfdbl
命令:
pdfdbl scan dir1 dir2
这将遍历作为参数提供的目录,并为找到的PDF文件,基于(按顺序)创建哈希:
假设来自poppler-utils软件包的pdfinfo,pdfimages和pdftotext是可用的。
将建立一个“数据库”,以对其~/.config/pdfdbl/pdf.lst
进行进一步的扫描测试。
去除标记
在ruamel/pdfdouble/pdfdouble.py
其中,可以增强两种方法来过滤掉PDF中的标记,从而使它们的唯一性降低,并使几乎相同的文件具有不同的哈希值。
对于文本,PdfData.filter_for_marking
应扩展该方法以从作为其参数的字符串中删除和标记并返回结果。
对于扫描的图像,该方法PdfData.process_image_and_update
需要增强,例如,通过切除图像的底部和顶部X线,以及通过将所有黑色像素设置为白色来删除任何灰色背景文本。此函数需要使用.update()
传入过滤数据的方法来更新传入的哈希。
限制条件
当前的“数据库”无法处理包含换行符的路径
该实用程序当前仅适用于Python 2.7。
符合IP的stringparts可以替换为Python的re
模块:
import re
IPre = re.compile("(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}"
"([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])")
x = IPre.sub(' ', 'abcd 132.234.0.2 ghi')
assert x == 'abcd ghi'