Python 通过hashlib查找重复文件?
我知道以前有人问过这个问题,我也看到了一些答案,但这个问题更多的是关于我的代码和完成这项任务的最佳方式 我想扫描一个目录,看看该目录中是否有任何重复项(通过检查MD5哈希)。以下是我的代码:Python 通过hashlib查找重复文件?,python,file,duplicates,hashlib,Python,File,Duplicates,Hashlib,我知道以前有人问过这个问题,我也看到了一些答案,但这个问题更多的是关于我的代码和完成这项任务的最佳方式 我想扫描一个目录,看看该目录中是否有任何重复项(通过检查MD5哈希)。以下是我的代码: import sys import os import hashlib fileSliceLimitation = 5000000 #bytes # if the file is big, slice trick to avoid to load the whole file into RAM def
import sys
import os
import hashlib
fileSliceLimitation = 5000000 #bytes
# if the file is big, slice trick to avoid to load the whole file into RAM
def getFileHashMD5(filename):
retval = 0;
filesize = os.path.getsize(filename)
if filesize > fileSliceLimitation:
with open(filename, 'rb') as fh:
m = hashlib.md5()
while True:
data = fh.read(8192)
if not data:
break
m.update(data)
retval = m.hexdigest()
else:
retval = hashlib.md5(open(filename, 'rb').read()).hexdigest()
return retval
searchdirpath = raw_input("Type directory you wish to search: ")
print ""
print ""
text_file = open('outPut.txt', 'w')
for dirname, dirnames, filenames in os.walk(searchdirpath):
# print path to all filenames.
for filename in filenames:
fullname = os.path.join(dirname, filename)
h_md5 = getFileHashMD5 (fullname)
print h_md5 + " " + fullname
text_file.write("\n" + h_md5 + " " + fullname)
# close txt file
text_file.close()
print "\n\n\nReading outPut:"
text_file = open('outPut.txt', 'r')
myListOfHashes = text_file.read()
if h_md5 in myListOfHashes:
print 'Match: ' + " " + fullname
这为我提供了以下输出:
Please type in directory you wish to search using above syntax: /Users/bubble/Desktop/aF
033808bb457f622b05096c2f7699857v /Users/bubble/Desktop/aF/.DS_Store
409d8c1727960fddb7c8b915a76ebd35 /Users/bubble/Desktop/aF/script copy.py
409d8c1727960fddb7c8b915a76ebd25 /Users/bubble/Desktop/aF/script.py
e9289295caefef66eaf3a4dffc4fe11c /Users/bubble/Desktop/aF/simpsons.mov
Reading outPut:
Match: /Users/bubble/Desktop/aF/simpsons.mov
我的想法是:
1) 扫描目录
2) 将MD5哈希+文件名写入文本文件
3) 以只读方式打开文本文件
4) 再次扫描目录并检查文本文件
我知道这不是一个好的方法,也不管用。“匹配”只是打印出最后处理的文件
我怎样才能让这个脚本真正找到重复的脚本?有人能告诉我一个更好/更容易完成这项任务的方法吗
非常感谢你的帮助。抱歉,这是一篇很长的文章。您要做的第一件事是在循环浏览文件时将h_md5保存到列表中。 比如:
h_md5=[]
在循环浏览目录之前。及
h_md5.append(getFileHashMD5(fullname))
在你的循环中。现在,您有了一个哈希列表,可以与输出文件进行比较,而不仅仅是在循环中创建的最后一个哈希
而且,很明显,使用当前代码,每次都会为每个文件找到一个匹配项,因为您将在列表中找到该特定文件本身的哈希。因此,如果您想查找重复项,您必须查找找到两个不同匹配项的实例
编辑:如果您愿意更改代码,@senderle上面的答案是一种更好的方法。识别重复项的明显工具是哈希表。除非处理大量文件,否则可以执行以下操作:
from collections import defaultdict
file_dict = defaultdict(list)
for filename in files:
file_dict[get_file_hash(filename)].append(filename)
for duplicates in file_dict.values(): # file_dict.itervalues() in Python 2
if len(duplicates) > 1:
# double-check reported duplicates and generate output
在这个过程结束时,file_dict
将包含每个唯一散列的列表;当两个文件具有相同的哈希值时,它们都将出现在该哈希值的列表中。然后过滤dict以查找长度大于1的值列表,并比较这些文件以确保它们是相同的,如下所示:
from collections import defaultdict
file_dict = defaultdict(list)
for filename in files:
file_dict[get_file_hash(filename)].append(filename)
for duplicates in file_dict.values(): # file_dict.itervalues() in Python 2
if len(duplicates) > 1:
# double-check reported duplicates and generate output
或者这个:
duplicates = [files for files in file_dict.values() if len(files) > 1]
get\u file\u hash
可以使用MD5s;或者它可以简单地获得文件的第一个和最后一个字节,正如Ramchandra Apte在上面的评论中所建议的那样;或者它可以简单地使用tdelaney在上面的评论中建议的文件大小。但后两种策略中的每一种都更有可能产生误报。你可以把它们结合起来以降低假阳性率
如果您正在处理大量的文件,那么您可以使用更复杂的数据结构,例如 @senderle有一个很好的答案,但既然他提到我的解决方案会产生误报,我想挑战已经打好了,我最好拿出一些代码。我精简了md5函数(它应该总是使用“fileslicelimition”的情况,并且输入缓冲区应该不那么吝啬),然后在执行md5之前按大小进行预过滤
import sys
import os
import hashlib
from collections import defaultdict
searchdirpath = sys.argv[1]
size_map = defaultdict(list)
def getFileHashMD5(filename):
m = hashlib.md5()
with open(filename, 'rb', 1024*1024) as fh:
while True:
data = fh.read(1024*1024)
if not data:
break
m.update(data)
return m.hexdigest()
# group files by size
for dirname, dirnames, filenames in os.walk(searchdirpath):
for filename in filenames:
fullname = os.path.join(dirname, filename)
size_map[os.stat(fullname).st_size].append(fullname)
# scan files of same size
for fullnames in size_map.itervalues():
if len(fullnames) > 0:
hash_map = defaultdict(list)
for fullname in fullnames:
hash_map[getFileHashMD5(fullname)].append(fullname)
for fullnames in hash_map.itervalues():
if len(fullnames) > 1:
print "duplicates:"
for fullname in fullnames:
print " ", fullname
(编辑)
关于此实现,我将在这里尝试回答几个问题:
1) 为什么(1024*1024)大小不是“5000000”
您的原始代码以8192(8kib)的增量读取,这对于现代系统来说非常小。通过一次抓取更多,您可能会获得更好的性能。1024*1024是1048576(1 MiB)字节,只是对一个合理数字的猜测。至于我为什么用如此奇怪的方式写它,1000(十进制千字节)为人们所喜爱,而1024(二进制千字节)为计算机和文件系统所喜爱。我习惯于写一些_number*1024,所以很容易看出我指的是1kib的增量。5000000也是一个合理的数字,但是你应该考虑5×1024×1024(即5 MIB),这样你就可以得到与文件系统很好对齐的东西。
2) 该位具体做什么:size\u map=defaultdict(list)
它创建一个“defaultdict”,为常规dict对象添加功能。当常规dict被不存在的键索引时,它会引发KeyError异常。defaultdict创建一个默认值,并将该键/值对添加到dict中。在我们的例子中,size\u map[some\u size]
说“给我一些大小的文件列表,如果没有,创建一个新的空列表”
size\u映射[os.stat(fullname).st\u size].append(fullname)
。这可细分为:
stat = os.stat(fullname)
size = stat.st_size
filelist = size_map[size] # this is the same as:
# if size not in size_map:
# size_map[size] = list()
# filelist = size_map[size]
filelist.append(fullname)
3) sys.argv[1]我猜sys.argv[1]只是让python py.py'filepath'参数起作用(其中filepath是argv[1])
是的,当您调用python脚本时,sys.argv[0]是脚本的名称,sys.argv[1:](arg 1及以下)是命令行中给出的任何附加参数。我使用了sys.argv[1]当我编写脚本时,这是一种快速测试脚本的方法,您应该更改它以满足您的需要。事实上,比较MD5实际上比逐字节比较文件慢(每个文件字节1个周期)。我非常确定MD5每个字节需要一个以上的周期。我建议您只需从文件中读取固定数量的字节并进行比较,这将更简单、更快。@RamchandraApte,但将每个文件与其他每个文件进行比较会很慢。使用MD5可以帮助避免这种情况。@senderle您是对的。但简单地比较fir可能会更快每个文件的st几个字节和最后几个字节,如果匹配,则比较整个文件。要真正提高速度,请首先按文件大小对文件进行分组,然后仅在组内进行比较。除非有奇怪的原因导致文件大小相同,否则大多数文件将具有唯一的文件大小,并且永远不需要扫描。是否需要添加该into我的代码?或者重新编写我的代码以使其正常工作?@BubbleMonster,以上内容将接近于替换代码中文件名为
的块。(当然,您必须将文件
更改为文件名
,并将from…import…
语句放在模块顶部。)