
在日常办公、数据处理和运维工作中我们经常会遇到这样一类需求把散落在多个文件夹甚至多层子文件夹里的文件统一提取到同一个目录里方便后续检索、分析和归档。比如收集多个项目组提交的报表、把不同日期导出的 Excel 汇总到一起、或者把分散在素材库各层目录里的图片集中管理。这种操作如果纯靠手动一个个复制粘贴不仅效率低还容易漏文件、覆盖同名文件尤其在文件数量达到几百上千时几乎不可接受。本文将围绕“多个文件夹文件汇总包括子文件夹”这个需求用 Python 写一个通用文件汇总脚本完整覆盖目录递归遍历、文件扩展名过滤、重名冲突处理、是否保留目录结构、日志记录等核心问题。文章会从基础概念讲起再逐步扩展到完整可运行的脚本最后给出常见问题和工程建议。无论是新手还是有一定开发经验的读者都可以直接复用。1. 什么是“多个文件夹文件汇总”1.1 需求场景分析文件汇总本质上是一个“多对一”的文件整理过程有多个源文件夹每个源文件夹内部可能还有多层子文件夹我们需要把所有符合条件的文件从一个或几个根目录中提取出来集中放到一个目标文件夹里。常见的业务场景包括数据收集从多个参与人提交的文件夹中提取所有 Word、PDF 文档到统一目录。日志归档把分布在多个服务日志目录中的 .log 文件集中到一个目录。素材整理把多层目录结构中的图片、图标、音频等资源统一收集。备份迁移把散落在移动硬盘不同文件夹中的照片复制到一个“待整理”目录中。这些场景的共同特点是源目录结构复杂、文件类型繁杂、文件数量大而且随时可能出现重名文件。1.2 手动操作与脚本方案对比手动操作的优势是灵活但缺点非常明显容易漏掉深层子目录里的文件。同名文件覆盖风险高。大批量文件复制时耗时费力。无法重复执行下一次整理又要从头再来。脚本方案的优势在于可重复、可追溯、可过滤、可自动化。我们用 Python 写一次脚本后以后每次遇到类似需求只需要修改路径和参数就能复用。更重要的是脚本可以提前用“试运行模式”列出将要执行的操作避免误操作。1.3 核心术语界定下面先统一几个概念后文都会用到源文件夹需要被遍历和提取文件的目录可以同时指定多个。子文件夹源文件夹内部的任意层级目录。目标文件夹汇总后文件存放的地方。递归遍历从根目录开始一层一层进入所有子目录并处理其中的文件。重名冲突不同源目录中存在相同文件名的文件复制到同一目标目录时会发生冲突。理解了这些基础概念后面的原理拆解和代码实现就更容易看懂了。2. 环境准备与测试素材2.1 运行环境说明本文的脚本基于 Python 3 编写建议使用 3.8 及以上版本。脚本只依赖 Python 标准库不需要安装第三方包因此只要电脑上安装了 PythonWindows、macOS、Linux 都可以直接运行。可以用下面命令检查 Python 版本python --version如果没有安装 Python可以到官网下载安装包。安装时记得勾选“Add Python to PATH”否则命令行里找不到 python 命令。本文示例以常见环境为例重点演示脚本思路和运行方式。实际使用中如果你的 Python 版本或操作系统有所不同只要路径写法正确脚本本身是通用的。2.2 准备测试目录为了验证脚本效果我们先手动创建一个测试用的目录结构。这里以 Windows 的 D 盘为例实际使用时可替换成自己的路径。D:/data/source1/ 2024-01/ 销售报表.xlsx 会议纪要.docx 2024-02/ 销售报表.xlsx 图片素材/ 产品图.png D:/data/source2/ 汇总表.xlsx 产品图.png 临时文件.tmp 2024-01/ 周报.xlsx在这个测试结构中两个源目录都包含子文件夹而且存在同名文件“销售报表.xlsx”和“产品图.png”。这些重名文件正好用来验证脚本的冲突处理功能。3. 核心原理拆解遍历、复制与冲突处理在动手写脚本之前先把实现原理讲清楚。文件汇总脚本的核心只有三件事遍历文件、复制文件、处理冲突。3.1 遍历目录的两种常见方式Python 遍历目录最常见的是os.walk另一种更简洁的是pathlib.Path.rglob。os.walk返回一个三元组分别是当前目录路径、子目录列表、文件列表。需要我们自己写循环去处理层级关系。pathlib.rglob(*)则更贴近人类思维它直接递归匹配所有子目录下的路径再用is_file()判断是否为文件即可。下面是最小示例from pathlib import Path root Path(D:/data/source1) for p in root.rglob(*): if p.is_file(): print(p)这段代码会递归打印 source1 下的所有文件路径包括子文件夹中的文件。为什么不建议只用os.listdir因为os.listdir只列出一层目录不会进入子文件夹拿它做递归需要自己维护栈或使用递归函数代码会相对啰嗦。3.2 复制、移动与硬链接的选择汇总文件时可以选择“复制”或“移动”两种方式。复制源文件保留目标目录生成一份副本。安全性高适合整理和归档。移动源文件被移走原始位置不再保留。适合腾空间但操作不可逆风险较高。硬链接在某些场景下可以节省空间但文件系统限制多不推荐新手使用。本文脚本默认使用shutil.copy2因为copy2会尽量保留文件的修改时间和元数据比copyfile更适合文档归档。如果你确定要移动文件可以把复制函数替换成shutil.move但前提是已充分确认不会误删数据。3.3 重名冲突处理策略同名文件是文件汇总里最核心的问题。两个不同文件夹中完全可能有同名文件如果直接复制到同一目标目录后者会覆盖前者造成数据丢失。常用冲突策略有三种策略行为适用场景rename自动重命名后复制例如“销售报表_1.xlsx”默认推荐保留全部文件overwrite后复制的内容覆盖已有文件明确知道同名文件内容相同或不需要保留旧版skip遇到同名文件直接跳过只需要一份且以第一次复制为准“重命名”策略更安全能在不丢失任何文件的前提下完成汇总。后文完整脚本中会针对这三种策略分别实现。3.4 过滤与保留目录结构汇总时通常不需要把所有文件都复制过去因此脚本应支持按扩展名过滤。例如只复制.xlsx、.docx、.png文件忽略.tmp临时文件。保留目录结构也有两种模式不保留所有文件直接铺开放到目标目录。优点是目录扁平查看方便缺点是重名概率高。保留以“源文件夹中的相对路径”作为目标目录的子路径。这样既能汇总文件又能避免大部分重名冲突。具体选择哪种模式取决于用途。如果是做素材库汇总建议不保留结构靠重命名策略解决冲突如果需要保留项目之间的隔离关系推荐保留相对目录结构。4. 完整实战编写文件汇总脚本下面进入实战环节。我们会从最小案例写起逐步扩展成一个带命令行参数、日志和试运行模式的生产级脚本。4.1 最小案例递归打印所有文件先写一个最简脚本确认目录遍历逻辑没有问题from pathlib import Path source_list [ Path(D:/data/source1), Path(D:/data/source2), ] for source in source_list: print(f当前目录{source}) for file_path in source.rglob(*): if file_path.is_file(): print( -, file_path)运行结果大致如下当前目录D:\data\source1 - D:\data\source1\2024-01\销售报表.xlsx - D:\data\source1\2024-01\会议纪要.docx - D:\data\source1\2024-02\销售报表.xlsx - D:\data\source1\2024-02\图片素材\产品图.png 当前目录D:\data\source2 - D:\data\source2\汇总表.xlsx - D:\data\source2\产品图.png ...这个案例虽然简单但是把目录递归的核心逻辑演示清楚了。4.2 基础案例复制文件并处理重名接着写一个真正能汇总文件的脚本。它会遍历所有源文件夹把所有文件复制到目标目录遇到重名自动加序号from pathlib import Path import shutil source_list [ Path(D:/data/source1), Path(D:/data/source2), ] target_dir Path(D:/data/merged) target_dir.mkdir(parentsTrue, exist_okTrue) def unique_dest(dest: Path) - Path: if not dest.exists(): return dest idx 1 while True: new_dest dest.parent / f{dest.stem}_{idx}{dest.suffix} if not new_dest.exists(): return new_dest idx 1 for source in source_list: for file_path in source.rglob(*): if not file_path.is_file(): continue dest unique_dest(target_dir / file_path.name) print(f复制{file_path} - {dest}) shutil.copy2(file_path, dest)这里用target_dir / file_path.name把文件名拼到目标目录下再通过unique_dest检查重名。很多需求到这个阶段就已经可以用了但还有几个问题没有扩展名过滤临时文件也会被复制。没有试运行模式一旦执行就真的复制文件。没有日志输出到文件操作过程不方便审计。所以还需要一个更完整的版本。4.3 增强案例保留目录结构如果希望汇总后的目录保留原有的相对结构关键操作是用relative_to计算文件相对源目录的路径。from pathlib import Path import shutil source_dir Path(D:/data/source1) target_dir Path(D:/data/merged) for file_path in source_dir.rglob(*): if not file_path.is_file(): continue rel_path file_path.relative_to(source_dir) dest target_dir / rel_path dest.parent.mkdir(parentsTrue, exist_okTrue) print(f复制{file_path} - {dest}) shutil.copy2(file_path, dest)这段代码会把D:/data/source1/2024-01/销售报表.xlsx复制为D:/data/merged/2024-01/销售报表.xlsx源目录内部结构被完整保留。如果担心不同根目录下出现相同相对路径可以再把源目录名称拼到目标路径前面例如D:/data/merged/source1/2024-01/销售报表.xlsx。4.4 生产级脚本完整命令行版本现在把前面的思路整合成一个完整的脚本支持多个源目录、扩展名过滤、冲突策略、保结构模式、试运行模式和日志记录。建议把脚本保存为file_merge.py#!/usr/bin/env python3 # -*- coding: utf-8 -*- 文件汇总脚本将多个文件夹含子文件夹中的文件汇总到目标目录 用法示例 python file_merge.py D:/data/source1 D:/data/source2 -t D:/data/merged python file_merge.py D:/data/source1 -t D:/data/merged --exts .xlsx .docx python file_merge.py D:/data/source1 -t D:/data/merged --keep-structure python file_merge.py D:/data/source1 -t D:/data/merged --dry-run import argparse import shutil from pathlib import Path def parse_args(): parser argparse.ArgumentParser(description汇总多个文件夹中的文件到目标目录) parser.add_argument(sources, nargs, help一个或多个源文件夹路径) parser.add_argument(--target, -t, requiredTrue, help目标文件夹路径) parser.add_argument(--exts, nargs*, defaultNone, help只复制指定扩展名例如 .txt .xlsx) parser.add_argument(--keep-structure, actionstore_true, help保留源目录中的相对子目录结构) parser.add_argument(--conflict, choices[rename, overwrite, skip], defaultrename, help同名文件处理策略默认 rename) parser.add_argument(--dry-run, actionstore_true, help只打印将要执行的操作不真正复制文件) parser.add_argument(--log, defaultNone, help日志文件路径) return parser.parse_args() def match_ext(file_path: Path, exts): if exts is None: return True normalized set() for ext in exts: ext ext.strip() if not ext.startswith(.): ext . ext normalized.add(ext.lower()) return file_path.suffix.lower() in normalized def unique_dest(dest: Path) - Path: if not dest.exists(): return dest idx 1 while True: new_dest dest.parent / f{dest.stem}_{idx}{dest.suffix} if not new_dest.exists(): return new_dest idx 1 def main(): args parse_args() target_dir Path(args.target) target_dir.mkdir(parentsTrue, exist_okTrue) log_file None if args.log: log_file open(args.log, a, encodingutf-8) def log(msg): print(msg) if log_file: log_file.write(msg \n) total_copied 0 total_skipped 0 for source_str in args.sources: source_dir Path(source_str) if not source_dir.exists() or not source_dir.is_dir(): log(f[警告] 忽略无效目录{source_dir}) continue for file_path in source_dir.rglob(*): if not file_path.is_file(): continue if not match_ext(file_path, args.exts): continue if args.keep_structure: rel_path file_path.relative_to(source_dir) dest target_dir / rel_path else: dest target_dir / file_path.name if dest.exists(): if args.conflict skip: log(f[跳过] {file_path} - {dest} 已存在) total_skipped 1 continue elif args.conflict overwrite: log(f[覆盖] {file_path} - {dest}) else: dest unique_dest(dest) log(f[重命名] {file_path} - {dest}) else: log(f[复制] {file_path} - {dest}) if args.dry_run: continue dest.parent.mkdir(parentsTrue, exist_okTrue) try: shutil.copy2(file_path, dest) total_copied 1 except Exception as e: log(f[错误] 复制失败{file_path} - {dest}原因{e}) log(f汇总完成成功复制 {total_copied} 个文件跳过 {total_skipped} 个文件。) if log_file: log_file.close() if __name__ __main__: main()脚本逻辑说明sources参数用nargs接收一个或多个源文件夹使用时用空格分隔。--target指定目标目录如果不存在会自动创建。--exts用于过滤扩展名支持不带点的写法。--keep-structure开启后保留文件在源目录中的相对子文件夹结构。--conflict决定同名文件处理策略默认自动重命名。--dry-run是最重要的安全选项只会输出计划不实际复制。--log可以把执行过程写入日志文件便于事后审计。4.5 运行与验证先进入脚本所在目录然后用刚才创建的测试目录验证。第一步试运行查看计划。python file_merge.py D:/data/source1 D:/data/source2 -t D:/data/merged --dry-run运行后会输出即将复制的文件路径例如[复制] D:\data\source1\2024-01\销售报表.xlsx - D:\data\merged\销售报表.xlsx [复制] D:\data\source1\2024-01\会议纪要.docx - D:\data\merged\会议纪要.docx [重命名] D:\data\source1\2024-02\销售报表.xlsx - D:\data\merged\销售报表_1.xlsx [复制] D:\data\source2\汇总表.xlsx - D:\data\merged\汇总表.xlsx [重命名] D:\data\source2\产品图.png - D:\data\merged\产品图_1.png第二步确认无误后去掉--dry-run正式执行。python file_merge.py D:/data/source1 D:/data/source2 -t D:/data/merged --log D:/data/merge_log.txt第三步检查目标目录和日志内容。dir D:/data/merged最终 merged 目录中应包含所有非重复文件重名文件会自动追加_1、_2这样的序号。日志文件里记录了每个文件的复制结果方便后续追溯。5. 常见问题与排查思路文件汇总脚本使用过程中经常会遇到一些和文件夹、权限、文件名相关的问题。下面整理成表格方便按图索骥。问题现象常见原因解决思路复制文件时提示 PermissionError文件被占用、源目录只读、目标目录无写权限关闭正在打开该文件的程序检查目录权限必要时以管理员身份运行命令汇总后目标位置出现文件缺失某个源文件被移动过遍历时路径失效先用--dry-run确认计划再正式执行检查源目录是否完整磁盘空间不足导致复制中断汇总文件体积超过目标盘剩余空间统计源文件总大小清理空间或改用移动方式中文文件名变成乱码终端编码不是 UTF-8Windows 下设置set PYTHONIOENCODINGutf-8后再运行某些文件复制失败文件名包含特殊字符或路径过长超过系统限制查看日志定位具体文件Windows 下开启长路径支持或改用短路径提示“操作无法完成因为其中的文件夹已在另一程序中打开”文件夹正被资源管理器、软件或杀毒程序占用关闭占用程序使用任务管理器结束可疑进程后重试汇总结果缺少某些文件扩展名过滤条件写错或没有包含隐藏文件检查--exts参数确认源文件夹中文件确实存在且不是隐藏系统文件希望把文件从原位置移走而不是复制当前脚本默认复制如需移动将脚本中的copy2换成shutil.move但务必先备份如果你遇到“文件夹删不掉”或“需要管理员权限才能删除文件夹”这类问题通常是因为有进程正在占用文件或者文件夹被设置了只读、访问控制等属性。可以先用 Process Explorer、资源监视器等工具定位并关闭占用进程再尝试删除或修改权限。对于脚本来说尽量只读取源目录不做删除操作可以从根源上减少权限风险。6. 最佳实践与工程建议写文件汇总脚本并不难但在真实项目和生产环境中使用需要注意几个工程层面的问题。6.1 先 Dry-Run 再正式执行所有涉及大量文件复制、移动、覆盖的操作都应该先试运行一遍。完整版脚本中的--dry-run参数就是为此设计的。试运行模式下脚本只打印执行计划不修改任何文件。确认计划里的路径和文件名都正确后再去掉--dry-run正式执行。6.2 复制优先移动谨慎如果源文件还有可能用到强烈建议默认使用复制而不是移动。移动文件一旦出现路径错误或者磁盘写入失败源文件可能丢失。汇总操作最好是“把副本集中到目标目录”保留原始目录不动。只有明确要做空间清理时才考虑移动而且移动前应做好备份。6.3 冲突策略要明确选择重命名策略时建议在日志中记录“原始路径 - 新路径”的对应关系否则事后可能找不到某个文件是从哪里来的。选择覆盖策略风险最高除非你确认同名文件内容一致否则不要在生产环境使用。选择跳过策略则要注意跳过的文件不会出现在目标目录后续可能还需要单独核对。6.4 日志与审计当源文件数量很大时控制台输出无法完整保留执行痕迹。建议在正式运行时开启--log参数把每个文件的处理结果写入日志文件。日志中应至少包含时间、操作类型、源路径、目标路径、结果这五个要素。这样即使某个文件被重命名或复制失败也能快速定位。6.5 性能与大数据量优化如果文件数量达到数万甚至几十万单个rglob全量递归可能比较慢。可以考虑下面几种优化方式按文件类型分批处理不要一次遍历全部目录。先统计文件大小预估磁盘空间是否足够。用shutil.copyfileobj配合自定义缓冲区复制超大文件。多线程复制在不同磁盘之间可以提高效率但普通小文件场景增加线程不一定有明显效果。对于绝大多数办公和开发场景单线程复制已经足够不必为了性能引入额外复杂度。6.6 拓展定时任务、GUI 与打包这个脚本可以继续扩展成定时文件汇总工具。在 Windows 上可以通过“任务计划程序”定期执行在 Linux 上可以通过 cron 定时运行。如果使用者不熟悉命令行还可以用 Tkinter 或 PySimpleGUI 给脚本加一个简单的图形界面让用户选择源文件夹和目标文件夹。最后可以用 PyInstaller 把脚本打包成 exe让没有安装 Python 的同事也能直接使用。7. 总结与进一步提升本文从“多个文件夹文件汇总包括子文件夹”这个实际需求出发先拆解了目录遍历、文件复制、重名冲突这三大核心问题再通过一个完整脚本把零散思路串联起来。你现在应该已经掌握了几个关键点使用pathlib.rglob递归遍历多层子文件夹。使用shutil.copy2复制文件并保留元数据。通过自动重命名、覆盖、跳过三种策略处理同名文件。通过--dry-run参数实现安全的试运行模式。通过日志记录整个汇总过程便于审计与排错。在实际项目中使用时优先关注三点先备份源数据、先试运行再执行、明确冲突策略。这些看起来细小的习惯能避免绝大多数数据丢失问题。下一步你可以尝试给脚本加入“最近修改时间过滤”“按文件大小过滤”“生成汇总统计报告”等功能甚至把它封装成带界面的工具。如果本文对你有帮助建议把脚本保存到本地下次整理文件时直接套用。