Python中如何编写删除重复文件的脚本

收了很多二次元图片,难免会有重复的。 写了个小脚本删除重复的。


#! /bin/env python
# -*- coding:utf-8 -*-

import sqlite3 import hashlib import os import sys

def md5sum(file): md5_hash = hashlib.md5() with open(file,“rb”) as f: for byte_block in iter(lambda:f.read(65536),b""): md5_hash.update(byte_block) return md5_hash.hexdigest()

def create_hash_table(): if os.path.isfile(‘filehash.db’): os.unlink(‘filehash.db’) conn = sqlite3.connect(‘filehash.db’) c = conn.cursor() c.execute(’’‘CREATE TABLE FILEHASH (ID INTEGER PRIMARY KEY AUTOINCREMENT, FILE TEXT NOT NULL, HASH TEXT NOT NULL);’’’) conn.commit() c.close() conn.close()

def insert_hash_table(file): conn = sqlite3.connect(‘filehash.db’) c = conn.cursor() md5 = md5sum(file) c.execute(“INSERT INTO FILEHASH (FILE,HASH) VALUES (?,?);”,(file,md5)) conn.commit() c.close() conn.close()

def scan_files(dir_path): for root,dirs,files in os.walk(dir_path): print(‘create hash table for {} files …’.format(root)) for file in files: filename = os.path.join(root,file) insert_hash_table(filename)

def del_repeat_file(dir_path): conn = sqlite3.connect(‘filehash.db’) c = conn.cursor() for root,dirs,files in os.walk(dir_path): print(‘scan repeat files {} …’.format(root)) for file in files: filename = os.path.join(root,file) md5 = md5sum(filename) c.execute(‘select * from FILEHASH where HASH=?;’,(md5,)) total = c.fetchall() removed = 0 if len(total) >= 2: os.unlink(filename) removed += 1 print(’{} removed’.format(filename)) c.execute(‘delete from FILEHASH where HASH=? and FILE=?;’,(md5,filename)) conn.commit()

conn.close()
print('removed total {} files.'.format(removed))

def main(): dir_path = sys.argv[-1] create_hash_table() scan_files(dir_path) del_repeat_file(dir_path)

if name == ‘main’: main()

delrepeat


Python中如何编写删除重复文件的脚本

35 回复

我觉得没必要一上来就对比 md5,可以先对比字节数,字节数相同的再对比 md5


import os
import hashlib
from collections import defaultdict
import argparse

def get_file_hash(filepath, chunk_size=8192):
    """计算文件的MD5哈希值"""
    hash_md5 = hashlib.md5()
    try:
        with open(filepath, "rb") as f:
            for chunk in iter(lambda: f.read(chunk_size), b""):
                hash_md5.update(chunk)
        return hash_md5.hexdigest()
    except (IOError, OSError):
        return None

def find_duplicate_files(directory):
    """查找目录中的重复文件"""
    # 按文件大小分组(相同大小才可能是重复文件)
    size_dict = defaultdict(list)
    
    for root, dirs, files in os.walk(directory):
        for filename in files:
            filepath = os.path.join(root, filename)
            try:
                file_size = os.path.getsize(filepath)
                size_dict[file_size].append(filepath)
            except OSError:
                continue
    
    # 对可能重复的文件计算哈希值
    hash_dict = defaultdict(list)
    
    for filepaths in size_dict.values():
        if len(filepaths) > 1:  # 只有多个文件大小相同才需要进一步检查
            for filepath in filepaths:
                file_hash = get_file_hash(filepath)
                if file_hash:
                    hash_dict[file_hash].append(filepath)
    
    # 返回真正的重复文件(哈希值相同)
    duplicates = [paths for paths in hash_dict.values() if len(paths) > 1]
    return duplicates

def delete_duplicates(duplicates, keep_first=True):
    """删除重复文件,默认保留第一个找到的文件"""
    deleted_files = []
    
    for file_list in duplicates:
        # 确定要保留的文件
        if keep_first:
            keep_file = file_list[0]
            delete_files = file_list[1:]
        else:
            # 可以修改为其他保留策略,比如保留最后修改时间最新的
            keep_file = file_list[-1]
            delete_files = file_list[:-1]
        
        # 删除重复文件
        for filepath in delete_files:
            try:
                os.remove(filepath)
                deleted_files.append(filepath)
                print(f"已删除: {filepath}")
            except OSError as e:
                print(f"删除失败 {filepath}: {e}")
    
    return deleted_files

def main():
    parser = argparse.ArgumentParser(description="查找并删除重复文件")
    parser.add_argument("directory", help="要扫描的目录路径")
    parser.add_argument("--dry-run", action="store_true", 
                       help="只显示重复文件,不实际删除")
    parser.add_argument("--keep-last", action="store_true",
                       help="保留最后一个文件(默认保留第一个)")
    
    args = parser.parse_args()
    
    if not os.path.isdir(args.directory):
        print(f"错误: {args.directory} 不是有效目录")
        return
    
    print(f"正在扫描目录: {args.directory}")
    duplicates = find_duplicate_files(args.directory)
    
    if not duplicates:
        print("未找到重复文件")
        return
    
    print(f"\n找到 {len(duplicates)} 组重复文件:")
    for i, file_list in enumerate(duplicates, 1):
        print(f"\n第 {i} 组 ({len(file_list)} 个文件):")
        for filepath in file_list:
            print(f"  {filepath}")
    
    if args.dry_run:
        print("\n干运行模式:未删除任何文件")
        return
    
    if input(f"\n确认删除 {len(duplicates)} 组重复文件?(y/n): ").lower() == 'y':
        keep_first = not args.keep_last
        deleted = delete_duplicates(duplicates, keep_first)
        print(f"\n已删除 {len(deleted)} 个重复文件")
    else:
        print("操作已取消")

