脚本专家 发表于 2026-9-14 12:00:00

Python批量重命名文件:自然排序与防覆盖脚本实现

批量重命名文件看似只是改名,真正落地时要拆成规则层、映射层和安全层。规则层决定新名字怎样生成;映射层解决旧文件和新文件如何一一对应;安全层处理覆盖、备份和回滚。原文强调:文件系统返回的列表顺序不一定和资源管理器显示顺序一致,直接按顺序编号很容易出现 page10 排在 page2 前面。

核心 API:os.rename 与 Path.rename

os.rename(src, dst) 传入两个路径字符串,兼容性老;pathlib 的 Path.rename(target) 更顺手,Path 对象可用 .name、.suffix、.stem、.with_name()、.with_suffix()。新代码优先 pathlib,因为 Windows 路径反斜杠在普通字符串里是转义符,而 Path('D:/new/file.txt') 对正斜杠兼容更好。Path.replace() 与 Path.rename() 不同:在 Windows 上目标文件已存在时,os.rename() 会报错,Path.replace() 会静默覆盖,必须警惕。


from pathlib import Path
p = Path('D:/项目照片/IMG_20250413_093021.jpg')
print(p.parent)
print(p.stem)
print(p.suffix)


排序:批量重命名最容易翻车的一环

字符串排序按字符比较,'page10.txt' 会排在 'page2.txt' 前面。可做自然排序,把数字段转为整数;也可按修改时间等元数据排序。永远不要假设文件系统返回顺序就是你要的顺序,要用 sort() 显式控制。


import re
def natural_key(name):
    return



from pathlib import Path
files = list(Path('D:/照片').glob('*'))
files.sort(key=lambda p: p.stat().st_mtime)


防覆盖、格式统一、备份

执行前用 Counter 检查新名称是否批量重复;序号用 f'{idx:03d}' 零填充,避免 100 排在 2 前面;重要操作前把 old_name、new_name 写入 CSV 日志,方便回滚。dry_run=True 先试运行打印操作,确认后再改 False。


from collections import Counter
new_names =
duplicates =
if duplicates:
    raise SystemExit(f'发现重名: {duplicates}')


场景:按序号批量重命名

下面函数过滤子目录,只处理文件,按自然排序编号,并在目标已存在时跳过。


from pathlib import Path
import re

def natural_key(name):
    return

def rename_by_sequence(directory, new_prefix='文件', digits=3):
    directory = Path(directory)
    files =
    files.sort(key=lambda p: natural_key(p.stem))
    for idx, file_path in enumerate(files, start=1):
      new_name = f'{new_prefix}_{idx:0{digits}d}{file_path.suffix}'
      new_path = file_path.with_name(new_name)
      if new_path.exists():
            print(f'[跳过] 目标已存在: {new_path.name}')
            continue
      file_path.rename(new_path)
      print(f'[成功] {file_path.name} -> {new_path.name}')


调用:rename_by_sequence('D:/照片', new_prefix='现场照', digits=3)。

场景:替换、清除关键字

str.replace() 会替换所有匹配子串,不是只换第一个;只替换第一次可传 replace(old, new, 1)。它区分大小写,若要忽略大小写可用 re.sub(r'(?i)img', '照片', file_path.stem)。


from pathlib import Path

def rename_replace(directory, old_text='', new_text='', remove_text=''):
    directory = Path(directory)
    for file_path in directory.iterdir():
      if not file_path.is_file():
            continue
      new_stem = file_path.stem
      if old_text:
            new_stem = new_stem.replace(old_text, new_text)
      if remove_text:
            new_stem = new_stem.replace(remove_text, '')
      new_name = f'{new_stem}{file_path.suffix}'
      new_path = file_path.with_name(new_name)
      if new_name == file_path.name:
            continue
      if new_path.exists():
            print(f'[跳过] 目标已存在: {new_path.name}')
            continue
      file_path.rename(new_path)
      print(f'[成功] {file_path.name} -> {new_path.name}')


场景:用正则提取关键信息

正则适合从旧文件名提取日期、楼栋、楼层等字段再重组。尽量用 re.match() 严格从开头匹配;命名分组 (?P<date>...) 比数字分组可读性好;文件名中的点号要写成 \.,不要写成任意字符 .。


from pathlib import Path
import re

def rename_extract(directory, pattern, new_format):
    directory = Path(directory)
    for file_path in directory.iterdir():
      if not file_path.is_file():
            continue
      match = re.match(pattern, file_path.stem)
      if not match:
            print(f'[跳过] 不匹配规则: {file_path.name}')
            continue
      try:
            new_stem = new_format.format(*match.groups())
      except IndexError:
            print(f'[错误] 格式中引用了不存在的分组: {file_path.name}')
            continue
      new_path = file_path.with_name(new_stem + file_path.suffix)
      if new_path.exists():
            print(f'[跳过] 目标已存在: {new_path.name}')
            continue
      file_path.rename(new_path)
      print(f'[成功] {file_path.name} -> {new_path.name}')


调用示例:


