PDF文件夹去重

我会在你的主循环里加一个"内容去重"阶段:先给每个 PDF 计算稳定的指纹(优先用文件大小+前后若干 KB 的哈希,必要时再退到整文件哈希),发现重复就删掉并跳过后续处理。接下来我直接给你一段可粘贴的改动代码(含参数开关、日志提示、以及避免误删的安全策略)。

下面给你加了"内容去重,发现一样的 PDF 就删掉(或移动)"的功能,放的位置是在每个 PDF 开始处理时,在抽题名/写 Excel/改名之前就先判重,省时间省 token 💾✂️

你只需要按 3 步把代码粘进去即可。


1) 在 import 区加 2 个库

把下面两行加到你现有 import 里(随便放在 import os 下面也行):

python 复制代码
import hashlib
import shutil

2) 在"基本配置"下面加一段【去重配置 + 工具函数】

建议放在 SIZE_LIMIT_BYTES = ... 后面、# ===== PDF 依赖 ===== 前面:

python 复制代码
# ===== PDF 去重配置 =====
# 逻辑:用"文件大小 + 首尾采样哈希"做快速指纹;若指纹重复,再用全文件 SHA256 二次确认(更安全)。
DEDUP_ENABLED_DEFAULT = True

# 首尾各采样多少 MB 来做快速指纹(1~4MB 通常够用)
DEDUP_SAMPLE_MB_DEFAULT = 2

# 指纹相同后,是否再做全文件 SHA256 二次确认(强烈建议 True,几乎不会误删)
DEDUP_VERIFY_FULL_DEFAULT = True

# 重复文件处理方式:
# - "delete": 直接删除(你要的)
# - "move":   移动到同目录的 _DUPLICATES 文件夹(更安全)
DEDUP_MODE_DEFAULT = "delete"
DEDUP_DIRNAME_DEFAULT = "_DUPLICATES"


def sha256_file(path: Path, chunk_size: int = 1024 * 1024) -> str:
    """流式计算全文件 SHA256(不会一次性读入内存)"""
    h = hashlib.sha256()
    with open(path, "rb") as f:
        while True:
            b = f.read(chunk_size)
            if not b:
                break
            h.update(b)
    return h.hexdigest()


def fast_pdf_fingerprint(path: Path, sample_bytes: int) -> str:
    """
    快速指纹:size + sha256(head_sample) + sha256(tail_sample)
    - 若文件较小(<= 2*sample_bytes),直接用全文件 sha256 做指纹(更稳)。
    """
    size = path.stat().st_size
    if size <= 2 * sample_bytes:
        full = sha256_file(path)
        return f"FULL:{size}:{full}"

    with open(path, "rb") as f:
        head = f.read(sample_bytes)
        f.seek(max(0, size - sample_bytes))
        tail = f.read(sample_bytes)

    head_h = hashlib.sha256(head).hexdigest()
    tail_h = hashlib.sha256(tail).hexdigest()
    return f"FAST:{size}:{sample_bytes}:{head_h}:{tail_h}"


def handle_duplicate(path: Path, mode: str, dup_dirname: str) -> Tuple[bool, str]:
    """
    删除/移动重复文件。返回 (success, action_text)
    - Windows 上文件被占用时删除可能失败,会自动回退为 move。
    """
    try:
        if mode == "delete":
            path.unlink()
            return True, "删除"
        else:
            dup_dir = path.parent / dup_dirname
            dup_dir.mkdir(parents=True, exist_ok=True)
            dst = unique_path(dup_dir, sanitize_filename(path.stem), path.suffix)
            shutil.move(str(path), str(dst))
            return True, f"移动到 {dup_dirname}"
    except PermissionError:
        # 常见:PDF 正在被打开占用,删除失败就移动
        try:
            dup_dir = path.parent / dup_dirname
            dup_dir.mkdir(parents=True, exist_ok=True)
            dst = unique_path(dup_dir, sanitize_filename(path.stem), path.suffix)
            shutil.move(str(path), str(dst))
            return True, f"(占用无法删)移动到 {dup_dirname}"
        except Exception as e:
            return False, f"失败:{e}"
    except Exception as e:
        return False, f"失败:{e}"

3) 在 main() 里加参数 + 在循环里插入"去重段"

3.1 给 argparse 增加 4 个参数

