news 2026/8/31 3:22:01

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用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用于可视化。文本处理部分使用标准库recollectionsjsoncsv,不需要额外安装。

推荐用虚拟环境隔离依赖:

python -m venv venv source venv/bin/activate

Windows 下激活命令是:

venv\Scripts\activate

然后安装matplotlib

pip 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.txt

lyrics_analysis/ ├── data/ │ └── lyrics.txt ├── output/ ├── analyze_lyrics.py └── requirements.txt

requirements.txt内容:

matplotlib>=3.5

data/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 test

2.3 样例数据的编码说明

歌词文本包含中文、韩文和英文,文件必须统一使用 UTF-8 编码。在 Windows 上用记事本新建文件时,默认可能是 GBK,写入代码后容易出现UnicodeDecodeError

推荐在代码里读取文件时显式指定编码:

with open("data/lyrics.txt", encoding="utf-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,s
You 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](?:\s+to\s+the\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](?:\s+to\s+the\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 EL 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 词频统计与停用词处理

停用词表不需要追求通用,而是要根据当前文本调整。歌词里的thetoa对主题分析没有帮助,可以直接过滤:

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_n=10, output="output/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, dpi=150) 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"] = False

axes.unicode_minus设置为False,是为了避免坐标轴负号显示成方块。实际项目里,如果担心字体不稳定,可以在图表标题和标签中统一使用英文,正文说明再使用中文。

6. 运行验证与结果检查

6.1 main 函数的组织方式

把前面所有函数汇总到analyze_lyrics.py中,main函数负责读取数据、统计、输出和绘图:

import json import csv def main(): with open("data/lyrics.txt", encoding="utf-8-sig") as f: lines = [line.strip() for line in f if line.strip()] speaker_counter = Counter() word_counter = Counter()
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/31 3:20:24

SHP点数据全流程处理:坐标校验、属性清洗与转3DTiles、CAD实操指南

简介&#xff1a;本资源为全国108288个小型矿产点位的高精度矢量SHP数据集&#xff0c;面向地质勘查、矿产资源管理、GIS空间分析及环境评估等领域的科研人员、高校师生与行业从业者&#xff0c;解决矿产空间分布建模、资源潜力评价与规划决策中基础地理数据缺失问题。压缩包共…

作者头像 李华
网站建设 2026/8/31 3:20:07

文件存储服务器配置指南:NFS、Samba与MinIO实战

在日常开发中&#xff0c;很多团队第一次认真考虑“文件存储服务器”这个词&#xff0c;不是因为这个概念新&#xff0c;而是业务把问题逼到了面前&#xff1a;应用从单机拆成多服务之后&#xff0c;后台上传的图片、日志文件、导出报表散落在不同机器&#xff0c;想统一管理却…

作者头像 李华
网站建设 2026/8/31 3:19:59

用Python验证8.14预测:GitHub Release日期核对实战

关于“8.14预测”&#xff0c;现在能看到的说法大致分三种&#xff1a;确定型、猜测型和引流型。确定型通常引用官方公告或仓库 Release 页面&#xff0c;猜测型来自“根据以往规律推测”&#xff0c;引流型则只有聊天截图或一句话预告&#xff0c;没有任何可核对的原始信息。做…

作者头像 李华
网站建设 2026/8/31 3:19:14

Cloudflare防护AI爬虫实战:从UA识别到WAF拦截

打开你自己的 Cloudflare 后台&#xff0c;先别急着看性能面板。去 Security → Bots 的流量视图里待两分钟&#xff0c;你会看到一组让人疑惑的曲线&#xff1a;明明产品没有迎来新用户&#xff0c;请求量却一直不低&#xff1b;UV 没变&#xff0c;但某几个页面每天都有规律的…

作者头像 李华
网站建设 2026/8/31 3:17:26

京广线一日双检与散装大巡检:可落地的巡检数据化方案

近期不少线路都在提升巡检频次&#xff0c;尤其像京广线这样运行时间长、运输压力大的干线&#xff0c;单纯依靠“静态台账 月度检查”已经很难覆盖风险变化。把“一日双检”真正执行到位&#xff0c;靠的不是口号&#xff0c;而是把检查项、人员编组、记录方式、问题闭环串成…

作者头像 李华
网站建设 2026/8/31 3:16:46

Agent Skill实战:用DeepSeek Harness为AI应用装上专业操作手册

最近做 AI 应用开发的朋友&#xff0c;大概率会遇到一个尴尬场景&#xff1a;大模型的“脑子”很聪明&#xff0c;但让它正经完成一件专业工作&#xff0c;结果却经常一言难尽。让它写周报&#xff0c;它写出的是流水账&#xff1b;让它做 PPT&#xff0c;它产出的是空话合集&a…

作者头像 李华