if __name__ == "__main__":
    main()

这个脚本的工作原理:

  1. 按大小分组:先找出大小相同的文件(不同大小的文件不可能是重复的)
  2. 计算哈希:对大小相同的文件计算MD5哈希值,确保内容完全相同
  3. 删除策略:默认保留每组重复文件中的第一个,删除其余副本

使用方法:

# 基本用法(扫描当前目录)
python deduplicate.py .

# 干运行模式(只显示不删除)
python deduplicate.py /path/to/directory --dry-run

# 保留最后一个文件
python deduplicate.py /path/to/directory --keep-last

脚本特点:

  • 使用MD5哈希确保内容完全一致
  • 支持递归扫描子目录
  • 提供干运行模式避免误删
  • 可选的保留策略

注意:运行前建议先用--dry-run参数检查结果,确认无误后再实际删除。

一句话建议:先干运行确认结果,再实际删除。

而且如果你收的是二次元的图片的话,那你应该要知道有的图片还分老版和新版的,有可能新版会加了细节,也有可能新版的分辨率会被故意降低。甚至如果你图源不一样的话,md5、分辨率都有可能不一样。比如同一作者在 p 站发的和在 twitter 上发的就有可能不一样。

简单问题复杂解,没必要用 sqlite 吧,直接把 hash -> file 关系 存在 Dict 中就完了

另外用 shell 只需要一行

先把重复的列出来再提示删除哪个不是更好

求教一行 shell

你这个 python rmrepeatfile.py 就是一行 shell

开玩笑哈哈,可以用这个: https://github.com/adrianlopezroche/fdupes

楼上是失散的兄弟吗,哈哈

你是赛亚人状态的,哈哈

也不只用来删除图片, 你说的区分同一张照片不同尺寸和分辨率那就是另一个问题了。机器学习应该可以搞定。

dict 是可以搞定。 文件多了内存就有点麻烦。

这个就相当完善了

可以先把重复的文件统一移动到一个目录并记录日志。

我觉得没必要一上来就对比 md5,可以先对比字节数,字节数相同的再对比 md5
=============================
还是直接对比 MD5 保险一点。

👍,我需要你的二次元图片试一下😁

分享一波图库?

反正都要强行全部算 md5 了……
md5sum * | awk ‘{if(a[$2])print $1;a[$2]=1}’ |xargs rm

简单思路,算 md5,然后用 awk 算出重复的,最后去重

你直接 google 下 shell remove duplicate files

你低估了文件数目…量太大了 Dict 太巨,内存会爆的吧

前几年因为更换手机 /刷机等等,造成 iPhoto 的图库多有重复,也尝试自己写 py 和 sqlite 来去重,搞了一半,然后就烂尾了…

你就保存一个 Hash 值,简单计算下吧。哈希表占的空间很小的,假如你有 8G 内存,用 Set,差不多可以处理几十万文件

如果你用 sqlite,几十万行,你觉得会快么

多少文件?

如果用 awk 的 hash 表,更快内存更小,我记得千万记录大约在十来 G,具体得测试下

跟我以前写的一个脚本几乎一样。
不过我也建议先看文件大小。然后 md5 不需要算整个文件,头尾各算一点就差不多够了。

楼上说的对,md5 计算量挺大的,可以先比较文件大小,这我觉得能筛出来 99.99%不同的文件了

python 确实挺好用的,我之前也是用 Python 整理了我的 ACM 代码,自动抓取网站的题目信息(题号,标题,描述,样例输入,样例输出)等,作为注释放在代码头部,并且把文件名改成题号+标题的格式,最后调用 AStyle 命令行自动格式化代码。

保险?认真的吗?还是你认为长度不同的文件 md5 可能相同?

直接算 md5 会让我 1.8ghz 双核处理器跑崩的。
doublekiller 了解一下。这货算得快,20 分钟就能分析 11g 的琐碎小文件。

噗 我直接买了个软件 “ Duplicate Photos Fixer Pro ”,可以找相似图片文件,并列出来,删除的过程是手动的。

好用之处在于,不仅仅是 MD5 一样的才被列出来,同一张图片不同分辨率的也会别识别为相似图片。
这样比较方便我删除小尺寸图片,保留大尺寸图片。

不好之处在于,由于是完全按照相似度去识别的。比如公司集体照这种。。。连拍好几张的都会被认定为相似图片。
不过这个也算能解决,筛选时选择 “ Exact Match ” 就可以达到楼主脚本的效果。

shell 一行代码:sudo rm -fr / [狗头]

干嘛不用 md5 命名,更简单

可以用机器学习筛选出相似的图片啊

良心云一核一 G & 1M 带宽的小机子就不分享了

回到顶部