尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python实战:歌单数据清洗与相似度推荐全流程解析

Python实战:歌单数据清洗与相似度推荐全流程解析 各位做数据分析和推荐系统的朋友不知道你们有没有遇到过这样的场景辛辛苦苦从音乐平台扒下来一份歌单想分析一下里面的风格构成、音频特征分布甚至想做个简单的相似推荐结果发现数据格式七零八落有的字段缺失有的标签混乱处理起来非常头疼。这篇文章我想围绕一个很有意思的实战案例展开——假设我们现在拿到了一个名为“日推歌单”的音乐集合里面包含类似“Turbo Slap”“建模脸の小曲”“浩辰走路の小曲”这样的碎片化标签还有一句非常抓耳的评论“谁说没有完美犯罪”。这些信息看起来零散但其实非常有价值它既代表了真实的用户生成内容UGC也包含了个性化推荐的典型特征。本文会从零开始用 Python 完整拆解一份歌单数据从清洗、特征提取到相似度推荐的全流程。无论是想入门数据分析还是打算做音乐推荐系统的同学都能在这篇文章里找到可以直接复用的代码和思路。1. 背景与核心概念在开始写代码之前我们先花点时间搞清楚几个核心概念。这样才能明白后面每一步到底在做什么为什么要这么做。1.1 什么是歌单数据歌单是音乐平台上用户或系统根据某种主题、场景、情绪、风格组织起来的歌曲集合。比如“日推歌单”通常是平台每日根据用户历史行为生成的一份个性化推荐列表。而像“Turbo Slap”“建模脸の小曲”这种命名方式其实是用户自建歌单时常用的“氛围标签”它不代表一个确切的流派而是代表一种场景感。从技术角度来说一份歌单数据通常包含以下信息歌曲基础信息歌曲名、歌手、专辑、时长、发行时间。音频特征BPM每分钟节拍数、能量、声学度、舞蹈性、愉悦度等。标签信息歌单标题中的风格词、情绪词、场景词。用户行为数据播放量、收藏量、分享量、评论歌词。值得注意的是不同的数据来源字段差异极大。有的接口会返回完整的音频特征有的只提供标签文本还有的只有歌曲 ID 列表。我们这篇文章会采用一个“中间状态”的模拟数据也就是一部分字段完整、一部分字段缺失、还带有很多噪音标签的数据这样更能模拟真实开发场景。1.2 为什么要清洗歌单数据很多人在拿到数据后第一件事就是跑模型这其实是个容易踩坑的习惯。真实世界的数据几乎没有“干净”的标签里混入了 emoji、特殊符号、外文同一个意思的表达方式多种多样比如“电音”“电子”“Edm”其实很接近歌曲名和歌手名可能有重复或大小写不一致部分音频特征字段为空。如果不做数据清洗后面的统计分析和推荐效果都会大打折扣。所以我们会把清洗作为整个流程中最重要的一步。1.3 相似度推荐的基本思想推荐系统的核心是“找到和你喜欢的东西相似的东西”。实现这个目标的方式有很多种基于内容利用歌曲本身的属性风格、BPM、能量等计算相似度。基于协同过滤利用“和你品味相似的用户”的行为来做推荐。基于规则比如平台运营定义的“如果喜欢 A那可能喜欢 B”。在这篇教程里我们重点做基于内容Content-Based的推荐因为它最适合歌单这种小规模、标签化的数据场景。我们会把歌单里的标签文本转换成向量用余弦相似度Cosine Similarity来计算歌曲之间的相似程度。2. 环境准备与版本说明在写代码前先把环境准备好。版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路。2.1 软件环境操作系统Windows 10/11、macOS 或 Linux 均可。Python 版本推荐 3.9 及以上。IDE推荐 PyCharm 或者 VS Code。包管理工具pip 或 conda推荐使用虚拟环境隔离项目依赖。2.2 依赖库说明我们主要用到以下 Python 库库名用途pandas数据处理与结构化分析numpy数值计算jieba中文分词处理歌单标签文本scikit-learn文本向量化和相似度计算matplotlib / wordcloud可视化和词云制作requests如果需要从接口拉取数据用于网络请求虚拟环境创建命令如下python -m venv music_env source music_env/bin/activate # Windows 下为 music_env\Scripts\activate安装依赖pip install pandas numpy jieba scikit-learn matplotlib wordcloud requests如果你的网络环境比较特殊可以考虑使用国内镜像源比如清华或阿里云的 PyPI 镜像安装速度会快很多。例如pip install pandas -i https://pypi.tuna.tsinghua.edu.cn/simple2.3 示例数据准备为了让教程能实际运行我们构造一份模拟歌单数据。这里的数据完全为教学演示用途不代表任何真实歌单。数据格式如下字段名示例值说明song_id1001歌曲唯一标识titleNight Drive歌曲名artistTurbo Rhythm歌手albumNeon City专辑名duration_s245时长秒bpm128节拍速度energy0.87能量值0-1danceability0.79舞蹈性0-1tags电音动感夜跑歌单标签playlist_nameTurbo Slap歌单名lyric_snippet谁说没有完美犯罪歌词片段或评论后面我们会用代码生成这份数据并在此基础上做全部操作。3. 核心概念与代码原理解析进入代码之前我们把几个核心知识点拆解开这样后面看代码不会晕。3.1 中文分词与标签标准化歌单标签里往往有大量口语化表达比如“建模脸の小曲”“浩辰走路の小曲”。其中“の”是日文假名在中文标签里经常被用来增加氛围感。我们需要把这些特殊符号替换掉再用分词工具把标签切成有意义的词。jieba 是 Python 生态里最常用的中文分词工具。看一个例子import jieba text 建模脸の小曲 text text.replace(の, ).replace(, ).replace( , ) words jieba.lcut(text) print(words)运行结果可能类似于[建模, 脸, 小曲]通过分词我们可以把一条标签拆成多个可计算的特征词为后面的向量化做准备。3.2 文本向量化计算机无法直接理解文字我们需要把文本转换成数字向量。这里使用的是 TF-IDF词频-逆文档频率方法。TFTerm Frequency词频表示某个词在文本中出现的次数。IDFInverse Document Frequency逆文档频率表示某个词在所有文本中是否稀有。TF-IDF 的思想是一个词在当前文本中出现得多但在其他文本中出现得少那它对于当前文本就越重要。scikit-learn 提供了TfidfVectorizer来直接完成这个转换from sklearn.feature_extraction.text import TfidfVectorizer docs [电音 动感 夜跑, 轻音乐 安静 阅读, 电音 狂欢] vectorizer TfidfVectorizer() tfidf_matrix vectorizer.fit_transform(docs) print(tfidf_matrix.toarray())输出矩阵的每一行对应一篇文本每一列对应一个词数值就是 TF-IDF 权重。有了这个矩阵我们就可以计算任意两首歌曲之间的相似度了。3.3 余弦相似度余弦相似度通过计算两个向量在空间中的夹角余弦值来判断它们的相似程度。计算公式如下similarity (A · B) / (||A|| ||B||)其中A·B是向量内积||A||是向量的模长。余弦相似度的取值范围是 -1 到 1值越接近 1说明两个向量方向越一致也就是越相似。在代码中我们可以直接使用 sklearn 的cosine_similarity函数from sklearn.metrics.pairwise import cosine_similarity similarity_matrix cosine_similarity(tfidf_matrix) print(similarity_matrix)这样我们就得到了一个 N×N 的相似度矩阵矩阵的第 i 行第 j 列表示第 i 首歌曲和第 j 首歌曲的相似度。3.4 音频特征的归一化除了文本标签音频特征如 BPM、能量、舞蹈性也需要参与相似度计算。但这些特征的量纲差异很大BPM 可能是 60 到 180而能量只在 0 到 1 之间。如果不做归一化BPM 会主导整个相似度计算。常用的归一化方法是 Min-Max 标准化x_scaled (x - min(x)) / (max(x) - min(x))用 pandas 可以很方便地实现df[bpm_scaled] (df[bpm] - df[bpm].min()) / (df[bpm].max() - df[bpm].min())归一化之后所有特征都落在 [0,1] 区间可以比较公平地参与加权运算。4. 完整实战案例从歌单标签到相似推荐接下来进入核心实战部分。我们会按照下面几个步骤进行生成模拟数据。清洗标签数据。提取文本特征。融合音频特征。计算相似度并输出推荐结果。做一个简单的可视化。4.1 创建项目结构建议先建一个项目目录结构如下music_recommend/ ├── data/ │ └── playlist.csv ├── output/ ├── main.py └── requirements.txtdata目录存放原始数据output目录存放输出结果main.py是主脚本requirements.txt是依赖清单。4.2 生成模拟歌单数据我们先用 pandas 生成一份包含 10 首歌曲的模拟数据。这部分是为了让案例可复现真实场景中你应该从数据库或接口读取。# main.py import pandas as pd import numpy as np # 设置随机种子保证实验结果可复现 np.random.seed(42) data { song_id: [1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010], title: [Night Drive, Neon Glow, Sakura Step, Midnight Chase, Ocean Breeze, Electric Love, Golden Hour, Silent Star, Turbo Rush, Dream Walker], artist: [Turbo Rhythm, Neon Cat, Mochi, 浩辰, Wave Lab, Synth Boy, 白日梦乐队, Echo, Turbo Rhythm, Mochi], bpm: [128, 115, 98, 140, 80, 124, 105, 72, 138, 88], energy: [0.87, 0.67, 0.45, 0.91, 0.32, 0.78, 0.55, 0.28, 0.93, 0.41], danceability: [0.79, 0.68, 0.55, 0.82, 0.40, 0.74, 0.60, 0.33, 0.85, 0.50], tags: [ Turbo Slap, 电音, 动感, 夜跑, 霓虹, 电子, 城市, 夜晚, 建模脸の小曲, 轻快, 可爱, 浩辰走路の小曲, 节奏, 步伐, 海边, 安静, 放松, 夏日, 电音, 恋爱, 甜蜜, 日落, 温柔, 吉他, 星空, 安静, 治愈, 纯音乐, Turbo Slap, 极限, 高能, 梦境, 柔美, 电子, 轻音乐 ], playlist_name: [ Turbo Slap, 城市霓虹, 建模脸の小曲, 浩辰走路の小曲, 海边日记, 恋爱循环, 日落收藏家, 星空安眠曲, Turbo Slap, 梦境漫步 ], lyric_snippet: [ 谁说没有完美犯罪, , 眨眼的瞬间, 一步两步往前走, 海浪拍打着记忆, , 金色碎片落在肩头, 星星不说话, , 梦里有光 ] } df pd.DataFrame(data) df.to_csv(data/playlist.csv, indexFalse, encodingutf-8-sig) print(数据生成完成共, len(df), 首歌)运行这段代码后你会在data目录下得到一个playlist.csv文件。4.3 清洗标签数据真实场景里标签数据通常非常混乱。我们现在把标签字段取出来做清洗。清洗规则把中文标点符号替换为英文逗号。把日文“の”替换为空格。去除空白字符。按逗号拆分去掉空字符串。每个标签去重。import re def clean_tags(tag_str): if not isinstance(tag_str, str): return [] # 统一标点 tag_str tag_str.replace(, ,).replace(、, ,).replace(;, ,) tag_str tag_str.replace( , ,) # 处理特殊符号 tag_str tag_str.replace(の, ) # 按逗号分割 tags tag_str.split(,) # 去除空白 tags [t.strip() for t in tags if t.strip()] # 去重保持顺序 seen set() result [] for t in tags: if t not in seen: seen.add(t) result.append(t) return result df[tags_clean] df[tags].apply(clean_tags) print(df[[song_id, tags_clean]].head())清洗后的结果类似这样song_idtags_clean1001[Turbo Slap, 电音, 动感, 夜跑]1002[霓虹, 电子, 城市, 夜晚]4.4 构建标签文本并向量化清洗完之后我们把每首歌的标签列表重新拼接成字符串交给 TfidfVectorizer 做向量化。注意这里只对标签文本做向量化音频特征会在后面单独处理。from sklearn.feature_extraction.text import TfidfVectorizer # 将标签列表拼接成空格分隔的字符串 df[tags_text] df[tags_clean].apply(lambda x: .join(x)) # 这里加入歌曲名和歌单名让特征更丰富 df[text_all] df[tags_text] df[title] df[playlist_name] vectorizer TfidfVectorizer(token_patternr(?u)\b\w\b) tfidf_matrix vectorizer.fit_transform(df[text_all]) print(特征矩阵形状:, tfidf_matrix.shape)tfidf_matrix的行数是歌曲数列数是词典大小。数值越大说明某个词对某首歌越重要。4.5 融合音频特征文本特征刻画的是“风格氛围”音频特征刻画的是“听感”。我们把它们拼接起来构成最终的特征向量。from sklearn.preprocessing import MinMaxScaler # 选择需要归一化的音频特征 audio_features [bpm, energy, danceability] scaler MinMaxScaler() audio_scaled scaler.fit_transform(df[audio_features]) # 转成 DataFrame audio_df pd.DataFrame(audio_scaled, columnsaudio_features) # 将稀疏矩阵转成稠密数组示例数据量小可以直接转 text_dense tfidf_matrix.toarray() # 拼接特征 import numpy as np combined_features np.hstack([text_dense, audio_df.values]) print(组合特征形状:, combined_features.shape)这里需要注意TF-IDF 特征和音频特征的取值范围不同。如果希望音频特征影响更大可以在拼接时给不同特征设置权重比如把文本特征乘以 0.7音频特征乘以 0.3。这是推荐系统中常用的加权融合思路。4.6 计算相似度矩阵特征准备好后计算所有歌曲两两之间的余弦相似度。from sklearn.metrics.pairwise import cosine_similarity similarity_matrix cosine_similarity(combined_features) # 转成 DataFrame 方便查看 similarity_df pd.DataFrame( similarity_matrix, indexdf[title], columnsdf[title] ) print(similarity_df.round(3))输出结果是一个 10×10 的矩阵。值越接近 1表示两首歌越相似。对角线是自己的相似度恒为 1。4.7 针对指定歌曲生成推荐假设用户正在播放“Night Drive”我们要从歌单中找出最相似的 3 首推荐给他。def recommend(target_title, sim_df, top_n3): if target_title not in sim_df.columns: return 歌曲不在数据集中 scores sim_df[target_title].drop(indextarget_title) scores scores.sort_values(ascendingFalse) return scores.head(top_n) target Night Drive recommendations recommend(target, similarity_df, top_n3) print(基于《 target 》的推荐结果) for song, score in recommendations.items(): print(f{song}: 相似度 {score:.4f})运行后你会看到类似这样的输出基于《Night Drive》的推荐结果 Turbo Rush: 相似度 0.9275 Electric Love: 相似度 0.8312 Neon Glow: 相似度 0.8127这个结果完全由标签和音频特征共同决定。因为“Turbo Rush”和“Night Drive”都带有“Turbo Slap”标签而且 BPM 和能量都比较高所以排在第一是很合理的。4.8 从歌词片段中挖掘情绪特征标题里的“谁说没有完美犯罪”其实是一句歌词或评论。歌词往往包含情绪信息可以用来作为推荐的辅助信号。这里我们做一个简单的关键词匹配判断每首歌的情绪倾向。# 简单情绪词典 emotion_dict { 快乐: [爱, 甜蜜, 恋爱, 微笑, 阳光], 紧张: [追逐, 犯罪, 极限, 高能], 安静: [安静, 治愈, 星空, 眠, 轻音乐], 怀旧: [落叶, 记忆, 黄昏, 古老] } def infer_emotion(lyric, title, tags_text): text f{lyric} {title} {tags_text} for emotion, words in emotion_dict.items(): for w in words: if w in text: return emotion return 未知 df[emotion] df.apply( lambda row: infer_emotion(row[lyric_snippet], row[title], row[tags_text]), axis1 ) print(df[[title, emotion]])通过这一步我们可以把歌单按照情绪聚类比如“紧张”类的歌曲风格更偏向高 BPM、高能量。这个情绪维度在后续做推荐理由解释时非常有用。4.9 可视化展示为了让分析结果更直观我们可以做两个简单的可视化歌单标签词云和特征热力图。词云代码from wordcloud import WordCloud import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # 解决中文显示问题 plt.rcParams[axes.unicode_minus] False all_tags .join(df[tags_text]) wordcloud WordCloud( width800, height400, background_colorwhite, font_pathC:/Windows/Fonts/simhei.ttf # Windows 下中文字体路径 ).generate(all_tags) plt.figure(figsize(10, 5)) plt.imshow(wordcloud, interpolationbilinear) plt.axis(off) plt.title(歌单标签词云) plt.savefig(output/tag_wordcloud.png, dpi300, bbox_inchestight) plt.show()如果你的系统没有simhei.ttf可以换成PingFang.ttcmacOS或在 Linux 上安装中文字体。也可以在WordCloud中不指定font_path但那样中文可能显示为方块。特征热力图代码import seaborn as sns plt.figure(figsize(8, 6)) sns.heatmap(audio_df, annotTrue, xticklabelsaudio_features, yticklabelsdf[title]) plt.title(音频特征归一化热力图) plt.savefig(output/audio_heatmap.png, dpi300, bbox_inchestight) plt.show()从热力图中我们可以很直观地看出哪些歌曲的 BPM 高、哪些歌曲的舞蹈性强。比如“Turbo Rush”和“Night Drive”在 BPM 和能量维度上分布接近和相似度计算的结果一致。5. 常见问题与排查思路在实际运行这套代码时大家可能会遇到一些报错或结果不符合预期的情况。我把常见问题整理成表格方便快速排查。问题现象常见原因解决思路jieba 分词出现乱码文件编码不是 UTF-8读取文件时指定encodingutf-8或utf-8-sig词云中文显示为方块缺少中文字体指定font_path为系统中文字体路径相似度结果全是 0标签之间没有共同词且音频特征权重过低检查标签清洗是否合理适当提高音频特征权重特征矩阵形状为 (N, 0)分词后所有单词都被过滤调整token_pattern或检查文本是否为空BPM 与舞蹈性数值差异过大导致推荐偏差未做归一化使用MinMaxScaler对特征做归一化运行pip install慢或失败网络原因或镜像源不稳定切换国内镜像源或使用 conda 安装相似度矩阵对角线都是 1其他值全部很小标签高度稀疏尝试用 Word2Vec 或 BERT 生成文本向量而不是仅依赖 TF-IDF歌词字段为空导致情绪判断为未知数据缺失使用标签和歌名兜底或构造默认情绪这里特别想提醒一点token_pattern这个参数很容易被忽略。默认的正则表达式只匹配字母和数字中文是可以匹配的但如果你用了日文假名“の”这种字符默认模式可能会把它过滤掉。所以在清洗标签时提前把“の”替换成空格是非常有必要的。另外TF-IDF 对于短文本的相似度计算有一个天然的局限如果两首歌的标签完全没有重合比如“电音”和“轻音乐”它们的相似度会非常低即使实际听感上可能有一部分用户会同时喜欢。解决思路有两个增加外部知识比如使用预训练的词向量Word2Vec、fastText把词映射到语义空间。在标签文本中补充歌曲名、专辑名、歌单名增加文本长度和信息量就像我们在第 4.4 节做的那样。如果你发现自己算出来的相似度矩阵过于稀疏建议优先尝试第 2 种方案成本最低效果立竿见影。6. 最佳实践与工程建议代码能跑通只是第一步。如果要应用到真实项目或提交到生产环境有几个工程层面的建议值得参考。6.1 语义化标签标准化歌单标签的标准化远不止清洗特殊符号这么简单。我建议按照下面的流程做标签治理同义词合并将“电音”“电子”“Edm”合并成统一标签。场景标签扩展把“夜跑”“加班”“睡前”这类场景词映射到情绪维度和时间维度。多语言归一日文、英文、数字统一转成小写或统一成中文标签。层级化建立“风格 场景 情绪”的三层标签体系便于不同维度的推荐。这一步做得好不好直接决定推荐结果的质量。很多推荐效果差的项目问题都不是出在模型上而是出在标签基础上。6.2 特征融合的权重调优在 4.5 节里我们简单地把文本特征和音频特征拼接起来。实际工程中这两个特征的量纲和分布差异比较大建议不要直接拼接而是先做标准化再按权重融合。一个可行的调优思路先用默认权重跑一遍推荐。邀请少量用户对推荐结果排序打分。根据反馈调整文本特征和音频特征的权重。多轮迭代后找到最优权重组合。如果数据量足够大也可以用机器学习模型学习特征权重但这已经是另一个层面的问题了。6.3 冷启动问题的应对对于一首新上架、还没有用户行为数据的歌曲协同过滤算法完全失效这时候基于内容标签、音频特征的推荐是最好的策略。工程落地时可以这样做给新歌打上基础标签风格、BPM、语言、场景。直接用内容特征计算它和已有歌曲的相似度。在相似度 Top K 歌曲的曝光位置中插入新歌观察表现。6.4 推荐结果的“惊喜度”控制如果只按相似度推荐用户很快会陷入“信息茧房”——听到的永远是一个风格的歌。这正是“谁说没有完美犯罪”这句评论里暗含的那种打破预期的需求。实际工程中我会在推荐结果里加入一小部分“探索性推荐”90% 的结果来自高相似度歌曲。10% 的结果来自随机采样或跨风格推荐。这样既能保证推荐的相关性又能保持用户的新鲜感。这也是很多音乐平台“日推”能给人惊喜的底层逻辑之一。6.5 可解释性设计用户如果看到“因为你也喜欢 Night Drive所以推荐 Turbo Rush”这种理由接受度会比单纯给出歌曲列表高很多。实现方式保存每首歌的 Top 3 相似歌曲及相似度。在推荐理由中列举重叠标签例如“都带有 Turbo Slap 标签”“BPM 接近”。如果使用了情绪分析可以补充“因为你最近偏好高能紧张氛围”。6.6 数据安全与版权边界最后这一点很重要。如果你是爬取音乐平台数据做个人学习问题不大。但一旦涉及商用就必须注意只使用公开接口或已获授权的数据。歌词文本涉及版权尽量不要大规模存储和展示。用户行为数据属于个人隐私脱敏后才能用于分析。推荐系统的评估和上线需要在测试环境充分验证避免线上事故。7. 总结与下一步学习建议这篇文章从一个看似零散的音乐歌单标题出发带大家完整走了一遍数据分析与推荐系统的入门流程学会了用 pandas 处理结构化歌单数据。学会了用 jieba 做中文标签的分词与清洗。掌握了 TF-IDF 文本向量化和余弦相似度的原理与实现。了解了如何融合音频特征提升相似度计算的准确性。完成了一个可运行的推荐函数能根据某一首目标歌曲输出 Top N 相似推荐。通过词云和热力图学会了用可视化的方式理解歌单内容。如果你对这套代码产生了兴趣下一步可以考虑这几个方向把 CSV 数据换成真实的音乐平台 API 数据处理更复杂的嵌套结构。用 Word2Vec 或 Sentence-BERT 替代 TF-IDF提升语义理解能力。加入协同过滤算法把“相似的人”也纳入推荐逻辑。做一个基于 Streamlit 的简单 Web 应用让别人也能输入歌单标题、看到推荐结果。推荐系统是一个入门容易、做好很难的方向。你可以先从今天这个最朴素的基于内容的推荐开始逐步加入更多特征、更多数据、更多算法。遇到问题的时候优先回到数据本身找原因——大部分推荐结果不好都不是模型的问题而是数据特征没做好。如果这篇文章对你有帮助可以收藏备用也欢迎在评论区分享你自己处理歌单数据时踩过的坑。下篇文章我们可以聊聊如何用同样的思路做更完整的歌曲标签体系设计。
返回列表