ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python正则表达式实战:智能解析音乐视频标题,实现自动化文件管理

Python正则表达式实战:智能解析音乐视频标题,实现自动化文件管理 最近在整理个人音乐收藏时发现很多海外歌曲的MV或现场版视频其标题和描述信息常常混杂着多种语言和特殊符号例如【中字】【Net JJ】《รักที่เป็นเธอ (The Love I Found)》Ost.LoveUponATimeSerieOfficial MV。这种格式虽然信息量大但不利于本地文件管理和信息检索。作为开发者我们完全可以用技术手段来自动化处理这类文本提取出核心的歌曲名、艺术家、语言标签等结构化信息。本文将分享一个完整的实战项目“音乐视频标题信息提取与格式化工具”。我们将从需求分析开始一步步使用 Python 实现一个能够智能解析复杂标题、并输出整洁元数据的小程序。无论你是想管理自己的影音库还是学习正则表达式、字符串处理及面向对象编程这篇文章都能提供从思路到代码的完整参考。1. 项目背景与核心需求在数字媒体时代我们从各种平台下载或保存的视频文件其文件名往往直接复制了网页标题。这些标题通常包含语言标签如【中字】、[CHS/CHT]、(字幕)用于提示视频内嵌字幕的语言。发布者/频道标签如【Net JJ】、[Official]表明视频来源。核心内容歌曲名、剧集名可能同时包含原始语言如泰语รักที่เป็นเธอ和翻译语言如英语The Love I Found。媒体类型与归属如Ost.LoveUponATimeSerie表示这是电视剧《Love Upon A Time》的原声带。视频类型如Official MV、Live Performance、符号常作为分隔符。手动整理这些信息费时费力。我们的工具需要实现以下核心功能智能解析自动识别并分离标题中的各类标签和核心内容。结构化输出将解析出的信息存储为结构化的数据如字典、JSON便于后续导入数据库或重命名文件。灵活可配置允许用户自定义需要识别的标签模式以适应不同平台的标题风格。容错处理对格式不标准或异常的标题能有基本的处理能力不至于程序崩溃。2. 技术选型与环境准备本项目主要使用 Python 实现因其在文本处理、正则表达式和快速原型开发方面具有强大优势。2.1 开发环境操作系统Windows 10/11, macOS, 或 Linux (Ubuntu) 均可。Python 版本3.8 或更高版本。确保已安装python3和pip。集成开发环境 (IDE)推荐使用 VSCode、PyCharm 或任何你熟悉的文本编辑器。2.2 项目初始化首先创建一个新的项目目录并初始化虚拟环境这能有效隔离项目依赖。# 创建项目目录 mkdir music_title_parser cd music_title_parser # 创建虚拟环境 (Python 3.8) python3 -m venv venv # 激活虚拟环境 # Windows (cmd或PowerShell) venv\Scripts\activate # macOS / Linux source venv/bin/activate # 激活后命令行提示符前应显示 (venv)2.3 依赖库本项目主要使用 Python 标准库无需安装额外包。但为了更好的开发体验可以安装black用于代码格式化pytest用于单元测试。(venv) pip install black pytest创建项目结构music_title_parser/ ├── venv/ # 虚拟环境目录由上述命令创建 ├── title_parser/ # 核心代码包 │ ├── __init__.py │ └── parser.py # 主解析器逻辑 ├── tests/ # 测试目录 │ ├── __init__.py │ └── test_parser.py # 单元测试 ├── main.py # 主程序入口 ├── requirements.txt # 项目依赖可选 └── README.md # 项目说明3. 核心解析逻辑设计与实现解析器的核心是定义一系列规则模式来匹配标题中的不同部分。我们将使用 Python 的re正则表达式模块。3.1 定义解析模式我们需要分析常见元素并为其编写正则表达式模式。# title_parser/patterns.py # 此文件定义所有正则表达式模式 import re class TitlePatterns: 定义用于匹配音乐视频标题各部分的正则表达式模式。 使用命名捕获组 (?Pname...) 来提取内容。 # 匹配【】或[]括起来的标签如【中字】、[Official] # 模式解释匹配全角【】或半角[]中间包含非】]的任意字符。 TAG_BRACKETS re.compile(r[【\[].*?[】\]]) # 匹配语言标签特定关键词优先级高于通用标签 # 模式解释匹配包含“中字”、“CHS”、“CHT”、“字幕”、“sub”等词的标签。 LANGUAGE_TAG re.compile(r[【\[].*?(?:中字|CHS|CHT|字幕|sub|Sub).*?[】\]], re.IGNORECASE) # 匹配《》括起来的内容通常是核心歌曲/剧集名 # 模式解释匹配全角《》中间内容尽可能非贪婪匹配。 CONTENT_ANGLE re.compile(r《(.*?)》) # 匹配 () 括起来的内容通常是副标题、翻译或年份 CONTENT_PAREN re.compile(r\((.*?)\)) # 匹配“Ost.”或“OST”开头的内容表示原声带归属 OST_TAG re.compile(r(?:Ost\.|OST)\s*(.), re.IGNORECASE) # 匹配视频类型如“Official MV”, “Live”, “Performance” VIDEO_TYPE re.compile(r([|]?\s*(?:Official\s*)?(?:MV|Music\s*Video|Live|Performance|Audio)), re.IGNORECASE) # 匹配可能的分隔符如“”, “-”, “–” SEPARATOR re.compile(r[|\-–]\s*)3.2 构建解析器类我们将创建一个TitleParser类它接收原始标题应用上述模式并返回一个结构化的字典。# title_parser/parser.py import re from .patterns import TitlePatterns class TitleParser: 音乐视频标题解析器。 将杂乱标题解析为结构化的字典信息。 def __init__(self, title: str): 初始化解析器。 :param title: 待解析的原始标题字符串。 self.original_title title.strip() self.patterns TitlePatterns() self.parsed_info { original_title: self.original_title, language_tags: [], other_tags: [], primary_content: , # 《》中的主要内容 secondary_content: [], # () 中的内容 ost_info: , video_type: , cleaned_core_title: # 去除所有标签后的核心标题 } def parse(self) - dict: 执行解析流程返回解析后的信息字典。 if not self.original_title: return self.parsed_info working_title self.original_title # 1. 提取语言标签 self._extract_language_tags(working_title) # 从working_title中移除已提取的语言标签避免重复匹配 working_title self.patterns.LANGUAGE_TAG.sub(, working_title) # 2. 提取其他通用标签【】或[] self._extract_other_tags(working_title) working_title self.patterns.TAG_BRACKETS.sub(, working_title) # 3. 提取《》中的主要内容 self._extract_primary_content(working_title) # 4. 提取 () 中的次要内容 self._extract_secondary_content(working_title) # 5. 提取 Ost. 信息 self._extract_ost_info(working_title) # 6. 提取视频类型 self._extract_video_type(working_title) # 7. 生成清理后的核心标题 self._generate_cleaned_title(working_title) return self.parsed_info def _extract_language_tags(self, text: str): 提取语言标签。 matches self.patterns.LANGUAGE_TAG.findall(text) # 去除标签本身的括号只保留内部文字 for match in matches: # 移除首尾的【】或[] clean_tag match[1:-1] if len(match) 1 else match if clean_tag and clean_tag not in self.parsed_info[language_tags]: self.parsed_info[language_tags].append(clean_tag) def _extract_other_tags(self, text: str): 提取非语言的通用标签。 # 先找到所有括号标签 all_bracket_matches self.patterns.TAG_BRACKETS.findall(text) for match in all_bracket_matches: clean_tag match[1:-1] if len(match) 1 else match # 检查是否已被识别为语言标签可能在原始文本中 is_language_tag any( lang_keyword in clean_tag.lower() for lang_keyword in [中字, chs, cht, 字幕, sub] ) if clean_tag and not is_language_tag and clean_tag not in self.parsed_info[other_tags]: self.parsed_info[other_tags].append(clean_tag) def _extract_primary_content(self, text: str): 提取《》中的主要内容。 match self.patterns.CONTENT_ANGLE.search(text) if match: self.parsed_info[primary_content] match.group(1) def _extract_secondary_content(self, text: str): 提取 () 中的内容。 matches self.patterns.CONTENT_PAREN.findall(text) if matches: self.parsed_info[secondary_content] matches def _extract_ost_info(self, text: str): 提取原声带信息。 match self.patterns.OST_TAG.search(text) if match: self.parsed_info[ost_info] match.group(1).strip() def _extract_video_type(self, text: str): 提取视频类型。 match self.patterns.VIDEO_TYPE.search(text) if match: self.parsed_info[video_type] match.group(1).strip(| ).strip() def _generate_cleaned_title(self, text: str): 生成一个清理后的核心标题。 策略移除所有已知标签、OST信息、视频类型后剩下的部分进行规整。 # 移除已识别的视频类型 temp_text text if self.parsed_info[video_type]: # 注意视频类型可能包含分隔符需用正则安全移除 temp_text self.patterns.VIDEO_TYPE.sub(, temp_text) # 移除 OST 信息 if self.parsed_info[ost_info]: temp_text self.patterns.OST_TAG.sub(, temp_text) # 移除所有括号内容包括《》和() temp_text self.patterns.CONTENT_ANGLE.sub(, temp_text) temp_text self.patterns.CONTENT_PAREN.sub(, temp_text) # 用分隔符正则分割取第一个非空部分作为核心标题 parts self.patterns.SEPARATOR.split(temp_text) for part in parts: clean_part part.strip() if clean_part: self.parsed_info[cleaned_core_title] clean_part break # 如果上述方法未获取到则使用去除首尾空格后的剩余文本 if not self.parsed_info[cleaned_core_title]: self.parsed_info[cleaned_core_title] temp_text.strip()4. 完整实战案例与演示让我们用文章开头提到的标题作为示例编写一个主程序来演示整个工具的工作流程。4.1 创建主程序入口# main.py import json from title_parser.parser import TitleParser def main(): # 示例标题 sample_title 【中字】【Net JJ】《รักที่เป็นเธอ (The Love I Found)》Ost.LoveUponATimeSerieOfficial MV print(原始标题) print(f \{sample_title}\) print(\n *50 \n) # 实例化解析器并执行解析 parser TitleParser(sample_title) result parser.parse() # 打印解析结果 print(解析结果) for key, value in result.items(): if isinstance(value, list): print(f {key}: {, .join(value) if value else (空)}) else: print(f {key}: {value if value else (空)}) print(\n *50 \n) # 演示结构化输出如JSON print(结构化输出 (JSON)) print(json.dumps(result, indent2, ensure_asciiFalse)) # 演示一个应用场景生成建议的文件名 print(\n *50 \n) print(生成建议文件名) suggested_name_parts [] if result[cleaned_core_title]: suggested_name_parts.append(result[cleaned_core_title]) if result[primary_content] and result[primary_content] ! result[cleaned_core_title]: # 如果《》内内容与核心标题不同可以考虑附加 pass # 本例中它们是相关的暂不添加 if result[video_type]: suggested_name_parts.append(result[video_type]) if result[language_tags]: lang_suffix f[{-.join(result[language_tags])}] suggested_name_parts.append(lang_suffix) suggested_filename - .join(filter(None, suggested_name_parts)) print(f {suggested_filename}) if __name__ __main__: main()4.2 运行程序与结果分析在项目根目录下运行命令(venv) python main.py预期输出如下原始标题 【中字】【Net JJ】《รักที่เป็นเธอ (The Love I Found)》Ost.LoveUponATimeSerieOfficial MV 解析结果 original_title: 【中字】【Net JJ】《รักที่เป็นเธอ (The Love I Found)》Ost.LoveUponATimeSerieOfficial MV language_tags: 中字 other_tags: Net JJ primary_content: รักที่เป็นเธอ (The Love I Found) secondary_content: The Love I Found ost_info: LoveUponATimeSerie video_type: Official MV cleaned_core_title: รักที่เป็นเธอ 结构化输出 (JSON) { original_title: 【中字】【Net JJ】《รักที่เป็นเธอ (The Love I Found)》Ost.LoveUponATimeSerieOfficial MV, language_tags: [ 中字 ], other_tags: [ Net JJ ], primary_content: รักที่เป็นเธอ (The Love I Found), secondary_content: [ The Love I Found ], ost_info: LoveUponATimeSerie, video_type: Official MV, cleaned_core_title: รักที่เป็นเธอ } 生成建议文件名 รักที่เป็นเธอ - Official MV - [中字]4.3 结果解读成功分离解析器准确识别出了“中字”语言标签和“Net JJ”发布者标签。内容提取primary_content: 提取了《》内的完整内容รักที่เป็นเธอ (The Love I Found)。secondary_content: 单独提取了括号()内的翻译The Love I Found。注意primary_content包含了括号而secondary_content是它的子集。这提供了不同粒度的信息。媒体信息正确识别出Ost.LoveUponATimeSerie和Official MV。核心标题通过清理算法得到了最核心的歌曲名รักที่เป็นเธอ。应用输出基于解析结果我们生成了一个更整洁、更适合作为文件名的字符串รักที่เป็นเธอ - Official MV - [中字]。5. 扩展功能与进阶处理一个基础的解析器已经完成但在实际应用中可能会遇到更复杂的情况。下面我们为其增加一些扩展功能。5.1 处理多语言和特殊字符原始标题可能包含泰语、韩语、日语等字符。Python 3 的字符串默认支持 Unicode所以处理这些字符没有问题。但为了更好的兼容性如文件系统我们可以增加一个音译或罗马化的选项这里以简单示例为例实际可能需要库支持。# title_parser/enhancements.py import unicodedata class TitleEnhancer: 提供标题增强功能如规范化、音译示例。 staticmethod def normalize_unicode(text: str) - str: 使用NFKC规范化Unicode字符。 例如将全角字母、数字转换为半角合并一些兼容字符。 return unicodedata.normalize(NFKC, text) staticmethod def transliterate_example(text: str) - str: 一个简单的示例将非ASCII字符替换为拼音或罗马字的占位逻辑。 注意这是一个示意函数。真正的音译需要依赖如pykakasi(日文)、pythainlp(泰文)等库。 # 此处仅为演示实际应调用专业库 # 例如简单地将非英文字母替换为‘_’ import re # 保留基本的拉丁字母、数字、空格、常用标点 pattern re.compile(r[^a-zA-Z0-9\s\(\)《》【】\[\]\-\.,!?]) return pattern.sub(_, text)5.2 批量处理与文件重命名结合os和pathlib库我们可以扫描一个目录下的视频文件并批量重命名。# batch_rename.py import os import shutil from pathlib import Path from title_parser.parser import TitleParser def batch_rename_files_in_directory(directory_path: str, dry_run: bool True): 批量重命名指定目录下的视频文件。 :param directory_path: 目标目录路径 :param dry_run: 为True时只打印预览不实际重命名为False时执行重命名。 dir_path Path(directory_path) if not dir_path.is_dir(): print(f错误路径 {directory_path} 不是一个有效的目录。) return # 支持的视频文件扩展名 video_extensions {.mp4, .mkv, .avi, .mov, .flv, .wmv, .webm} for file_path in dir_path.iterdir(): if file_path.is_file() and file_path.suffix.lower() in video_extensions: original_stem file_path.stem # 文件名不含扩展名 parser TitleParser(original_stem) info parser.parse() # 构建新文件名使用 cleaned_core_title 和 video_type new_stem_parts [] if info[cleaned_core_title]: new_stem_parts.append(info[cleaned_core_title]) if info[video_type]: new_stem_parts.append(info[video_type]) if info[language_tags]: lang_suffix f[{-.join(info[language_tags])}] new_stem_parts.append(lang_suffix) if not new_stem_parts: new_stem original_stem # 如果解析不出核心内容保留原名 else: new_stem - .join(new_stem_parts) # 确保新文件名是安全的移除非法字符 safe_new_stem .join(c for c in new_stem if c not in r:/\|?*) new_file_path file_path.with_stem(safe_new_stem) if dry_run: print(f[预览] {file_path.name} - {new_file_path.name}) else: # 如果目标文件已存在添加后缀避免覆盖 counter 1 while new_file_path.exists(): new_file_path file_path.with_stem(f{safe_new_stem}_{counter}) counter 1 try: shutil.move(str(file_path), str(new_file_path)) print(f[已重命名] {file_path.name} - {new_file_path.name}) except Exception as e: print(f重命名 {file_path.name} 时出错: {e}) # 使用示例 if __name__ __main__: # 指定你的视频文件夹路径 target_dir ./my_music_videos # 第一步先预览不实际修改 print( 预览模式 (dry_runTrue) ) batch_rename_files_in_directory(target_dir, dry_runTrue) # 确认无误后取消下面一行的注释来执行实际重命名 # print(\n 执行模式 (dry_runFalse) ) # batch_rename_files_in_directory(target_dir, dry_runFalse)6. 常见问题与排查思路在开发和使用此类文本解析工具时你可能会遇到以下问题问题现象可能原因解决思路解析结果为空或不准1. 正则表达式模式与标题格式不匹配。2. 标题格式超出预设规则如使用了不常见的括号。3. 编码或特殊字符问题。1. 打印中间处理过程的working_title查看哪一步匹配失败。2. 扩展TitlePatterns类添加新的正则模式或调整现有模式。3. 在解析前对字符串进行str.strip()和unicodedata.normalize()处理。程序抛出AttributeError或TypeError1. 传入的title参数不是字符串类型。2. 在字符串为None时调用了字符串方法。1. 在__init__方法中使用str(title)进行强制转换或添加类型检查。2. 在使用parse()前检查if self.original_title:。处理大量文件时性能慢1. 对每个文件都重新编译正则表达式。2. 文件数量极大。1. 确保正则表达式模式如TitlePatterns中的在模块加载时只编译一次而不是每次解析都编译。2. 对于十万级别的文件考虑使用多进程multiprocessing并行处理。生成的新文件名包含非法字符不同操作系统Windows/macOS/Linux对文件名允许的字符集有不同限制。在生成最终文件名前使用一个过滤函数移除目标系统不允许的字符。上面的safe_new_stem是一个简单示例可根据需要完善。解析后核心标题仍包含多余空格或符号清理逻辑不够彻底残留了分隔符或空格。在_generate_cleaned_title方法最后对cleaned_core_title执行更严格的清理clean_text re.sub(r\s, , clean_text).strip()。调试建议在TitleParser类中添加一个debug模式在解析过程中打印每一步处理后的working_title这能帮助你快速定位模式匹配的问题所在。7. 最佳实践与工程建议将这个小工具工程化以便于维护、测试和集成到更大的系统中。7.1 代码组织与配置化模式可配置不要将正则表达式模式硬编码在代码中。可以考虑将其移至 JSON 或 YAML 配置文件方便非开发者用户调整。# config/patterns.json { language_tags: [中字, CHS, CHT, 字幕, sub], bracket_patterns: [【.*?】, \\[.*?\\]] }使用日志替换print语句为logging模块可以灵活控制输出级别DEBUG, INFO, WARNING。7.2 健壮性增强异常处理在parse方法内部用try-except包裹各个提取步骤即使某一步失败也能继续执行其他步骤并记录错误。输入验证对输入标题进行预处理确保它是有效的字符串并处理可能的None或空值。单元测试为TitleParser编写全面的单元测试覆盖各种边界情况和奇怪的标题格式。这是保证代码质量的关键。# tests/test_parser.py import pytest from title_parser.parser import TitleParser def test_parse_language_tag(): parser TitleParser(【中字】Test Title) result parser.parse() assert result[language_tags] [中字] assert result[cleaned_core_title] Test Title def test_parse_complex_title(): title [4K]《Song Name (English Ver.)》OST.DramaLive Concert parser TitleParser(title) result parser.parse() assert result[other_tags] [4K] assert result[primary_content] Song Name (English Ver.) assert English Ver. in result[secondary_content] assert result[video_type] Live Concert7.3 生产环境注意事项性能如果用于处理海量文件如媒体服务器考虑将解析器封装为 REST API 或微服务并加入缓存机制如对解析结果进行哈希缓存。安全性batch_rename_files_in_directory函数务必做好权限检查避免误操作系统文件。始终先在dry_run模式下预览。可扩展性设计良好的类接口。如果需要支持新的视频平台标题格式应该通过继承TitleParser并重写相关方法或使用策略模式来实现而不是修改原有核心逻辑。7.4 后续优化方向机器学习辅助对于极其不规则、难以用规则覆盖的标题可以尝试训练一个简单的 NER命名实体识别模型来识别歌曲名、艺术家等实体。网络信息补充解析出核心歌曲名后可以调用音乐 API如 Last.fm, Spotify来获取更准确的元数据专辑、艺术家、流派等。图形界面 (GUI)使用tkinter或PyQt为工具制作一个简单的桌面界面方便非技术用户使用。集成到媒体管理工具将解析器作为插件集成到beets,MusicBrainz Picard等专业媒体管理工具的工作流中。通过这个项目我们不仅解决了一个具体的文件管理问题更实践了从需求分析、正则表达式编写、类设计到批量文件操作的完整开发流程。代码已具备良好的结构你可以直接复制使用也可以根据自己遇到的标题格式进行扩展和调整。开始整理你的音乐库吧让技术为你的数字生活带来更多秩序和效率。
返回列表