最近在整理本地视频素材时,我遇到了一个挺典型的问题:手头有一堆从不同渠道下载的直播回放文件,文件名五花八门,像【纯净】[QT奈-直播回放] 2026年07月25日19点场.mp4这种格式。我的需求很简单,就是想把这些文件批量重命名,提取出主播名和直播时间,整理成主播名_直播时间.mp4这样清晰、统一的格式,方便归档和检索。
这个需求听起来不复杂,但手动操作几十上百个文件,不仅枯燥,还容易出错。更麻烦的是,这些文件名里混杂了各种特殊字符、中括号、空格,甚至还有全角日期,直接用简单的字符串替换或者正则表达式,很容易写错。我试过用一些现成的批量重命名工具,但规则稍微复杂一点,比如要同时处理中括号和提取特定字段,配置起来就很头疼,还不如自己写脚本来得直接可控。
所以,我决定用 Python 写一个脚本来解决这个问题。这不仅仅是为了完成一次性的重命名任务,更是想沉淀一个可复用的处理框架。因为类似“从混乱的原始文件名中提取关键信息并标准化”的场景,在处理用户上传内容、整理爬虫数据、归档媒体库时太常见了。今天,我就把这个从需求分析、正则编写、到异常处理和批量执行的完整思路分享出来,重点不是代码本身,而是背后的思考过程和那些容易踩坑的细节。
1. 先拆解文件名:看似简单,陷阱不少
拿到一个像【纯净】[QT奈-直播回放] 2026年07月25日19点场.mp4这样的文件名,第一反应可能是用字符串的split方法按[、]或者】来切割。这个方法在文件名格式绝对规整时可行,但一旦遇到格式微调(比如少了某个括号,或者日期格式变了),脚本就会崩溃。
更稳健的方法是使用正则表达式。正则表达式的核心思想是“模式匹配”,它不关心字符的具体位置,而是描述我们期望的文本模式。让我们一步步拆解目标文件名:
- 前缀与分隔符:
【纯净】这部分是固定前缀,后面可能跟着其他字符。[QT奈-直播回放]看起来是一个“单元”,主播名和描述都在里面,用中括号包裹。 - 核心信息:我们最需要的是“主播名”(QT奈)和“直播时间”(2026年07月25日19点场)。注意,时间信息里包含了“年”、“月”、“日”、“点”这些中文单位,并且“19点场”可能是一个整体。
- 文件扩展名:最后的
.mp4需要保留。
基于这个分析,我们可以设计正则表达式来“捕获”关键部分。在正则中,括号()用来分组,分组后的内容可以被单独提取出来。
对于这个例子,一个初步的正则模式可能是:【纯净】\[(.+?)-直播回放\] (.+?)\.mp4
我们来解读一下:
【纯净】:匹配固定前缀。\[:匹配左中括号[,因为[在正则中是特殊字符,所以需要转义。(.+?):这是一个非贪婪匹配的分组,匹配任意字符至少一次,但尽可能少地匹配。它在这里用于匹配“QT奈”。?使得匹配在遇到后面的-时就停止,防止匹配过多内容。-直播回放\]:匹配固定的“-直播回放]”字符串。- :匹配一个空格(原文件名中主播单元和时间之间有个空格)。
(.+?):另一个非贪婪分组,用于匹配“2026年07月25日19点场”这个时间字符串。\.mp4:匹配扩展名,点号也需要转义。
这个模式能很好地匹配示例文件。但这就是终点了吗?远远不是。真实世界的文件名往往比示例更“脏”。
2. 构建健壮的正则:应对真实世界的混乱
上面那个正则太“脆弱”了。它假设前缀一定是【纯净】,假设主播名和“直播回放”之间一定用“-”连接,假设中间一定有一个空格。在实际操作中,你可能会遇到:
[主播A-直播录像] 2025年12月01日20点场.mkv【高清】主播B的直播 202412311800.flv主播C-20230725-直播片段.mp4
因此,我们需要一个更具弹性的正则表达式。我们的目标是尽可能匹配,并提取出主播名和日期时间信息,即使格式有出入。
一个更健壮的思路是:
- 忽略固定前缀:像
【纯净】、【高清】这类前缀,我们可以选择不捕获,或者用可选模式匹配。 - 灵活匹配主播名:主播名可能被
[]包裹,也可能没有。可能包含中文、英文、数字、特殊符号。我们可以尝试匹配从文件开头或某个标志后,到第一个日期数字或“直播”等关键词之前的内容。 - 精准匹配日期时间:日期时间格式相对有规律(数字+中文单位或纯数字)。这是提取的关键锚点。
我们可以设计一个分两步走的策略:
- 第一步,宽松匹配:用一个相对宽松的正则,把可能包含核心信息的“文本块”匹配出来。
- 第二步,精确提取:在匹配到的文本块内,再用更具体的正则去提取主播名和日期。
但为了保持脚本的简洁,我们也可以尝试一个“强化版”的单次正则。例如,针对原始格式的变体,可以这样写:
import re pattern = r'【.*?】?\[?(.+?)(?:-|的)?直播(?:回放|录像)?\]?\s*(\d{4}年\d{1,2}月\d{1,2}日\d{1,2}点场)\.(mp4|mkv|flv)'解释一下这个模式的思路:
【.*?】?:匹配可能存在的【】前缀,非贪婪,且整个前缀是可选的(?)。\[?:左中括号可选。(.+?):第一个捕获组,用于匹配主播名。这是我们要提取的核心信息一。(?:-|的)?:一个非捕获组(?:...),匹配“-”或“的”,可选。用于处理“主播-直播”或“主播的直播”这两种连接方式。直播(?:回放|录像)?:匹配“直播”,后面可能跟着“回放”或“录像”,可选。\]?:右中括号可选。\s*:匹配任意空白字符(空格、制表符等),零次或多次。(\d{4}年\d{1,2}月\d{1,2}日\d{1,2}点场):第二个捕获组,用于严格匹配“年月日点场”格式的日期时间。\d代表数字,{4}代表4位数字(年)。这是我们要提取的核心信息二。\.(mp4|mkv|flv):匹配扩展名,并捕获扩展名类型(第三个捕获组),方便后续保留。
这个正则的适应性更强,但它依然基于“日期格式非常规整”的假设。如果日期格式变成202412311800(年月日时分),它就会失效。因此,正则表达式的设计永远是一个在“精度”和“召回率”之间的权衡。没有一劳永逸的完美正则,必须根据你的实际数据样本进行调整。
3. 从单文件测试到批量脚本:搭建可靠的处理流程
写好正则后,不要急着写循环批量处理。正确的做法是,先对单个文件名进行充分的测试。
import re import os def parse_filename(old_name): """ 解析旧文件名,提取主播名和直播时间。 返回 (匹配是否成功, 主播名, 直播时间, 扩展名) """ # 使用上述强化版正则 pattern = r'【.*?】?\[?(.+?)(?:-|的)?直播(?:回放|录像)?\]?\s*(\d{4}年\d{1,2}月\d{1,2}日\d{1,2}点场)\.(mp4|mkv|flv|avi)' match = re.search(pattern, old_name) if match: anchor_name = match.group(1) # 主播名 live_time = match.group(2) # 直播时间 ext = match.group(3) # 扩展名 # 可以对提取的内容进行清洗,比如去除首尾空格 anchor_name = anchor_name.strip() live_time = live_time.strip() return True, anchor_name, live_time, ext else: return False, None, None, None # 测试用例 test_files = [ "【纯净】[QT奈-直播回放] 2026年07月25日19点场.mp4", "[主播A-直播录像] 2025年12月01日20点场.mkv", "【高清】主播B的直播 2024年12月31日18点场.flv", "这个文件不符合格式.txt", # 应该匹配失败 ] for f in test_files: success, anchor, time, ext = parse_filename(f) if success: new_name = f"{anchor}_{time}.{ext}" print(f"匹配成功: '{f}' -> '{new_name}'") else: print(f"匹配失败: '{f}', 需要手动处理或检查规则")运行测试,观察输出是否符合预期。特别是对于匹配失败的情况,要分析原因,是文件名真的格式迥异,还是我们的正则需要微调?这个测试环节至关重要,能防止批量重命名时误伤“友军”。
测试通过后,我们就可以编写完整的批量重命名脚本了。脚本的核心逻辑很简单:遍历目录下的文件,对每个文件尝试解析,如果解析成功则构造新文件名并重命名,如果失败则记录下来。
import re import os import sys def batch_rename(directory): """ 批量重命名指定目录下的视频文件。 """ # 预编译正则表达式,提升效率 pattern = re.compile(r'【.*?】?\[?(.+?)(?:-|的)?直播(?:回放|录像)?\]?\s*(\d{4}年\d{1,2}月\d{1,2}日\d{1,2}点场)\.(mp4|mkv|flv|avi|mov)', re.IGNORECASE) failed_files = [] for filename in os.listdir(directory): old_path = os.path.join(directory, filename) # 跳过目录,只处理文件 if not os.path.isfile(old_path): continue match = pattern.search(filename) if match: anchor_name = match.group(1).strip() live_time = match.group(2).strip() ext = match.group(3).lower() # 扩展名统一小写 # 构造新文件名:主播名_直播时间.扩展名 # 处理可能存在的非法文件名字符(如Windows下不能有:等) safe_anchor = anchor_name.replace(':', '_').replace('?', '_').replace('*', '_').replace('\"', '_').replace('<', '_').replace('>', '_').replace('|', '_') safe_time = live_time.replace(':', '_').replace('?', '_').replace('*', '_').replace('\"', '_').replace('<', '_').replace('>', '_').replace('|', '_') new_filename = f"{safe_anchor}_{safe_time}.{ext}" new_path = os.path.join(directory, new_filename) # 防止新文件名重复 counter = 1 while os.path.exists(new_path): new_filename = f"{safe_anchor}_{safe_time}_{counter}.{ext}" new_path = os.path.join(directory, new_filename) counter += 1 try: os.rename(old_path, new_path) print(f"重命名成功: {filename} -> {new_filename}") except Exception as e: print(f"重命名失败 {filename}: {e}") failed_files.append(filename) else: print(f"格式不匹配,跳过: {filename}") failed_files.append(filename) # 输出总结报告 if failed_files: print(f"\n以下文件未能处理,请检查:") for f in failed_files: print(f" - {f}") else: print(f"\n所有文件处理完成!") if __name__ == "__main__": # 使用当前目录,或者通过命令行参数指定目录 target_dir = "." if len(sys.argv) > 1: target_dir = sys.argv[1] if not os.path.isdir(target_dir): print(f"错误:目录 '{target_dir}' 不存在。") sys.exit(1) print(f"开始处理目录: {target_dir}") # 安全提示:可以先模拟运行,不实际重命名 # 正式运行前,建议先备份原文件 confirm = input("是否确认执行重命名?(输入 'yes' 继续): ") if confirm.lower() == 'yes': batch_rename(target_dir) else: print("操作已取消。")4. 超越重命名:将经验沉淀为可复用的数据处理框架
完成这个脚本后,我意识到它解决的不是一个孤立的“直播回放重命名”问题,而是一类更通用的“非结构化文本信息提取与标准化”问题。无论是处理爬虫抓取的新闻标题、整理混乱的下载文件名,还是清洗用户提交的表单数据,核心逻辑都是相通的:识别模式、提取要素、清洗转换、输出标准结果。
我们可以把这个过程抽象成一个简单的框架:
- 模式探索与定义:收集一批样本数据(文件名),人工观察并归纳出有效信息的常见模式和位置。这是最关键的一步,决定了后续规则的准确性。
- 规则实现与测试:使用正则表达式(或更复杂的解析器,如解析HTML/JSON)将模式转化为代码规则。务必编写单元测试,用正例和反例验证规则。
- 批处理与容错:将规则应用到批量数据上。必须包含完善的错误处理(
try...except)和日志记录,对无法处理的数据进行隔离和报告,而不是让整个程序崩溃。 - 结果验证与迭代:检查处理后的结果。对于错误匹配或匹配失败的情况,分析原因,返回第一步优化规则。这是一个迭代的过程。
把这个框架应用回我们的案例:
- 模式:
[主播信息] 日期时间信息.扩展名。 - 规则:强化版正则表达式。
- 批处理:
batch_rename函数,包含跳过、重命名、防冲突、错误记录。 - 迭代:通过
failed_files列表收集异常,供后续分析优化规则。
对于更复杂或格式极其不统一的情况,单一的规则可能不够用。这时可以考虑:
- 规则集:定义多个正则模式,按顺序尝试,直到有一个匹配成功。
- 机器学习:如果数据量巨大且模式难以手工总结,可以考虑训练一个简单的文本分类或序列标注模型(如用CRF)来识别实体(主播名、日期)。但这属于进阶方案,对于大多数日常任务,精心设计的正则规则集已经足够强大。
最后,关于文件操作,还有几个重要的安全提醒:
警告:任何批量文件操作都有风险。在执行重命名、移动、删除前,务必先备份原始数据。可以先运行一个“模拟”或“预览”模式,只打印新旧文件名对应关系而不实际操作,确认无误后再执行。
注意文件名冲突和非法字符。我们的脚本虽然做了简单的重复名处理和非法字符替换,但在跨平台(Windows/Linux/macOS)时,文件名规范仍有差异。对于生产环境,需要使用更完善的路径处理库(如
pathlib)和字符串清洗函数。
通过这样一个具体的需求,我们不仅完成了一个实用的脚本,更重要的,是掌握了一套处理杂乱文本数据、将其转化为结构化信息的方法论。这套方法的价值,远不止于重命名几个视频文件。