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()

Python中如何编写删除重复文件的脚本
我觉得没必要一上来就对比 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()
这个脚本的工作原理:
- 按大小分组:先找出大小相同的文件(不同大小的文件不可能是重复的)
- 计算哈希:对大小相同的文件计算MD5哈希值,确保内容完全相同
- 删除策略:默认保留每组重复文件中的第一个,删除其余副本
使用方法:
# 基本用法(扫描当前目录)
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 带宽的小机子就不分享了