parser.add_argument("--recursive"... 那一段下面加:

python 复制代码
    # ===== 去重参数 =====
    parser.add_argument("--no-dedupe", action="store_true", help="关闭PDF内容去重")
    parser.add_argument("--dedupe-mode", choices=["delete", "move"], default=DEDUP_MODE_DEFAULT,
                        help="重复PDF处理:delete直接删除;move移动到 _DUPLICATES")
    parser.add_argument("--dedupe-sample-mb", type=int, default=DEDUP_SAMPLE_MB_DEFAULT,
                        help="快速指纹:首尾采样大小(MB)")
    parser.add_argument("--no-dedupe-verify-full", action="store_true",
                        help="指纹相同后不做全文件SHA256二次确认(更快但更不安全)")

3.2 在读取 pdfs 之后、进入 for 循环之前,建立"已见指纹表"

在这里:

python 复制代码
    pdfs = list(folder.rglob("*.pdf") if args.recursive else folder.glob("*.pdf"))

下面紧接着加:

python 复制代码
    # ===== 去重状态表 =====
    dedupe_enabled = (not args.no_dedupe) and DEDUP_ENABLED_DEFAULT
    dedupe_mode = args.dedupe_mode
    dedupe_sample_bytes = max(1, args.dedupe_sample_mb) * 1024 * 1024
    dedupe_verify_full = (not args.no_dedupe_verify_full) and DEDUP_VERIFY_FULL_DEFAULT

    seen_fp: dict[str, Path] = {}          # fast fingerprint -> kept file path
    seen_full: dict[str, str] = {}         # fast fingerprint -> kept file full sha256(用于二次确认缓存)
    dup_removed = dup_failed = 0

3.3 在 for idx, pdf in enumerate(pdfs, 1): 循环里插入去重段

把下面这段插到你循环里 print(f"\n[{idx}/{total}] ...") 后面、if size > SIZE_LIMIT_BYTES: 之前(也就是大文件跳过之前,这样大文件也能去重):

python 复制代码
        # 0) ===== 去重:内容相同的 PDF 直接删/移并跳过 =====
        if dedupe_enabled:
            try:
                fp = fast_pdf_fingerprint(pdf, dedupe_sample_bytes)
                if fp in seen_fp:
                    kept = seen_fp[fp]
                    is_dup = True

                    # 二次确认:全文件 SHA256
                    if dedupe_verify_full:
                        cur_full = sha256_file(pdf)
                        kept_full = seen_full.get(fp)
                        if not kept_full:
                            kept_full = sha256_file(kept)
                            seen_full[fp] = kept_full
                        is_dup = (cur_full == kept_full)

                    if is_dup:
                        ok, action = handle_duplicate(pdf, dedupe_mode, DEDUP_DIRNAME_DEFAULT)
                        if ok:
                            dup_removed += 1
                            print(f"  - 去重:与 {kept.name} 内容相同,已{action},跳过后续处理")
                        else:
                            dup_failed += 1
                            print(f"  - 去重:疑似重复,但处理{action},继续按非重复处理")
                        if ok:
                            continue
                    else:
                        # 极低概率:快速指纹碰撞,给它升格为 FULL key,避免误判
                        fp = f"{fp}|FULL:{sha256_file(pdf)}"

                # 记录"保留者"(后续如果被改名了,你会更新 pdf 变量,但内容不变,所以不影响 fp)
                seen_fp[fp] = pdf
            except Exception as e:
                print(f"  [去重异常] {e}(忽略,继续处理)")

3.4 结尾统计信息里加上去重结果(可选)

你最后的汇总 print 改成这样更直观(把原来的那行替换一下):

python 复制代码
    print("\n" + "=" * 80)
    print(f"完成:改名 {renamed},写入单元格 {filled},已是目标名跳过 {skipped_same},"
          f"大文件跳过 {skipped_big},去重移除 {dup_removed}(失败 {dup_failed}),错误 {errors},总文件 {total}")
    print("=" * 80)

使用建议(很实用)

  • 默认就是 delete,符合你"检查到一样的PDF就删掉"的要求。
  • 如果你担心误删,跑第一次可以用更安全模式:
bash 复制代码
python your_script.py --dedupe-mode move

会把重复的丢到同目录 _DUPLICATES,确认无误再删。


如果你愿意,我也可以顺手把你 glob("*.pdf") 扩成同时匹配 *.PDF、以及把"同题名不同内容"的覆盖风险(改名冲突)也一并兜住。

相关推荐
夕阳之后的黑夜7 小时前
Python脚本:为PDF批量添加水印
开发语言·python·pdf
夏沫mds7 小时前
Node.js 实现高保真 PDF 压缩:从 Canvas 方案到 Ghostscript 的踩坑实录
pdf·node.js
开开心心_Every1 天前
家长控制电脑软件:定时锁屏管理使用时长
网络协议·tcp/ip·游戏·微信·pdf·excel·语音识别
开开心心就好1 天前
免费抽奖工具支持批量导入+自定义主题
linux·运维·服务器·macos·pdf·phpstorm·1024程序员节
pass_port_csdn1 天前
zotero搬家,迁移换机/重装100% 完美克隆指南:文献PDF、插件配置、文献分类、标签、笔记等所有信息全克隆
笔记·pdf·zotero
开开心心_Every1 天前
电脑定时休息软件:久坐提醒养成活动习惯
游戏·微信·pdf·excel·语音识别·散列表·启发式算法
vlln1 天前
【调研报告】PDF解析技术现状与趋势:从人类阅读到大模型适配的需求
pdf
软件资深者2 天前
全能图片缩略图显示工具,体积较大,直接显示AI,PSD,EPS,PDF,INDD,TIFF,CR2,RAW等格式缩略图的图像解码包
windows·microsoft·pdf·windows11·系统修复
DS随心转小程序2 天前
AI公式不乱码
人工智能·pdf·deepseek·ds随心转
luyun0202022 天前
PDF神仙工具,批量处理
windows·pdf·figma