Python批量重命名文件实战:从os.rename到Excel映射
你遇到过这种情况吗手头有 2 万张产品图片或者从旧系统里导出的一大批合同 PDF命名完全不成规则有的是“新建文档(1).docx”有的是时间戳还有的是“未命名-副本-最终版”。如果靠手动右键重命名一天能改 200 个就算不错而且人的注意力是有限的连续操作超过半小时漏改、错改、重复命名的概率会迅速上升。批量重命名这件事本质上是一个“规则 循环”的问题正好是脚本最擅长解决的场景。Python 不需要复杂框架也不需要图形界面只需要几十行代码就能把几万个文件的改名工作压缩成几分钟。这篇文章会从最基础的rename用法讲起逐步覆盖添加前后缀、替换文件名内容、按序号重命名、递归处理子目录、根据 Excel 映射表批量重命名等真实需求。不管你是运维、测试、产品还是行政岗位只要电脑里需要跟大量文件打交道建议收藏这篇迟早用得上。1. 文件重命名为啥要交给 Python1.1 手工重命名的真实痛点文件重命名看起来简单但数量一旦上去就会暴露三个问题。第一是效率低。一次右键重命名需要“鼠标点击文件名 - 等待输入框激活 - 删除旧名称 - 输入新名称 - 回车”哪怕很熟练一个文件也需要 5 到 10 秒。一万个文件就意味着十几个小时连续重复劳动。第二是易出错。手工改名时大脑需要不断记忆当前改到第几个、下一个应该是什么规则。一旦中途被消息打断很可能出现两个文件用了同一个名字或者中间漏掉一个序号。更要命的是Windows 不允许同一目录出现同名文件冲突时系统会弹窗提示如果直接点了“跳过”事后很难找出哪几个文件没被改到。第三是难追溯。手工操作没有日志改完之后如果规则有问题想回退基本不可能。只能靠备份但很多人动手前根本没备份。1.2 Python 批量重命名的适用场景Python 批量重命名不是银弹但非常适合以下场景对某个目录下的所有文件统一添加前缀或后缀例如批量加上日期、项目编号将文件名中的指定字符串替换成另一个字符串例如把“旧客户”全部替换为“新客户”按文件名中的自然顺序批量编号例如图片整理成001.jpg、002.jpg根据 Excel 表格中的对应关系把散乱的文件名改成规范文件名递归处理多级目录例如把整个资料库中所有.txt文件的编码或命名统一定期执行重复性任务比如每周一次的文件归档。它的核心优势是确定性和可重复性。同样的规则写在脚本里执行一万次结果都一样不会因为操作者疲劳而出现偏差。只要在写脚本时保留好日志出了任何问题都可以通过日志判断是哪个环节错了。1.3 重命名前必须想清楚的三个问题写代码之前比代码更重要的三件事你的新文件名规则是否唯一目标文件夹里不能出现两个同名文件。你是否做了备份批量修改一旦执行无法通过 CtrlZ 撤销。你的规则会不会误伤文件如果目录里同时有.jpg和.docx只改图片还是全部都要改很多人只关心写代码忽略了上面的问题结果脚本跑完后发现图片全乱了后悔已经来不及。后面第 2 节和第 6 节会重点展开备份与安全校验方法。2. 环境准备先让电脑能跑 Python2.1 Python 安装与版本校验如果你的电脑里还没有 Python需要先安装。Windows 用户可以前往 Python 官网下载安装包或者通过 Microsoft Store 安装。安装过程中务必勾选“Add Python to PATH”否则后续在命令行中使用python命令会提示找不到程序。本文示例基于 Python 3.7 以上版本较新的 3.10、3.11、3.12 均可运行。不同小版本在基础文件操作上没有明显差异不需要精确锁定版本。安装完成后打开命令行工具输入以下命令验证python --version如果输出类似Python 3.11.5说明安装成功。如果提示python 不是内部或外部命令通常是 PATH 环境变量没有配置好。可以重新运行安装程序选择 Modify勾选 Add Python to PATH或者手动在系统环境变量中把 Python 安装目录添加进去。2.2 使用 IDLE 还是 IDEPython 自带的 IDLE 可以用来写简单脚本但批量重命名涉及多文件调试更推荐使用 VS Code 或 PyCharm。VS Code 轻量安装 Python 插件后即可运行脚本。PyCharm Community 版免费适合需要观察目录结构的初学者。为了本文演示你只需要能创建.py文件并在命令行执行即可界面工具不是重点。最简单的做法是新建一个文本文件改后缀为.py然后用命令行运行python rename_files.py2.3 重命名前强烈建议先复制一份测试目录在真实处理几万个文件之前强烈建议先从目标目录中复制一小部分文件到测试目录例如 20 个。脚本先在这 20 个文件上跑一遍确认结果符合预期再搬到真实目录执行。这听起来多此一举但实践中能避免大量事故。脚本报错甚至比脚本“成功执行但规则错误”更好排查——前者会立刻暴露问题后者则可能等你处理完几千个文件后才发现命名规则理解错了届时要靠备份恢复耗时巨大。更保险的做法是在真实执行前把整个目录压缩备份一次或者移动到旁边的备份文件夹。磁盘空间紧张时也可以只把“目标文件列表”备份成 CSV 或 txt这样至少能知道原文件名清单。后续第 8 节会介绍先输出重命名计划、确认后再执行的安全写法。3. Python 重命名文件的两个核心 API批量重命名依赖的核心其实只有一个动作把文件从旧路径移动到新路径。当旧路径与新路径在同一个目录时rename就表现为重命名。3.1 os.rename 基本用法Python 标准库os模块提供os.rename(src, dst)其中src是当前文件完整路径dst是目标文件完整路径。import os src rD:\素材\图片_001.jpg dst rD:\素材\图片_001_new.jpg os.rename(src, dst) print(重命名成功)这里有个容易踩坑的点两个参数都必须是“完整路径”而不只是文件名。如果脚本和文件不在同一个目录只写文件名会报FileNotFoundError。再来看一个批量示例。假设文件夹D:\素材里有多个文件需要给每个文件前面加上HD_前缀import os folder rD:\素材 prefix HD_ for filename in os.listdir(folder): full_path os.path.join(folder, filename) # 只处理文件跳过子目录 if os.path.isfile(full_path): new_filename prefix filename new_path os.path.join(folder, new_filename) os.rename(full_path, new_path) print(f{filename} - {new_filename})这段代码里的os.listdir返回目录下的所有名称包括文件夹。删除文件或目录前必须用os.path.isfile判断否则会把文件夹当成文件处理导致后续拼接出错。3.2 pathlib.Path.rename 更现代的写法Python 3.4 之后推荐的pathlib方案更简洁路径对象直接带rename方法。用一个Path对象表示源文件调用它的.rename(目标路径)即可完成重命名。from pathlib import Path folder Path(rD:\素材) prefix HD_ for file in folder.iterdir(): # 遍历目录内容 if file.is_file(): new_path folder / (prefix file.name) file.rename(new_path) print(f{file.name} - {new_path.name})folder / 文件名这种写法会自动拼接系统对应的分隔符在 Windows 和 Linux 上都适用避免手写\\或/混用导致的问题。3.3 文件路径与当前工作目录的理解新手最容易疑惑的一点是为什么明明文件就在脚本旁边Python 找不到原因是程序默认的“当前目录”不一定是.py文件所在目录。当你用python D:\script\rename.py执行时当前目录通常是命令行所在的路径而不是D:\script。如果脚本里用相对路径素材Python 会去当前目录找素材文件夹找不到就报错。所以批量重命名脚本中路径最好统一写成绝对路径或者先执行os.chdir()切换当前目录。为了省心本文所有示例都用Path(rD:\...)这样的绝对路径写法。4. 日常高频场景的批量重命名实战4.1 批量添加前缀与后缀给公司项目文档统一添加年份前缀或者给整理好的图片加批次后缀是最常见的需求。添加前缀from pathlib import Path folder Path(rD:\资料\2025年合同) prefix 2025_ for file in folder.iterdir(): if file.is_file(): new_name prefix file.name file.rename(folder / new_name) print(f{file.name} - {new_name})添加后缀时要注意后缀不能直接加在file.name末尾否则会加在扩展名后面例如报告.pdf_已审核。正确做法是先取stem文件名主干不含扩展名拼上新内容之后再加上suffix扩展名from pathlib import Path folder Path(rD:\资料\2025年合同) suffix_text _已审核 for file in folder.iterdir(): if file.is_file(): new_name file.stem suffix_text file.suffix file.rename(folder / new_name) print(f{file.name} - {new_name})输出类似合同A.pdf - 合同A_已审核.pdf 合同B.pdf - 合同B_已审核.pdf4.2 批量替换文件名中的指定字符项目对接时经常需要把文件名里的旧版称谓换成新版称谓。替换操作的逻辑很简单先判断旧字符串是否在文件名中如果在就替换并重命名。from pathlib import Path folder Path(rD:\标书) for file in folder.iterdir(): if file.is_file() and 投标方A in file.name: new_name file.name.replace(投标方A, 投标方B) file.rename(folder / new_name) print(f{file.name} - {new_name})如果只替换扩展名之前的主干部分可以这样处理from pathlib import Path folder Path(rD:\标书) for file in folder.iterdir(): if file.is_file(): new_stem file.stem.replace(投标方A, 投标方B) new_name new_stem file.suffix file.rename(folder / new_name)这种写法会把投标方A合同.pdf改成投标方B合同.pdf而不会误改扩展名。4.3 按序号批量重命名并保持顺序把IMG_1032.jpg、IMG_1033.jpg改成风景照_001.jpg、风景照_002.jpg是另一种高频需求。直接用列表遍历看起来简单但需要注意排序问题。os.listdir和iterdir()返回顺序不保证是文件名排序后的顺序。如果文件名不是整齐的 001、002而是一堆带有数字编号的字符串字符串排序会变成10.jpg排在2.jpg前面。遇到这种情况可以用正则表达式把文件名中的数字部分提取出来再参与排序from pathlib import Path import re def natural_key(path: Path): # 把 照片_10.jpg 拆成 [照片_, 10, .jpg] parts re.split(r(\d), path.stem) return [int(part) if part.isdigit() else part for part in parts] folder Path(rD:\照片\旅行) files [p for p in folder.iterdir() if p.is_file()] files.sort(keynatural_key) for i, file in enumerate(files, start1): new_name f{i:03d}{file.suffix} # 001.jpg、002.jpg ... file.rename(folder / new_name) print(f{file.name} - {new_name})关键点说明re.split(r(\d), name)会把文件名按数字切分切出来的数字单独成组int(part) if part.isdigit() else part让数字部分按数值大小比较而不是按字典序f{i:03d}的作用是生成三位数编号1会变成001。如果你确定文件名本身就是连续编号例如1.txt、2.txt用files.sort()也可以。但如果目录中混有1.txt和10.txt用了普通字符串排序就会出现1, 10, 2, 3...的错误顺序。4.4 批量重命名时保留扩展名很多新手在处理批量重命名时会把file.stem和file.suffix搞混。专门强调一次file.name完整文件名如年度总结.pdffile.stem主干名如年度总结file.suffix扩展名含点号如.pdf。所以在生成新文件名时推荐的公式是新文件名 新主干名 原扩展名如果直接对file.name做字符串拼接很容易把扩展名换掉或者产生年度总结_2025.pdf.exe之类的嵌套扩展名。因此本文中所有涉及重命名的示例都优先通过stem加工主干部分再单独把suffix拼回去。4.5 递归处理子目录中的所有文件有很多目录是嵌套的例如一级资料目录下面有 2023、2024、2025 等年份文件夹每个年份文件夹里还有 PDF、Word 等文件。如果只想处理某个目录里所有层级的文件就需要递归遍历。Path.rglob(*)可以递归拿到目录下所有文件from pathlib import Path root_folder Path(rD:\项目归档) old_text 临时 new_text 正式 for file in root_folder.rglob(*): if file.is_file() and old_text in file.stem: new_name file.stem.replace(old_text, new_text) file.suffix file.rename(file.with_name(new_name)) print(f{file} - {new_name})file.with_name(new_name)的作用是生成一个与当前文件同目录、但新文件名的Path对象。这样不需要手动拼接上层目录。递归脚本最容易出现文件数量比预期多的问题建议先在root_folder中放少量测试文件试运行。5. 进阶实战根据 Excel 映射表批量重命名 Word/其他文件日常工作中还有一种更难手工处理的场景系统导出的文件叫扫描件_001.pdf但业务上必须按客户姓名或合同编号重命名。这种一对一的对应关系往往记录在 Excel 表格里。如果只有几十个文件可以手工复制粘贴如果有几千个就必须写脚本读 Excel。5.1 场景需求与数据格式假设目录D:\待重命名文件里有这些文件扫描件_001.pdf 扫描件_002.pdf 扫描件_003.pdfExcel 文件映射表.xlsx内容如下原文件名新文件名扫描件_001.pdf张三_劳动合同.pdf扫描件_002.pdf李四_劳动合同.pdf扫描件_003.pdf王五_劳动合同.pdf需求很简单目录中文件名等于“原文件名”的改成对应“新文件名”。Excel 中如果只填了新文件名主干例如“张三_劳动合同”也可以在脚本里自动补充原扩展名减少编辑工作量。5.2 安装 pandas 与 openpyxlPython 标准库不能直接读取 xlsx 文件需要用 pandas 配合 openpyxl 引擎。安装命令pip install pandas openpyxl如果是在公司内网环境且没有外网权限需要先在内网 PyPI 镜像源上确认包是否可用或者使用本地的 wheel 文件安装。安装成功后脚本才能正常读取 xlsx。5.3 读取 Excel 并批量重命名import pandas as pd from pathlib import Path source_folder Path(rD:\待重命名文件) excel_path Path(rD:\映射表.xlsx) # 读取 Excel把所有单元格按字符串读入避免文件名中的 001 变成 1 df pd.read_excel(excel_path, dtypestr) # 构建映射字典 mapping dict(zip(df[原文件名], df[新文件名])) success_count 0 skip_count 0 error_count 0 for old_name, new_name in mapping.items(): src_file source_folder / old_name # 原文件不存在 if not src_file.exists(): print(f[缺失] {old_name} 未在目录中找到) error_count 1 continue # 如果 Excel 单元格没有写扩展名自动补充 new_path_name new_name if not Path(new_name).suffix: new_path_name new_name src_file.suffix dst_file source_folder / new_path_name # 目标文件已存在且不是同一个文件为避免覆盖则跳过 if dst_file.exists(): print(f[跳过] 目标文件已存在{new_path_name}) skip_count 1 continue try: src_file.rename(dst_file) print(f[成功] {old_name} - {new_path_name}) success_count 1 except Exception as e: print(f[失败] {old_name} 重命名出错{e}) error_count 1 print(f\n处理完成成功 {success_count} 个跳过 {skip_count} 个失败 {error_count} 个)这段脚本相比最初版增加了文件存在性判断、自动补扩展名、重复目标跳过、异常捕获、结果统计已经接近生产可用的状态。5.4 运行验证与预期结果命令行执行python rename_by_excel.py正常输出[成功] 扫描件_001.pdf - 张三_劳动合同.pdf [成功] 扫描件_002.pdf - 李四_劳动合同.pdf [成功] 扫描件_003.pdf - 王五_劳动合同.pdf 处理完成成功 3 个跳过 0 个失败 0 个需要注意Excel 读取后默认会把001处理成1会造成文件名不匹配。上面代码中用dtypestr参数强制以字符串读取同时在重命名时“原文件名”列最好保持为文本格式。如果发现脚本提示“原文件未找到”优先检查 Excel 中的编号是不是变成了数值格式。6. 如何让脚本更健壮异常处理与安全校验6.1 目标文件不存在的处理批量重命名中如果目录里文件数量很大配对的 Excel 表中有几条记录需要手工确认或者个别文件已经被移动过就会导致“原文件不存在”。如果不做判断直接rename程序会抛FileNotFoundError中断整个流程。上面示例中的处理方式是打印[缺失]然后继续这样脚本不会因为单条记录出错而整体中断。6.2 重名冲突与覆盖风险Windows 和 Linux 的rename行为有差异。Windows 中如果目标文件已存在os.rename可能直接报FileExistsErrorLinux 下同目录内 rename 也可能覆盖已有目标。为了避免无意识覆盖重命名前一定要检查目标路径是否存在。推荐的做法不是简单exists()就跳过而是把冲突文件单独列出来交给人工处理因为这说明原文件名映射表中可能有重复逻辑或者目录中原本就存在同名文件。6.3 文件被占用导致 PermissionError在 Windows 中如果一个文件正被 Word、Excel、PDF 阅读器打开重命名时会抛出PermissionError。尤其是处理 Word 文件时文档没有关闭很常见。处理策略是捕获PermissionError把失败文件记录到日志中等关闭程序后重新运行一次脚本。不让脚本在异常时崩溃也不建议用重试循环来自动处理这类问题因为如果用户没有关闭程序无限重试只会浪费时间。6.4 记录日志方便人工复核把重命名结果输出到控制台只能在运行时看到关闭后就找不到了。工程上建议输出一份日志文件from pathlib import Path from datetime import datetime log_path Path(rD:\重命名日志) / frename_log_{datetime.now():%Y%m%d_%H%M%S}.txt with open(log_path, w, encodingutf-8) as log: for old_name, new_name in mapping.items(): # 执行 rename... log.write(f{old_name} - {new_name}\n) print(f日志文件已保存{log_path})这样即使脚本执行后发现大量错误也可以拿日志跟原文件列表对比人工审理哪里出了问题。7. 常见问题与排查思路问题现象常见原因解决思路FileNotFoundError源文件路径不对或文件已被移动打印完整路径确认文件实际位置使用绝对路径FileExistsError目标文件已存在Windows 不允许覆盖改名前检查目标是否存在冲突文件单独记录PermissionError文件正被 Word/Excel/WPS 等程序占用关闭所有占用程序脚本捕获该异常并继续文件名中出现乱码Excel 或 CSV 编码不一致读取 CSV 时指定encodingutf-8-sig读取 Excel 时保持文本格式批量改完后发现编号顺序错了使用了普通字符串排序而不是自然排序用正则拆分数字后按数值排序参考第 4.3 节脚本执行但没有任何文件被修改判断条件写错例如替换文本不匹配先在循环里打印file.name和是否命中的日志Windows 提示文件名不能包含某些字符文件名里有 /:*?这里单独展开说一下 CSV 编码问题。很多人用 pandas 读取中文 CSV 文件时会遇到乱码这通常是因为 Excel 保存的 CSV 是 GBK 编码而 pandas 默认按 UTF-8 读取。解决方法是df pd.read_csv(mapping.csv, encodinggbk, dtypestr)如果是自己用记事本生成的 UTF-8 CSV则要加参数df pd.read_csv(mapping.csv, encodingutf-8-sig, dtypestr)utf-8-sig会去掉 Excel 打开 UTF-8 文件时可能出现的 BOM 头避免第一列列名多出一个\ufeff字符。8. 工程化建议与脚本封装技巧8.1 把功能封装成函数而不是一长串命令不要把所有重命名逻辑都写在文件顶部平铺执行。更好的组织方式是先定义函数再在main中调用这样脚本可以复用也方便测试。from pathlib import Path import sys def batch_rename_replace(folder: str, old_text: str, new_text: str) - None: 批量替换文件名主干中的指定字符串。 folder_path Path(folder) if not folder_path.is_dir(): sys.exit(f目录不存在{folder_path}) count 0 for file in folder_path.iterdir(): if file.is_file() and old_text in file.stem: new_stem file.stem.replace(old_text, new_text) new_name new_stem file.suffix try: file.rename(file.with_name(new_name)) print(f{file.name} - {new_name}) count 1 except PermissionError: print(f[占用] 文件被其他程序打开跳过{file.name}) print(f总共处理 {count} 个文件) if __name__ __main__: batch_rename_replace(rD:\资料, 旧关键词, 新关键词)函数加上类型标注后后续维护者能更快明白参数含义。if __name__ __main__:的写法允许这个脚本被其他模块导入而不立即执行。8.2 增加“试运行”参数先看计划再执行如果想处理几万个文件又担心改错建议在脚本里增加一个dry_run参数。当参数为 True 时只打印待执行的改名计划不真正重命名。def batch_rename(folder: str, dry_run: bool True): folder_path Path(folder) plan [] for file in folder_path.iterdir(): if file.is_file(): new_name prefix_ file.name plan.append((file, folder_path / new_name)) if dry_run: print(以下是准备执行的改名计划) for old, new in plan[:20]: print(f{old.name} - {new.name}) print(f共 {len(plan)} 个文件以上仅显示前 20 条) return for old, new in plan: old.rename(new)真实使用中我会先跑一次试运行确认计划里的文件名都符合预期再把dry_runFalse传入执行。8.3 修改前过滤非法字符Windows 文件名不能包含以下字符\ / : * ? |当新文件名来自 Excel 时可能混入这些字符导致OSError。可以写一个清洗函数import re def clean_filename(name: str) - str: 移除 Windows 文件名中的非法字符。 cleaned re.sub(r[\\/:*?|], _, name) return cleaned.strip()在构造新文件名后调用一次能避免大量“文件名不合法”报错。8.4 区分底层文件重命名与移动文件rename的本质是“移动”。如果目标路径在不同分区例如从C:\移到D:\Path.rename可能跨文件系统移动失败此时shutil.move更加可靠。import shutil src Path(rC:\temp\a.txt) dst Path(rD:\archive\a.txt) shutil.move(str(src), str(dst))日常同目录批量重命名不涉及这个问题但如果脚本需要“把文件改名的同时归入对应文件夹”建议使用shutil.move。8.5 使用虚拟环境避免污染全局包如果需要安装 pandas、openpyxl 等第三方库建议项目开始时先创建虚拟环境python -m venv rename_envWindows 激活虚拟环境rename_env\Scripts\activateLinux/macOS 激活source rename_env/bin/activate激活后命令行前面会出现(rename_env)标识再执行pip install pandas openpyxl第三方包就只装在这个项目环境内不会影响系统全局 Python。虚拟环境在团队开发中也能保证依赖版本一致避免“明明自己电脑能跑同事电脑报错”的问题。9. 一份可以直接上手的完整模板脚本如果前面各个章节看得有点多平时最常用的替换文件名方案可以精简成下面这份模板。把它保存为rename_tool.py修改开头变量即可运行。 简易批量重命名工具 支持功能批量替换文件名主干中的指定文字 用法修改下方三个变量后运行 python rename_tool.py from pathlib import Path import sys import re # 需要修改的配置 TARGET_DIR rD:\资料\测试目录 # 目标目录 OLD_TEXT 旧名称 # 要被替换的文字 NEW_TEXT 新名称 # 替换后的文字 DRY_RUN True # True 只预览False 真正执行 # def clean_filename(name: str) - str: 清洗 Windows 非法字符。 return re.sub(r[\\/:*?|], _, name) def main(): folder Path(TARGET_DIR) if not folder.is_dir(): sys.exit(f目录不存在{folder}) plan [] for file in folder.iterdir(): if not file.is_file(): continue if OLD_TEXT not in file.stem: continue new_stem file.stem.replace(OLD_TEXT, NEW_TEXT) new_name clean_filename(new_stem) file.suffix plan.append((file, file.with_name(new_name))) if not plan: print(没有找到需要处理的文件请检查目录和替换文字。) return if DRY_RUN: print(f共匹配到 {len(plan)} 个文件预览前 10 条) for old, new in plan[:10]: print(f{old.name} - {new.name}) print(当前为试运行模式确认无误后把 DRY_RUN 改成 False 再执行。) return success 0 for old, new in plan: try: old.rename(new) print(f[成功] {old.name} - {new.name}) success 1 except PermissionError: print(f[占用] {old.name} 可能被其他程序打开请关闭后重试) except OSError as e: print(f[失败] {old.name} - {new.name}原因{e}) print(f执行完成成功 {success} 个失败 {len(plan) - success} 个) if __name__ __main__: main()首次使用时把DRY_RUN保留为True运行后先看预览结果。确认没有改错后把DRY_RUN改成False再执行一次就能在几分钟内完成几万个文件的批量重命名。文中最前面提到的 Excel 映射重命名场景也可以用同一套思路扩展成 GUI 工具或命令行工具箱。如果平时经常处理这类文档建议把替换命名、序号命名、Excel 映射命名三个功能整理到同一个renamer.py中用函数加参数的方式统一调度后续只需要维护一份代码不需要每次临时写新脚本。