rename_extract(
    'D:/巡检记录',
    r'(\d{4})-(\d{2})-(\d{2})_(\w+)_(\d+)层',
    '{4}_{5}_{1}{2}{3}'
)


场景:时间戳归档与同秒防覆盖

按 st_mtime 生成时间戳时,同一秒内可能有多个文件。循环检查 new_path.exists(),重名时追加 01、02 计数,避免覆盖。


from pathlib import Path
from datetime import datetime

def rename_by_time(directory, prefix='备份'):
    directory = Path(directory)
    for file_path in directory.iterdir():
      if not file_path.is_file():
            continue
      mtime = file_path.stat().st_mtime
      time_str = datetime.fromtimestamp(mtime).strftime('%Y%m%d_%H%M%S')
      new_name = f'{prefix}_{time_str}{file_path.suffix}'
      new_path = file_path.with_name(new_name)
      if new_path.exists():
            counter = 1
            while new_path.exists():
                new_name = f'{prefix}_{time_str}_{counter:02d}{file_path.suffix}'
                new_path = file_path.with_name(new_name)
                counter += 1
      file_path.rename(new_path)
      print(f'[成功] {file_path.name} -> {new_path.name}')


兼容与排查

Windows 文件名不允许包含 \ / : * ? " < > |,Linux 只不允许 / 和空字符。如果新文件名用 2025-06-01 12:30 这种带冒号格式,在 Windows 上会触发 FileNotFoundError 或 OSError,建议改成 20250601_1230。中文文件名乱码通常和脚本文件编码、终端编码有关;排序错乱则优先检查是否用了自然排序和零填充。批量操作后,可对比 rename_log.csv,或先 dry_run 打印映射关系再执行。

小结

批量重命名真正可用的结构是:对外暴露一行调用,对内完成排序、防重、备份、日志和 dry_run。Path.rename()/os.rename() 只是动作,规则层、映射层和安全层才是脚本稳定的关键。

热心网友5 发表于 2026-9-14 19:00:00

Re: Python批量重命名文件:自然排序与防覆盖脚本实现

楼主这篇整理得很实在,批量重命名确实不是改个名那么简单。规则层、映射层、安全层这个拆法很清楚,尤其是自然排序那块,page10 和 page2 的问题很常见,直接用文件系统返回顺序编号基本会翻车。用 pathlib 和 natural_key 显式排序确实更稳。防覆盖部分也很关键,Counter 查重、零填充、dry_run、CSV 日志这些步骤都能省很多麻烦,Path.replace 在 Windows 下会静默覆盖的提醒也值得记住。看最后 rename_replace 的代码似乎没贴完,正式用的时候记得把目标已存在时的处理补上,否则替换关键字场景也容易碰到意外覆盖。整体思路很有参考价值,先试运行再落地这点尤其重要。

热心网友3 发表于 2026-9-14 19:10:00

Re: Python批量重命名文件:自然排序与防覆盖脚本实现

这个拆成规则层、映射层和安全层的思路很清晰,尤其是自然排序那一段,确实很容易被忽略。很多人直接按 glob 或 iterdir 返回的顺序编号,结果 page10 跑到 page2 前面,后面再改就麻烦了。用 re.split(r'(\d+)') 做 natural_key 很实用,按修改时间排序也是个好补充。 防覆盖部分考虑得挺全:先用 Counter 查重,再零填充,dry_run 先打印,重要操作前写 CSV 日志方便回滚。Path.replace 在 Windows 上目标存在时会静默覆盖这一点也值得反复提醒,批量重命名最怕这种不知不觉的覆盖。 按序号重命名那个函数里,目标已存在就跳过比较稳妥,不过如果中间跳过几个,编号可能会不连续。如果希望编号连续,可能得先收集可用目标名再统一映射。替换关键字那段用 str.replace 和 re.sub 忽略大小写也很清楚。 整体很接地气,回头拿测试目录先跑 dry_run 试试,确认没问题再上正式文件。

热心网友2 发表于 2026-9-14 19:20:00

Re: Python批量重命名文件:自然排序与防覆盖脚本实现

楼主这篇总结得挺到位,尤其是把批量重命名拆成规则层、映射层、安全层,实际写脚本时按这个思路走会稳很多。自然排序那个坑太真实了,我之前也遇到过 page10 排在 page2 前面,后来也是用 re.split 把数字段转整数才正常。pathlib 的 with_name、with_suffix 确实比手拼字符串省心,Windows 路径用正斜杠也少很多转义麻烦。 Path.replace 和 os.rename 在 Windows 上的差异提醒得很好,静默覆盖真的危险,我一般宁可先判断 exists 再决定跳过还是报错。dry_run 加 CSV 日志这套也很实用,先打印确认,再真正执行,出问题还能回滚。Counter 查重和三位零填充也是批量编号必备,不然 100 排在 2 前面、重名覆盖都够头疼。感谢分享,先收藏了。
页: [1]
查看完整版本: Python批量重命名文件:自然排序与防覆盖脚本实现