Python 通过hashlib查找重复文件?

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

我知道以前有人问过这个问题,我也看到了一些答案,但这个问题更多的是关于我的代码和完成这项任务的最佳方式

我想扫描一个目录,看看该目录中是否有任何重复项(通过检查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 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…
语句放在模块顶部。)