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、以及把"同题名不同内容"的覆盖风险(改名冲突)也一并兜住。

相关推荐
ComPDFKit17 分钟前
ComPDF 与 Aspose:转换 SDK 的全面比较
pdf
优选资源分享1 小时前
PDF 电子签章工具 v5.0:全能处理PDF电子签章
pdf
Arvin_Zhang20161 小时前
使用python实现从PDF格式的control mapping获取gross die数量
python·pdf
徐同保1 小时前
上传文件,在前端用 pdf.js 提取 上传的pdf文件中的图片
前端·javascript·pdf
CodeCraft Studio1 小时前
国产化PDF处理控件Spire.PDF教程:使用Python批量自动化将PDF转换为黑白(灰度)
python·pdf·自动化·spire.pdf·文档自动化·pdf开发组件·国产化文档组件
成旭先生2 小时前
文档(如word、ppt、pdf等)在线预览解决方案:基于HTML转换的技术实践与对比
pdf·word·powerpoint
m5655bj2 小时前
使用 C# 将 RTF 文档转换为 PDF 格式
pdf·c#
开开心心_Every20 小时前
免费进销存管理软件:云端本地双部署
java·游戏·微信·eclipse·pdf·excel·语音识别
winfredzhang1 天前
从零构建:手写一个支持“高度定制化排版”的 Chrome 网页摘录插件
chrome·pdf·插件·epub·零碎信息归档
裴嘉靖1 天前
前端获取二进制文件并预览的完整指南
前端·pdf