几万个文件放在一起,命名规则乱到没法归档,手动改名既慢又容易漏。Python批量重命名是处理这类重复劳动最实用的方案:一次写好规则,再多人同时整理也不容易把文件名改乱。尤其是文件夹里全是带日期的素材、客户文件、报表、扫描件,或是需要按照 Excel 清单去改 Word 文件名称时,这个思路能帮你省下大量时间。
这篇内容我会按真正的落地顺序拆解:先判断命名需求,再准备 Python 环境,然后写几种最常用的重命名脚本。等你跑通小规模测试后,再加防呆检查、日志和异常处理,最后再放到几万个文件的真实目录里跑。文章更适合刚接触 Python、或者只会写 if/else 但没做过文件批处理的读者,也适合想把自己的整理流程做成可复用脚本的人。
1. 先分清你要处理的是哪一类命名问题
1.1 多数批量命名可以归成三种模式
先别急着搜“批量重命名代码”。命名规则不确认,代码写得再漂亮都可能出错。常见需求其实能分成三类:
- 固定规则替换:给所有文件加前缀、加后缀、去掉空格、把中文括号改成英文括号、统一大小写、改扩展名。
- 顺序编号:把一堆照片或扫描件按文件名、修改时间、创建时间排序后,统一改成
0001_name.jpg、0002_name.jpg这种带序号的名字。 - 外部映射对照:Excel 或 CSV 里有一列旧文件名、一列新文件名,脚本按这个对应关系去改名。比如“根据 Excel 表格批量重命名对应的 Word 文件名称”就是这个场景。
三种场景的处理逻辑完全不同。固定规则替换只需要处理字符串;顺序编号要先确定排序方式;外部映射则必须有准确的“新旧名称对应表”。
如果不区分场景,直接写一个“万能重命名脚本”,很容易出现两个问题。第一个问题是脚本参数过多,每次跑之前要反复确认;第二个问题是没法预测结果,遇到特殊情况时日志不清晰,改错了都不知道是哪一步出的问题。
1.2 建议先写一版命名规则样例
我自己的习惯是,不管需求多简单,都先在纸上或表格里写两三个示例:
旧:DSC_0001.jpg 新:2025_客户A_0001.jpg 旧:合同(初稿).docx 新:合同_初稿.docx 旧:IMG_2381.png 新:产品图_2381.png写样例的作用是让你提前关注几个容易被忽略的细节:
- 新文件名里要不要保留扩展名;
- 序号是三位还是四位;
- 日期放前缀还是后缀;
- 分隔符用下划线、中划线还是空字符;
- 原文件名里的空格、括号要不要一起处理。
这些细节一旦确定,代码结构基本就出来了。后面要做的是把节点拆开:先改动一个目录里的少量文件,确认结果符合预期,再往大目录里放。
2. Python 环境准备:先确认你在哪个环境里跑
2.1 命令行检查 Python 版本
很多人不是不会写 Python,而是环境没准备好,导致脚本在系统之间跑来跑起。做批量文件重命名,先确认你本机能不能正常调用 Python。
Windows 下打开命令行,输入:
python --version如果提示找不到命令,最常见原因是安装时没有把 Python 写入环境变量。安装包里通常有 “Add python.exe to PATH” 选项,安装时勾上就能省掉后面手工配置环境变量的麻烦。已经安装但没有勾选,也可以在命令行里试试:
py --versionmacOS 和 Linux 大多自带 Python 3,但自带版本可能和你需要的不完全一致。先输入:
python3 --version确认是 3.8 以上比较稳妥。新版 Python 命令在有些系统里是python3,不是python,这一点不用纠结,认准当前终端能用的那个命令即可。
2.2 第三方库尽量装在虚拟环境里
如果只是用os.rename或pathlib做基础改名,不需要安装额外库。但如果你要读 Excel 映射表,通常会用到pandas、openpyxl或xlrd。这时候我建议新建一个虚拟环境,避免不同项目之间依赖冲突。
在项目目录下执行:
python -m venv venvWindows 激活虚拟环境:
venv\Scripts\activatemacOS 或 Linux 激活虚拟环境:
source venv/bin/activate激活后在终端里可以看到环境名前缀。此时再安装依赖:
pip install pandas openpyxl虚拟环境不是批量重命名的必须步骤,但一旦你的 Python 环境里装了多个版本或各种项目包,这个习惯能节省不少排查时间。毕竟很多报错根本不是代码问题,而是库装在了别的解释器下面。
2.3 用哪个编辑器不重要,懂调试最关键
VSCode、PyCharm、Notepad++、系统自带记事本都能写这类脚本。VSCode 配置 Python 环境后能直接运行脚本,也会显示语法错误。PyCharm 对新手更友好,但项目结构略显重。两者都可以,不需要争哪一个更好。
关键是你会不会看错误信息。比如pip install时报权限错误,说明当前 Python 环境没有写入权限,优先考虑虚拟环境而不是盲目加sudo或管理员权限。再比如运行文件时显示ModuleNotFoundError: No module named 'pandas',大概率不是代码错了,而是你没有在激活的 venv 中安装 pandas,或者运行脚本用的不是同一个 Python 解释器。
3. 最常见的三类改名脚本,先跑通这版再优化
3.1 统一加前缀和后缀
先看一个小而完整的示例。假设目录D:\待重命名里有一堆合同扫描件,要统一加前缀合同_。
from pathlib import Path folder = Path(r"D:\待重命名") prefix = "合同_" for p in folder.iterdir(): if p.is_file() and not p.name.startswith(prefix): p.rename(p.with_name(prefix + p.name))iterdir()会列出当前目录下的内容。p.is_file()用来过滤掉文件夹。p.with_name(...)会生成一个位于同一目录、但文件名不同的路径。这样就不用手动拼字符串里的路径分隔符,避免 Windows 和 Linux 路径写法差异带来的坑。
如果目标是把后缀统一加上,比如此前导出的文件都没有.txt后缀:
from pathlib import Path folder = Path(r"D:\待重命名") suffix = ".txt" for p in folder.iterdir(): if p.is_file() and not p.suffix.lower() == suffix: p.rename(p.with_name(p.name + suffix))这段代码有个明显缺陷:如果文件原本是.pdf,强行加上.txt会把内容不变、扩展名改成.txt,可能反而更乱。所以“加后缀”需要先明确,是给没有扩展名的文件补后缀,还是给所有文件加自定义关键词前缀。不要混在一起用。
3.2 把空格、中文括号等替换成统一字符
文件名里如果有空格,在命令行和很多工具里都不方便处理。常见做法是把空格替换成下划线。
for p in folder.iterdir(): if p.is_file(): new_name = p.name.replace(" ", "_") new_name = new_name.replace("(", "(").replace(")", ")") if new_name != p.name: p.rename(p.with_name(new_name))同样,如果文件名里有连续多个空格,建议一次性处理掉:
import re for p in folder.iterdir(): if p.is_file(): new_name = re.sub(r"\s+", "_", p.name) if new_name != p.name: p.rename(p.with_name(new_name))很多文件名不是“不能看”,而是各种空格、制表符、中文符号混在一起。先做一个清洁步骤,后面的搜索和归档会顺畅很多。
3.3 按下标顺序统一编号
目录里放了几百张图片,希望按名称顺序排好后重新编号。可以先取出文件列表,再按文件名字符串排序。
files = [p for p in folder.iterdir() if p.is_file()] files.sort(key=lambda p: p.name.lower()) for index, p in enumerate(files, start=1): new_name = f"{index:04d}_{p.name}" p.rename(p.with_name(new_name))f"{index:04d}"会把 1 格式化成0001,10 格式化成0010。为什么要补零?因为如果不补零,排序时会出现1.jpg、10.jpg、2.jpg这种自然顺序错乱。补零后,0001到0010能按文件管理器的人性化排序方式对齐。
如果不需要保留原文件名,只想用序号命名,可以改成:
suffix = p.suffix new_name = f"{index:04d}{suffix}"但这里要特别提醒:编号类脚本很容易把“第二次运行”搞砸。第一次运行后,文件名已经变成了0001_xxx.jpg。第二次再执行同一段代码,就会把所有文件重新编号一遍,很可能把已经排好的顺序打乱。后面我会单独说如何让脚本可重复执行。
4. 批量改名前,先把防误改和干跑做进去
4.1 目标路径冲突检查
文件改名的最大风险不是跑得慢,而是目标名和现有文件重名。在 Linux 和 macOS 上,Path.rename在某些情况下会自动覆盖目标文件,这会造成不可逆转的丢失。Windows 上虽然经常会报FileExistsError,但不同环境行为不完全一致,不能赌系统行为。
所以,在真正执行改名之前,必须先检查目标路径是否已经存在。下面是一个比较安全的改名函数:
from pathlib import Path folder = Path(r"D:\待重命名") def safe_rename(src: Path, dst: Path): if not src.exists(): print(f"源文件不存在: {src.name}") return False if src == dst: return False if dst.exists(): print(f"目标文件已存在,跳过: {dst.name}") return False src.rename(dst) return True无论你用什么规则生成新文件名,都建议先经过这个函数,再执行真正的改名。虽然没有一个脚本能绝对避免所有误操作,但“目标存在就跳过”这一个检查,能挡住很多批量事故。
4.2 特殊字符与非法文件名过滤
Windows 文件名不能包含\ / : * ? " < > |这类字符,也不能以某些保留设备名命名。跨平台使用时要格外小心。建议在生成新文件名时先做一次清洗。
import re def clean_name(name: str) -> str: # 把连续空白替换成下划线 name = re.sub(r"\s+", "_", name) # 把 Windows 非法字符替换成下划线 name = re.sub(r'[\\/:*?"<>|]', "_", name) # 去掉首尾多余的点或空格 name = name.strip(" .") return name清洗后再去执行改名。这个过程不需要太复杂,也不需要你去判断每种操作系统的细节。统一把常见非法字符过滤掉,能有效避免一批原来好端端的文件名,因为脚本里的一个replace变成了不可用路径。
4.3 干跑模式:先输出改名计划再执行
“干跑”听起来很专业,其实就是在改名前把计划打印出来或写到文件里,不真正操作文件。这是我会强烈建议新手养成的习惯。
from pathlib import Path folder = Path(r"D:\待重命名") plan = [] for p in folder.iterdir(): if not p.is_file(): continue new_name = clean_name(p.name) if new_name == p.name: continue dst = p.with_name(new_name) plan.append((p, dst)) # 先打印前 20 条 for src, dst in plan[:20]: print(src.name, "->", dst.name) print(f"待处理文件数: {len(plan)}")执行到这一步,文件还没有被改动。看到待处理文件数后,你可以人工确认数量是否符合预期。如果数量不对,说明规则可能有漏网或误伤。确认没问题后,再询问用户是否继续:
answer = input("输入 y 开始执行:") if answer.strip().lower() == "y": for src, dst in plan: safe_rename(src, dst)如果是在服务器上无法交互输入,可以把这段改成“读取确认文件”。干跑的意义是让电脑先给出一个可预览、可复核的执行计划,而不是直接用鼠标双击脚本后就“盲改”。
4.4 日志和中断恢复
几万个文件不是几秒就能处理完的。如果跑到一半停电、崩溃、用户点了停止,后面会面临“不知道改到哪了”的尴尬。最好在改名前记录日志,或者把整个计划先落盘。
log_file = folder / "rename_plan.txt" with log_file.open("w", encoding="utf-8") as f: for src, dst in plan: f.write(f"{src.name}\t{dst.name}\n")执行脚本时,如果中断了,下一次可以读取这个日志,跳过已经处理过的文件。简单做法是:
- 每条记录都有一个待执行状态;
- 成功改名后,在日志里标记
done; - 再次执行时跳过
done。
不用专门引入队列系统,用一个文本日志就足够。重要的是别把所有文件一把全读进内存,也别在失败后重复执行相同的改名逻辑。文件数量达到几万时,能不能继续跑,往往比能跑多快更重要。
5. 从 Excel 表格映射到 Word 文件名的批量方案
5.1 映射表字段尽量写成这样
按 Excel 表格批量重命名 Word 文件,是工作中很高频的需求。通常你会拿到一张表,里面列着“旧名称”和“新名称”。我建议表格结构尽量简单:
| 序号 | 旧文件名 | 新文件名 |
|---|---|---|
| 1 | 合同_张三_2023.docx | 合同_张三_2024.docx |
| 2 | 汇报_初版.docx | 汇报_终版.docx |
字段不要混在一列里,也不要把文件路径写进去。如果文件在子目录,前两列可以加“相对路径”列,但更稳妥的做法是把待改名文件统一复制到一个目录里,处理完再归档。
5.2 pandas 读取 Excel 并逐个改名
如果安装了pandas和openpyxl,读取 Excel 非常方便。
import pandas as pd from pathlib import Path folder = Path(r"D:\待改名") df = pd.read_excel("rename_map.xlsx", dtype=str) for _, row in df.iterrows(): old_name = str(row.get("旧文件名", "")).strip() new_name = str(row.get("新文件名", "")).strip() if not old_name or not new_name: continue src = folder / old_name dst = folder / new_name if not src.exists(): print("找不到源文件:", old_name) continue if dst.exists(): print("目标文件已存在:", new_name) continue src.rename(dst) print("已重命名:", old_name, "->", new_name)dtype=str可以避免 Excel 中类似0001的文本被读成数字1,这个细节对文件名很重要。
如果你没有pandas,也可以只用标准库csv读取 CSV 文件。但 Excel 文件经常有多个 sheet,数据里还可能存在空行,用pandas处理起来更顺手。
5.3 空值、重复项和后缀问题怎么处理
映射方案最常见的坑不是代码本身,而是表格数据和实际文件对不上。
第一类问题是空值。Excel 里可能存在看似为空、其实是空格的行。建议在读取后先做数据清洗。
df = df.dropna(subset=["旧文件名", "新文件名"]) df["旧文件名"] = df["旧文件名"].astype(str).str.strip() df["新文件名"] = df["新文件名"].astype(str).str.strip() df = df[df["旧文件名"] != "None"] df = df[df["新文件名"] != "None"]第二类问题是重复。如果旧文件名重复,同一个文件被规划成多个新名字,脚本就会冲突。可以先检查重复项。
duplicated = df[df["旧文件名"].duplicated(keep=False)] if not duplicated.empty: print("存在重复的旧文件名,请先处理 Excel")第三类问题是后缀不匹配。表的“新文件名”里的扩展名如果和 Word 原文件不同,比如.doc改成.docx,那本质上可能已经是格式转换问题,不只是文件名改名。所以映射表中的后缀要尽量保持和源文件一致。如果 Excel 里的名称不带后缀,可以在脚本中按实际类型补全,但要注意doc和docx是两种后缀,不能默认只补.docx。
最后再看一个常见问题:Excel 中旧文件名写的是绝对路径,而待改名文件在另一个目录。跨目录映射时,脚本会以为源文件也存在绝对路径下,从而报找不到。最简单的处理办法:Excel 只写文件名,不写路径;文件统一放到脚本指定的folder里。这样脚本清晰,也不容易误操作其它目录。
6. 几万个文件要怎么处理才不慢、不乱
6.1 先想清楚是当前目录还是递归目录
文件数量多时,首先要想清楚你要处理的是当前目录,还是包含所有子目录的递归目录。
# 只处理当前目录和所有文件 for p in folder.iterdir(): ... # 只处理当前目录里的 jpg 图片 for p in folder.glob("*.jpg"): ... # 递归处理所有子目录里的 jpg 图片 for p in folder.rglob("*.jpg"): ...如果只改顶层文件,用iterdir()或glob("*.pattern")。如果要把子目录里的文件也一起处理,用rglob()。
但要注意:递归处理时,如果你的改名规则可能会改到目录名,建议先别把目录名也纳入规则。否则正在遍历时目录改了名字,脚本可能找不到后续文件。稳妥做法是先处理文件,再单独处理目录,并且不做递归目录改名。
6.2 不要轻易跨盘符,也不要盲目上并发
文件重命名是文件系统操作,主要瓶颈通常不在 Python 运算,而在磁盘读写。如果是普通本地硬盘,脚本速度不会差到哪;如果是网络共享盘,瓶颈在网络延迟,这时候你会觉得单个循环很慢。
很多人遇到慢,第一反应是开多线程、多进程。我不建议一开始就这么做。并发重命名会引入额外的问题:命名冲突、日志乱序、资源竞争、错误难排查。先用单线程跑一个小目录,记录时间,评估整体耗时。如果确实到了大批量状态,可以考虑用ThreadPoolExecutor,但最大工作线程数也不要拉太高。每个 worker 都要调用文件系统,太多 worker 会让磁盘排队反而更慢。
from concurrent.futures import ThreadPoolExecutor def rename_one(pair): src, dst = pair safe_rename(src, dst) pairs = [(src, dst) for src, dst in plan if not dst.exists()] # 先小并发试一下 with ThreadPoolExecutor(max_workers=4) as executor: list(executor.map(rename_one, pairs))这段代码只作为“确实需要并发时”的示例。真实任务里,还是先确认单线程是否能接受,再决定要不要并发。
另一个更重的坑是跨盘符。Path.rename()默认是同一文件系统的重命名。如果源和目标不在同一个盘符或挂载点,可能报跨设备错误。很多人把批量改名写成了“移动文件”,这就会碰到这个问题。如果确实要把文件移动到另一个磁盘,应该用shutil.move(),并把“改名”和“移动”分成两步设计,不要混在一个脚本里。
6.3 让脚本幂等:跑第二遍也不出错
批量任务最好有一个特性:幂等。意思是,无论脚本执行一遍还是多遍,结果都保持一致,不会因为重复执行而把文件名改乱。
加前缀的脚本可以判断是否已经有前缀:
if not p.name.startswith(prefix): p.rename(p.with_name(prefix + p.name))替换空格的脚本天然具备一定的幂等性,因为第二次执行时,new_name与原来的p.name相同,就不会再改。
最危险的是编号脚本。第一次运行后,文件名已经带上了编号;第二次如果直接对全部文件重新编号,很容易把之前的顺序破坏。
一个简单做法是,在编号前先排除已经符合新命名规则的文件:
files = [ p for p in folder.iterdir() if p.is_file() and not p.name.startswith(prefix) ] files.sort(key=lambda p: p.name.lower()) for index, p in enumerate(files, start=1): new_name = f"{prefix}{index:04d}{p.suffix}" p.rename(p.with_name(new_name))这样第二次运行时,带prefix的文件不会被再次处理。但如果你原文件名本身就以prefix开头,就要换一种排除方法。最好的办法是:不要只靠脚本保护,第一次跑之前先备份文件列表或使用干跑日志,确认规则无误后再正式执行。
7. 常见报错与排查顺序
7.1 按这个顺序排查绝大多数文件名问题
遇到批量改名报错,先不要急着改代码。我喜欢按下面的顺序排查:
- 看现象:是直接报错,还是日志里某几个文件没被处理?是改到一半卡住,还是改完以后发现命名不对?
- 看文件名:源文件名是否带扩展名?Excel 里的名称是一模一样,还是有肉眼看不出来的空格或中文全角字符?
- 看目录路径:脚本里写的路径是否存在?是不是把
folder和文件名的反斜杠、正斜杠拼错了? - 看文件状态:文件是否被 Word、Excel、WPS、资源管理器某个窗口占用?
- 看脚本逻辑:新文件名是不是合法?目标文件是否已经存在?脚本有没有把已经处理过的文件又纳入第二次循环?
很多问题的根源不在“代码不会写”,而是环境或者输入数据没有处理干净。只要你不跳过排查顺序,大多数报错都能在三五分钟内定位。
7.2 几种高频报错的直接原因
| 报错或现象 | 常见原因 |
|---|---|
FileNotFoundError | 旧文件名不存在、路径写错、Excel 里的名字和实际文件不一致 |
PermissionError | 文件正被其它软件打开,或当前用户没有目录写权限 |
FileExistsError | 目标文件已经存在;在 Windows 上尤其常见 |
OSError: [WinError 123] | 新文件名包含非法字符,或路径太长 |
| 中文文件名打印乱码 | 终端编码问题;脚本本身不一定错了 |
| 任务跑到一半卡住 | 大概率是某个文件被占用、目录网络等待,或文件数量很大但没有日志反馈 |
PermissionError最常见的诱因是文件被办公软件临时打开。Word 文件如果在桌面上处于打开状态,脚本想改文件名时,Windows 会拒绝。所以批量执行前,先关闭相关软件。
中文文件名乱码则要看具体情况。Windows 上的 Python 3 处理 Unicode 文件名本身没有问题,多数是终端打印窗口的显示编码不是你预期的。可以不用纠结控制台输出,直接看日志文件,日志文件用 UTF-8 写入即可。
7.3 改完之后的验证步骤
改完后不要只看“没有报错”就觉得完事。我一般会做三件事:
- 统计一下重命名后的文件数量;
- 抽查前面、中间、后面各几个文件,看命名规则是否符合预期;
- 检查是否存在重复的目标名。
数量对比可以用代码:
renamed_count = len(list(folder.glob("*.jpg"))) print("jpg 数量:", renamed_count)但这只能看到当前扩展名文件有多少,没法直接验证重命名后的名字是否完全正确。更可靠的方法是从日志里挑几十条,打开真实目录人工抽查。对最重要的文件,建议在改名前先复制一份备份目录,或者至少保留重命名日志。后续如果发现规则想调整,日志能帮你做反向操作。
8. 给新手收尾的一套执行流程
8.1 新手先按五步走
如果你是第一次用 Python 做批量文件重命名,别直接处理几万个文件。把下面五步走完,大概率能把风险降下来。
第一步,在某个临时目录里复制 30 到 50 个有代表性的文件。第二步,写一个只针对这个临时目录的脚本,先只打印改名计划,不执行。第三步,检查打印结果,看有哪些文件会被改、哪些会被漏掉。第四步,在临时目录里真正执行,看结果是否和计划一致。第五步,确认没问题后,再把目录路径从临时目录改成真实目录,并且保留日志。
这一步看起来很繁琐,但对于几万文件的批量任务来说,一次误改的成本远高于这几分钟的准备时间。
8.2 这几个习惯值得长期保留
把常用的安全函数保存成一个公共脚本,会让后续工作高效很多。比如safe_rename()、clean_name()、日志记录代码,都是可以复用的部分。下次遇到类似需求,不需要重写,只改文件名生成规则就行。
如果涉及 Excel 映射,建议把 Excel 表的第一行字段固定下来,比如旧文件名、新文件名。长期整理资料时,你会发现字段命名混乱比文件命名混乱更让人头疼。
同时保持脚本只做一件事:要么只是重命名,要么只是移动,要么只是清理文件名中的特殊字符。把多个任务堆在同一个脚本里,虽然看起来“一键完成”,但发生错误后很难回滚。单一职责在这里同样适用。
8.3 什么时候不该用 Python 改名
Python 批量改名并不是万能的。只有几个文件需要简单调整时,直接在文件管理器里重命名更快。文件数量中等但规则较复杂时,也可以用专门的批量重命名软件,图形界面能实时预览。
但如果你的文件数量很大、命名规则有明确业务含义、需要反复重跑、或需要先按 Excel 清单对应关系处理,Python 脚本的回报就更明显。只要把环境、规则、干跑、日志这几件事做扎实,几万个文件的批量重命名并没有想象中那么难。真正踩坑的地方,多数不是代码能力,而是没有提前发现文件名的重复、非法字符和路径不一致。