多台机器/多个备份目录里的重复文件,一个脚本按内容找出来

2026-10-06 · 本文所有代码均在真实测试数据上跑通后发布

手机、电脑、旧备份盘各存了一份照片和文档,磁盘快满了想清重复。只看文件名会误杀(同名不同内容很常见),只看大小也不够(不同文件可能恰好一样大)。正确姿势是两级过滤:文件名+大小分组,组内再算 MD5 确认内容真的一模一样。

真实测试结果

实际输出:

重复组:
  保留: d1/photo.jpg | 可删: ['d2/photo.jpg']

同名但内容不同的那份被正确留下了喵。每组的第一份按路径排序保留,删之前自己再核对一眼。

脚本

import os, hashlib, collections

def file_md5(p, chunk=65536):
    h = hashlib.md5()
    with open(p, 'rb') as f:
        for b in iter(lambda: f.read(chunk), b''):
            h.update(b)
    return h.hexdigest()

roots = ['d1', 'd2']   # 要比对的目录,可加多个
groups = collections.defaultdict(list)
for r in roots:
    for dp, _, fs in os.walk(r):
        for fn in fs:
            p = os.path.join(dp, fn)
            groups[(fn, os.path.getsize(p))].append(p)

dupes = []
for key, paths in groups.items():
    if len(paths) > 1:
        byhash = collections.defaultdict(list)
        for p in paths:
            byhash[file_md5(p)].append(p)
        for h, ps in byhash.items():
            if len(ps) > 1:
                dupes.append(ps)

for ps in dupes:
    print("保留:", ps[0], "| 可删:", ps[1:])

4 个踩过的坑

顺手来个姊妹问题:合并文件夹时重名防覆盖

把多个目录的文件搬进一个目录,a.png 会把已有的 a.png 悄悄覆盖。用"跳号"策略安全改名:

import os, shutil

def batch_rename(src_dir, dst_dir, prefix='img'):
    os.makedirs(dst_dir, exist_ok=True)
    used = set()
    for i, fn in enumerate(sorted(os.listdir(src_dir)), 1):
        p = os.path.join(src_dir, fn)
        if not os.path.isfile(p):
            continue
        stem, ext = os.path.splitext(fn)
        dst = os.path.join(dst_dir, f"{prefix}_{i:03d}{ext}")
        while dst in used or os.path.exists(dst):  # 重名跳号防覆盖
            i += 1
            dst = os.path.join(dst_dir, f"{prefix}_{i:03d}{ext}")
        used.add(dst)
        shutil.copy2(p, dst)
    return len(used)

实测 3 个文件输出 photo_001.png ~ photo_003.png,copy2 保留修改时间。

这类"扫目录、去重、批量改名"的杂活,我们也可以按你的实际目录结构代写定制(¥19.9 起),到下单页看看。虚拟物品交付后不退款;未收到或未达承诺免费补发/重做。