ARTICLE DETAIL

资讯详情

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

用Python实现歌词文本分析:清洗、分词与拼写模式识别

用Python实现歌词文本分析:清洗、分词与拼写模式识别 拿到一段 K-pop 歌词文本时如果只是读一遍谈不上有什么技术含量。真正需要动手的场景往往是这样的你想知道某首歌里出现频率最高的词是什么想统计每位成员的台词行数或者想把像“D to the E, to the L-I-C-I-O-U-S”这样逐字母拼出来的特殊歌词从普通文本里自动识别出来。逐行复制到网页词频工具里遇到几十首歌就会崩溃直接按空格 split又会被逗号、连字符和大小写扰乱结果。这次以 TREASURE崔玹硕、金道荣、朴炡禹相关歌词片段中这句“D to the E, to the L-I-C-I-O-U-S”为入口搭建一个最小可运行的 Python 文本分析管线把清洗、分词、模式提取、词频统计、可视化和结构化输出一次走通。文章中的代码和数据可以直接复制运行之后替换成自己的歌词文本就能复用。做这类分析时最大的误区是一上来就写正则抓词。歌词文本普遍带着说话人标记、标点、大小写、连字符和韩英混排直接处理很容易出现“看起来对了换一行就崩”的情况。因此下面先定义清楚输入和输出再逐步搭建处理流程。1. 先确定任务和输入模型一句歌词文本能挖出什么1.1 输入样例和最小数据模型先看素材里给出的这段文本特点D to the E, to the L-I-C-I-O-U-S。它表面是一个带有逗号的英文短句内部却包含两种特殊结构字母之间用“to the”连接D to the E字母之间用连字符连接L-I-C-I-O-U-S把两部分还原得到的就是DELICIOUS。这种“逐字母拼写单词”的手法在歌词、应援口号、综艺口号里很常见但它并不符合普通英文词法通用分词器不会把它当成一个词。为了让程序可以处理先定义最小输入模型。每一行歌词由两部分组成[说话人标记] 歌词内容例如[崔玹硕] D to the E, to the L-I-C-I-O-U-S在代码里每一行可以表示成这样的结构{speaker: 崔玹硕, lyric: D to the E, to the L-I-C-I-O-U-S}实际工程中可以用一个列表保存所有行再逐行处理。不要在一开始就把整首歌词拼成一个超长字符串否则后面统计说话人分布时会非常痛苦。1.2 期望输出和它在实际项目里的用途按照上面这个输入模型程序完成后应该输出四类结果输出项含义典型用途单词频率清洗后每个词出现的次数了解歌词关键词判断主题拼写短语识别出的逐字母拼写结构及还原单词分析特殊歌词写法、口号设计说话人统计每位成员出现的行数歌词分工统计、应援物料整理结构化文件JSON、CSV、图片后续导入表格或做二次分析这些结果并不只是“统计着玩”。在字幕制作、歌词站点内容维护、粉丝物料整理、语料库建设这类场景里结构化输出可以直接进入下一步流程避免每次都要重新处理原始文本。1.3 为什么不用现成的在线词频统计工具在线工具适合一次性的、几十字的文本。遇到带说话人标记的歌词文件会遇到三个问题无法自动剥离[成员名]标记统计结果会混入标记字符。无法识别L-I-C-I-O-U-S这种跨分隔符的拼写结构在线工具通常只按空格或标点切词。数据要上传到第三方站点歌词文本可能涉及版权也不适合反复提交。自己写脚本的收益不只是“免费”而是可重复、可配置、可排查。处理结果一旦不对你可以直接看中间变量找到是清洗正则的问题、停用词表的问题还是数据本身编码的问题。下面就从环境准备开始。2. 环境准备和样例数据用最小工程结构跑通管线2.1 Python 版本与依赖安装这个项目只需要 Python 3.8 及以上版本以及一个第三方库matplotlib用于可视化。文本处理部分使用标准库re、collections、json、csv不需要额外安装。推荐用虚拟环境隔离依赖python -m venv venv source venv/bin/activateWindows 下激活命令是venv\Scripts\activate然后安装matplotlibpip install matplotlib python --version如果matplotlib下载较慢可以指定国内镜像源pip install matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成后用python --version确认版本再用下面命令确认matplotlib可用python -c import matplotlib; print(matplotlib.__version__)2.2 项目目录和样例数据文件建议按下面结构组织文件后续替换歌词文本时只需要改data/lyrics.txtlyrics_analysis/ ├── data/ │ └── lyrics.txt ├── output/ ├── analyze_lyrics.py └── requirements.txtrequirements.txt内容matplotlib3.5data/lyrics.txt内容如下。第一行是素材中给出的歌词片段其余几行是为了演示词频和说话人统计补写的普通文本并非原词实际分析时请替换成有授权的完整歌词[崔玹硕] D to the E, to the L-I-C-I-O-U-S [金道荣] You know the lyrics? So delicious [朴炡禹] D to the E, to the L-I-C-I-O-U-S [金道荣] Spell it loud and clear, delicious [崔玹硕] A demo line for frequency test2.3 样例数据的编码说明歌词文本包含中文、韩文和英文文件必须统一使用 UTF-8 编码。在 Windows 上用记事本新建文件时默认可能是 GBK写入代码后容易出现UnicodeDecodeError。推荐在代码里读取文件时显式指定编码with open(data/lyrics.txt, encodingutf-8-sig) as f: lines [line.strip() for line in f if line.strip()]这里用utf-8-sig而不是utf-8是为了兼容带 BOM 的 UTF-8 文件。BOM 是文件开头几个不可见字节如果不处理第一行的[崔玹硕]前面会多出一个\ufeff导致说话人标记解析失败。3. 清洗和分词文字处理的第一个关口3.1 清洗策略两路文本两种用途很多人处理文本时只用一份清洗结果这是最容易出问题的设计。以素材中的这行为例D to the E, to the L-I-C-I-O-U-S如果要统计普通单词频率应该把逗号、连字符去掉让每个字母独立成词但如果在清洗时把连字符全部替换成空格L-I-C-I-O-U-S这个拼写结构就被破坏了后面识别拼写模式时什么都匹配不到。因此在设计清洗函数时要把文本分成两路一路是“用于模式识别”的原始歌词内容只剥离说话人标记保留连字符和逗号。一路是“用于词频统计”的干净文本统一小写、去掉标点、把连字符变成空格。先把说话人标记剥离出来这一步对两路通用import re SPEAKER_TAG re.compile(r^\[([^\]])\]\s*(.*)$) def parse_line(line: str): match SPEAKER_TAG.match(line.strip()) if match: return match.group(1), match.group(2) return None, line.strip()^\[([^\]])\]匹配行首方括号(.*)$捕获后面的歌词内容。这里必须用line.strip()先去掉行首空格否则正则要求行首是[遇到缩进行就会失败。3.2 词频分词的最小实现词频统计用的清洗函数要处理大小写和标点def clean_for_words(lyric: str) - str: text lyric.lower() # 标点先替换为空格避免粘连单词 text re.sub(r[,;:!?\()], , text) # 连字符替换为空格让拼写字母独立成词 text re.sub(r[-], , text) return text def tokenize(text: str) - list[str]: return text.split()清洗后的分词效果如下原始行清洗后文本分词结果D to the E, to the L-I-C-I-O-U-Sd to the e to the l i c i o u sd,to,the,e,to,the,l,i,c,i,o,u,sYou know the lyrics? So deliciousyou know the lyrics so deliciousyou,know,the,lyrics,so,delicious注意到第一行分词后全是单字母这些单字母如果直接进入词频统计会把结果淹没。因此词频统计时要加一个最小词长过滤把len(word) 2的丢弃。3.3 清洗前后对照与检查点清洗逻辑写完以后先不要急着写后面的统计代码。建议先跑一个最小检查把每条原始行和清洗后结果打印出来for line in lines: speaker, lyric parse_line(line) print(repr(line)) print(speaker:, speaker) print(cleaned:, clean_for_words(lyric)) print(---)检查点有三个说话人标记是否全部剥离。标点是否被替换成空格有没有出现deliciousso这种粘连。连字符是否按预期变成空格模式识别用的原始行是否仍然保留连字符。这一步如果出错后面词频和模式提取的结果都会异常而错误源头往往是最早的清洗函数。4. 最关键的环节识别逐字母拼写模式4.1 逐字母拼写有哪几种常见写法“逐字母拼写”不是一个严格的语言学概念它指的是把一个单词拆成多个字母再用某种连接符串起来。常见写法有三种写法类型示例特点连字符连接L-I-C-I-O-U-S字母之间用-分隔点号连接L.I.C.I.O.U.S字母之间用.分隔连接词连接D to the E字母之间用to the连接素材里的这行歌词混合了后两种D to the E, to the L-I-C-I-O-U-S。识别时要同时支持连字符序列和to the序列。4.2 用正则表达式提取拼写片段可以用一个正则表达式同时匹配两类片段SPELLING_SEGMENT re.compile( r\b[A-Za-z](?:-[A-Za-z])\b r|\b[A-Za-z](?:\sto\sthe\s[A-Za-z]), re.IGNORECASE )拆开看\b[A-Za-z](?:-[A-Za-z])\b匹配至少两个字母、用连字符分隔的片段例如L-I-C-I-O-U-S。\b[A-Za-z](?:\sto\sthe\s[A-Za-z])匹配D to the E这种结构支持多个to the连接。re.IGNORECASE让大小写都能匹配。提取函数def get_spelling_letters(segment: str) - str: letters re.findall(r[A-Za-z], segment) return .join(letters).upper() def extract_spelling_segments(lyric: str) - list[str]: segments [] for match in SPELLING_SEGMENT.finditer(lyric): segments.append(match.group(0)) return segments对素材中这行文本结果是extract_spelling_segments(D to the E, to the L-I-C-I-O-U-S) # 预期得到 [D to the E, L-I-C-I-O-U-S]注意正则不会跨过逗号去匹配D to the E, to the L-I-C-I-O-U-S整串因为,不在允许的连接符里。这是合理的先识别片段再合并还原。4.3 拼写片段还原成完整单词识别出片段后把每个片段中的字母抽出来按顺序拼接就能还原单词def merge_spelling_in_line(lyric: str) - list[str]: segments extract_spelling_segments(lyric) if not segments: return [] merged .join(get_spelling_letters(seg) for seg in segments) return [merged] if len(merged) 3 else []对素材中的行两个片段分别抽出D E和L I C I O U S按出现顺序拼接后得到DELICIOUS。还原逻辑有两个要注意的地方不是所有拼写片段都要合并。如果分析目标是逐段输出可以只把片段本身放进结果如果目标是还原单词才需要按顺序拼接。拼接时只取字母去掉to the、空格、连字符、点号避免DTO THE E这种脏数据。4.4 这个环节最容易踩的坑第一个坑是连字符不一致。歌词文本如果来自网页复制连字符可能是全角或长划线–不是 ASCII 的-。正则只匹配-遇到全角字符就匹配不到。处理方式是在清洗前做字符归一化text text.replace(, -).replace(–, -).replace(—, -)第二个坑是finditer的顺序。如果使用多个正则分别匹配再手动排序容易因为str.index()误判位置。推荐的做法是像上面一样把多个分支写进同一个正则利用finditer天然按位置返回结果。第三个坑是空格连接的单字母序列。\b[A-Za-z](?:\s[A-Za-z])\b看起来能匹配D to the E的变体但也会把普通句子里的a b c这种零散单字母当成拼写结构。实际项目中要结合文本特点收紧规则宁可少匹配也不要误报。5. 词频、说话人分布与可视化5.1 说话人标记解析在前面parse_line的基础上逐行累加说话人次数from collections import Counter speaker_counter Counter() for line in lines: speaker, lyric parse_line(line) if speaker: speaker_counter[speaker] 1这里使用Counter而不是普通字典是因为Counter自带most_common方法后续输出排序更方便。5.2 词频统计与停用词处理停用词表不需要追求通用而是要根据当前文本调整。歌词里的the、to、a对主题分析没有帮助可以直接过滤STOPWORDS {the, to, a, an, of, and, or, in, on, for, with, is, are, it, so, you} word_counter Counter() for line in lines: _, lyric parse_line(line) for word in tokenize(clean_for_words(lyric)): if len(word) 2 and word not in STOPWORDS: word_counter[word] 1注意len(word) 2这个过滤条件。如果没有它D to the E清洗后产生的单字母会把整个词频榜单占满演示数据里尤其明显。5.3 用 matplotlib 输出可视化词频结果适合用柱状图展示import matplotlib.pyplot as plt def draw_word_freq(counter: Counter, top_n10, outputoutput/word_freq.png): items counter.most_common(top_n) labels [word for word, _ in items] counts [count for _, count in items] plt.figure(figsize(8, 5)) plt.bar(labels, counts) plt.title(Top Words in Demo Lyrics) plt.tight_layout() plt.savefig(output, dpi150) plt.close()保存成图片而不是直接plt.show()是为了让脚本在无图形界面的环境下也能运行适合服务器部署和批处理。5.4 中韩文字体问题的处理如果图表标题或歌词文本包含中文、韩文默认字体很可能显示成方块。处理方式是根据操作系统设置字体import platform system platform.system() if system Windows: plt.rcParams[font.family] Malgun Gothic elif system Darwin: plt.rcParams[font.family] AppleGothic else: plt.rcParams[font.family] Noto Sans CJK KR plt.rcParams[axes.unicode_minus] Falseaxes.unicode_minus设置为False是为了避免坐标轴负号显示成方块。实际项目里如果担心字体不稳定可以在图表标题和标签中统一使用英文正文说明再使用中文。6. 运行验证与结果检查6.1 main 函数的组织方式把前面所有函数汇总到analyze_lyrics.py中main函数负责读取数据、统计、输出和绘图import json import csv def main(): with open(data/lyrics.txt, encodingutf-8-sig) as f: lines [line.strip() for line in f if line.strip()] speaker_counter Counter() word_counter Counter()
返回列表