最近在分析音乐榜单数据时,发现很多朋友对一首歌的全球热度变化趋势很感兴趣,尤其是像 Olivia Rodrigo 的《Good 4 u》这样的现象级单曲。它不仅在 Spotify 全球日榜上掀起波澜,更在 Billboard Hot 100 榜单上上演了一场精彩的“位次推移”大戏。本文将带你从零开始,完整复现如何获取、处理并可视化这类榜单数据,最终生成一张清晰展示歌曲排名随时间变化的推移图。无论你是数据爱好者、音乐行业从业者,还是想学习 Python 数据分析的开发者,都能从这篇实战教程中获得一套可直接复用的代码和方法。
1. 项目背景与核心概念
在音乐流媒体时代,一首歌的成功与否,其在不同榜单上的排名变化是最直观的衡量标准之一。我们常听到“空降冠军”、“逆袭回升”、“高位徘徊”等说法,这些词汇描述的正是歌曲的“位次推移”(Chart Movement)。
- Billboard Hot 100:美国乃至全球最具权威性的单曲排行榜,综合了实体销量、数字下载、电台点播和流媒体播放量(包括 Spotify、Apple Music 等)数据。一首歌在 Hot 100 上的排名变化,反映了其在美国市场的综合热度走势。
- Spotify 全球日榜:代表了全球最大的音乐流媒体平台每日的播放量排名,是观察歌曲即时、全球性流媒体热度的绝佳窗口。其数据波动往往更加频繁和剧烈。
- 位次推移分析:通过追踪一首歌在不同榜单上每日或每周的排名,绘制出其排名随时间变化的折线图。上升曲线代表热度攀升,下降曲线则代表热度减退。对比不同榜单的曲线,可以分析歌曲在不同市场或数据维度(如流媒体 vs 综合热度)的表现差异。
本次实战,我们将以 Olivia Rodrigo 的《Good 4 u》为例,目标是获取其在某一段时间内(例如发歌后两个月)的 Spotify 全球日榜和 Billboard Hot 100 周榜排名数据,并进行可视化分析。
2. 环境准备与工具说明
本项目主要使用 Python 进行数据抓取、处理和可视化。请确保你的开发环境已就绪。
核心工具与库:
- 编程语言:Python 3.8 及以上版本。
- 开发环境:推荐使用 Jupyter Notebook 或 VS Code 进行交互式开发和调试。
- 关键库:
requests/selenium:用于从网站抓取(爬取)榜单数据。requests适用于静态页面,selenium适用于需要 JavaScript 渲染的动态页面。pandas:数据处理和分析的核心库,用于清洗、整合和操作数据表格。matplotlib&seaborn:数据可视化库,用于绘制精美的位次推移图。BeautifulSoup4/lxml:HTML 解析库,配合requests使用来提取网页中的特定数据。
- 数据来源声明:请注意,直接抓取 Billboard 或 Spotify 官方数据可能违反其服务条款。为了教学目的,我们将使用公开的、允许访问的榜单数据存档网站(如
kworb.net)或模拟手动查找公开数据集的方式进行。在实际应用中,请务必遵守相关网站的使用条款,考虑使用官方 API(如 Spotify Web API)或购买正规数据源。
安装依赖:打开终端或命令提示符,执行以下命令安装必要的库:
pip install requests pandas matplotlib seaborn beautifulsoup4 lxml如果目标网站是动态加载的,你可能还需要安装selenium和对应的 WebDriver:
pip install selenium # 同时需要下载 ChromeDriver 或 GeckoDriver 并配置到系统 PATH3. 数据获取策略与原理拆解
获取准确的榜单历史数据是本项目的第一步,也是最具挑战性的一环。主要有以下几种思路:
3.1 思路一:从聚合数据网站抓取(以 kworb.net 为例)
kworb.net是一个知名的音乐数据聚合网站,它整理了 Billboard、Spotify、iTunes 等平台的历史榜单数据,并以结构化的表格形式呈现,非常适合爬虫初学者。
工作原理:
- 分析页面结构:使用浏览器开发者工具(F12)查看目标页面的 HTML 结构。例如,查找
https://kworb.net/spotify/artist/1McMsnEElThX1knmY4oliG.html(Olivia Rodrigo 的 Spotify 页面)或特定歌曲的榜单详情页。 - 定位数据表格:榜单数据通常存放在
<table>标签内。我们需要找到这个表格的 HTML 标识(如id或class)。 - 发送HTTP请求:使用
requests.get()获取网页的 HTML 源代码。 - 解析与提取:使用
BeautifulSoup解析 HTML,定位到目标表格,然后用pandas的read_html()函数直接将表格转换为 DataFrame,或者手动遍历表格行(<tr>)和单元格(<td>)提取数据。
优点:数据集中,易于解析,免去了处理复杂 API 的麻烦。缺点:依赖第三方网站,其数据更新频率和准确性无法绝对保证;需遵守该网站的robots.txt协议。
3.2 思路二:使用官方API(推荐用于生产环境)
- Spotify Web API:提供了丰富的端点来获取艺术家、专辑、歌曲的详细信息,但不直接提供历史日榜排名。你可以获取歌曲的当前流行度指数(
popularity,0-100),但这不是排名。历史流媒体数据可能需要商业权限。 - Billboard API:Billboard 官方有提供部分数据的 API,但通常也是商业接口。对于个人学习和非商业用途,直接爬取官网历史榜单页面是更常见的做法(需谨慎评估法律风险)。
鉴于教学和可操作性,下文将主要基于思路一(kworb.net)进行演示,并强调数据获取的伦理和技术细节。
4. 完整实战案例:获取与可视化《Good 4 u》排名数据
我们将流程分解为:数据抓取、数据清洗、数据整合、可视化四个步骤。
4.1 步骤一:抓取 Spotify 全球日榜排名(模拟)
假设我们在kworb.net上找到了《Good 4 u》的 Spotify 流媒体历史页面(URL 需实际查找)。这里我们模拟一个抓取过程。
import requests from bs4 import BeautifulSoup import pandas as pd import time # 目标URL(示例,实际URL需要你根据网站结构寻找) # 假设结构为:https://kworb.net/spotify/song/xxxxx.html song_id = “6RUKPb4LETWmmr3iAEQktW” # Good 4 u 的Spotify ID (示例,非真实) url = f“https://kworb.net/spotify/song/{song_id}.html” headers = { ‘User-Agent’: ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36’ # 模拟浏览器访问 } try: response = requests.get(url, headers=headers) response.raise_for_status() # 检查请求是否成功 soup = BeautifulSoup(response.content, ‘html.parser’) # 假设日榜数据在第一个表格里,我们需要根据实际页面调整选择器 # 使用浏览器开发者工具精确找到表格的id或class table = soup.find(‘table’, {‘class’: ‘wikitable’}) # 示例class,kworb常用 if table: # 使用pandas直接读取HTML表格 df_list = pd.read_html(str(table)) spotify_df = df_list[0] # 假设第一个表格是我们需要的 print(“Spotify 数据抓取成功,前5行:”) print(spotify_df.head()) else: print(“未找到榜单表格,请检查页面结构或选择器。”) # 可以尝试其他选择器,如 soup.find_all(‘table’)[1] except requests.exceptions.RequestException as e: print(f“网络请求出错:{e}”) except Exception as e: print(f“解析过程出错:{e}”) # 为了演示,我们创建一份模拟的Spotify全球日榜数据(2023年某两周) import numpy as np dates = pd.date_range(‘2023-05-01’, periods=14, freq=‘D’) # 假设从5月1日开始 # 模拟排名:初期高位,随后下降,中期有小幅回升 spotify_ranks = [1, 1, 2, 3, 5, 8, 12, 15, 10, 8, 12, 18, 22, 25] spotify_daily_data = pd.DataFrame({ ‘Date’: dates, ‘Platform’: ‘Spotify Global’, ‘Rank’: spotify_ranks }) print(“\n模拟的 Spotify 全球日榜数据:”) print(spotify_daily_data)4.2 步骤二:抓取 Billboard Hot 100 周榜排名(模拟)
Billboard Hot 100 是周榜,数据频率与日榜不同。我们同样模拟抓取过程。
# 模拟从 Billboard 或 kworb 抓取 Hot 100 周榜数据 # 假设URL格式:https://kworb.net/bb/artist/oliviarodrigo.html 或歌曲特定页面 # 创建模拟的Billboard Hot 100周榜数据(同一时间段,周榜) # 周榜日期通常是每周六更新 billboard_dates = pd.date_range(‘2023-05-06’, periods=4, freq=‘7D’) # 5月6日,13日,20日,27日 # 模拟排名:空降冠军,随后位次变化 billboard_ranks = [1, 1, 2, 4] billboard_weekly_data = pd.DataFrame({ ‘Date’: billboard_dates, ‘Platform’: ‘Billboard Hot 100’, ‘Rank’: billboard_ranks }) print(“\n模拟的 Billboard Hot 100 周榜数据:”) print(billboard_weekly_data)4.3 步骤三:数据清洗与整合
将两个来源的数据合并,并统一格式,为绘图做准备。
# 合并两个数据集 combined_data = pd.concat([spotify_daily_data, billboard_weekly_data], ignore_index=True) # 确保日期列是 datetime 类型 combined_data[‘Date’] = pd.to_datetime(combined_data[‘Date’]) # 按平台和日期排序 combined_data = combined_data.sort_values([‘Platform’, ‘Date’]).reset_index(drop=True) # 检查数据 print(“\n合并后的数据:”) print(combined_data) print(f“\n数据概览:”) print(combined_data.info()) print(combined_data[‘Platform’].value_counts())4.4 步骤四:绘制位次推移图
使用matplotlib和seaborn绘制双线折线图,直观展示排名变化。
import matplotlib.pyplot as plt import seaborn as sns # 设置中文字体和图表样式(如果不需要中文可省略字体设置) # plt.rcParams[‘font.sans-serif’] = [‘SimHei’, ‘Arial’] # 用来正常显示中文标签 # plt.rcParams[‘axes.unicode_minus’] = False # 用来正常显示负号 sns.set_style(“whitegrid”) # 设置seaborn风格 plt.figure(figsize=(14, 8)) # 设置画布大小 # 为每个平台绘制折线 platforms = combined_data[‘Platform’].unique() colors = {‘Spotify Global’: ‘#1DB954’, ‘Billboard Hot 100’: ‘#FF6B6B’} # Spotify绿,Billboard红 markers = {‘Spotify Global’: ‘o’, ‘Billboard Hot 100’: ‘s’} # 圆圈和方块 for platform in platforms: platform_data = combined_data[combined_data[‘Platform’] == platform] plt.plot(platform_data[‘Date’], platform_data[‘Rank’], label=platform, color=colors.get(platform, ‘blue’), marker=markers.get(platform, ‘o’), linewidth=2.5, markersize=8) # 优化图表细节 plt.gca().invert_yaxis() # 反转Y轴,让排名1在顶部,更符合阅读习惯 plt.title(‘Olivia Rodrigo - “Good 4 u” 位次推移图\nSpotify全球日榜 vs Billboard Hot 100周榜’, fontsize=16, fontweight=‘bold’, pad=20) plt.xlabel(‘日期’, fontsize=12) plt.ylabel(‘排名 (数字越小越靠前)’, fontsize=12) plt.legend(title=‘榜单平台’, fontsize=11, title_fontsize=12) plt.grid(True, which=‘both’, linestyle=‘—’, linewidth=0.5, alpha=0.7) # 美化X轴日期格式 plt.gcf().autofmt_xdate() # 自动倾斜日期标签 # 添加数据标签(可选,数据点多时可能拥挤) # for platform in platforms: # platform_data = combined_data[combined_data[‘Platform’] == platform] # for x, y in zip(platform_data[‘Date’], platform_data[‘Rank’]): # plt.text(x, y+0.5, f’{int(y)}’, ha=‘center’, va=‘bottom’, fontsize=9, color=colors.get(platform)) plt.tight_layout() # 自动调整子图参数,使之填充整个图像区域 plt.show()预期结果:你将得到一张折线图。X轴是时间,Y轴是排名(顶部为第1名)。绿色折线(带圆圈标记)代表 Spotify 全球日榜,每日一个点,波动可能更频繁。红色折线(带方块标记)代表 Billboard Hot 100 周榜,每周一个点,趋势相对平滑。通过对比,你可以清晰看到:
- 歌曲发布初期在两个榜单均迅速登顶。
- Spotify 日榜排名可能因流媒体日活变化而更快出现下滑和波动。
- Billboard 周榜因综合了销量、电台等数据,排名下降可能相对缓慢,体现了更持久的综合热度。
5. 常见问题与排查思路
在实践过程中,你可能会遇到以下问题:
| 问题现象 | 可能原因 | 解决思路 |
|---|---|---|
requests.get()返回 403 或 404 错误 | 1. 网站有反爬机制(如验证 User-Agent)。 2. 页面需要登录或 JavaScript 加载。 3. URL 已失效或拼写错误。 | 1. 检查并完善headers,模拟真实浏览器。2. 尝试使用 selenium处理动态页面。3. 手动在浏览器中访问该 URL 确认有效性。 |
BeautifulSoup找不到表格 (find返回None) | 1. HTML 结构判断错误,表格的class或id不对。2. 数据可能是通过 JS 异步加载,初始 HTML 中没有。 | 1. 使用开发者工具仔细检查元素,尝试soup.find_all(‘table’)查看所有表格。2. 使用 selenium等待页面完全加载后再获取page_source。 |
pd.read_html()解析出错或返回空列表 | 1. 表格结构复杂,pandas无法自动识别。2. 网页编码问题。 | 1. 改用BeautifulSoup手动解析表格行 (tr) 和单元格 (td)。2. 指定 encoding参数,如response.content.decode(‘utf-8’)。 |
| 图表Y轴方向不符合习惯(排名1在底部) | 默认坐标系排名值越大位置越高。 | 使用plt.gca().invert_yaxis()反转Y轴。 |
| 两条曲线看起来不协调(一个点密一个点疏) | 数据频率不同(日榜 vs 周榜)。 | 这是正常现象,正好反映了不同榜单的更新频率。可以在图例或标题中说明。 |
| 数据抓取速度慢或被封IP | 高频请求触发了网站的反爬虫策略。 | 1. 在请求间添加随机延时:time.sleep(random.uniform(1, 3))。2. 使用代理IP池(高级)。 最重要:尊重 robots.txt,控制抓取频率,仅用于个人学习。 |
6. 最佳实践与工程建议
遵守法律法规与道德规范:
- 优先使用官方API:对于商业或长期项目,尽一切可能申请和使用 Spotify、Billboard 等平台的官方 API。这是最稳定、合法的方式。
- 尊重
robots.txt:在抓取任何网站前,访问https://目标网站/robots.txt,查看是否允许爬虫抓取目标路径。 - 限制请求频率:在代码中设置合理的延时(如每秒1次请求),避免对目标服务器造成负担。
- 明确数据用途:确保你的项目是用于个人学习、研究或教育,而非商业盈利。在成果中注明数据来源。
代码健壮性与可维护性:
- 异常处理:如示例所示,使用
try-except块包裹网络请求和解析代码,确保单次失败不会导致整个程序崩溃。 - 配置化:将 URL、请求头、文件保存路径等变量提取到配置文件或脚本开头,方便修改。
- 数据持久化:抓取到的数据应立即保存到本地文件(如 CSV、JSON 或 SQLite 数据库),避免每次分析都重新抓取。
# 保存数据 combined_data.to_csv(‘good4u_chart_history.csv’, index=False, encoding=‘utf-8-sig’) # 读取数据 df = pd.read_csv(‘good4u_chart_history.csv’, parse_dates=[‘Date’])- 函数封装:将数据抓取、清洗、绘图的代码分别封装成函数,提高代码复用性。
- 异常处理:如示例所示,使用
数据分析深度拓展:
- 计算在榜周数:统计歌曲在 Top 10、Top 50、Top 100 内停留的周数/天数。
- 计算排名变化幅度:计算每周或每日的排名升降名次。
- 多歌曲对比:在同一张图中绘制多位歌手或歌曲的排名曲线,进行横向对比。
- 关联其他数据:尝试寻找或抓取社交媒体讨论热度、音乐视频播放量等数据,与榜单排名做相关性分析。
可视化优化:
- 关键节点标注:在曲线上标注重要事件点,如“音乐视频发布”、“电台大力推广”、“获奖”等,分析事件对排名的影响。
- 使用渐变色:用颜色深浅表示时间先后或排名高低。
- 交互式图表:考虑使用
plotly库生成交互式图表,可以悬停查看具体日期的排名数值。
通过这个完整的项目,你不仅学会了如何分析一首歌的榜单位次推移,更掌握了一套从网页抓取、数据处理到可视化的通用数据分析流程。这套方法稍加修改,就可以用于分析任何你感兴趣的榜单数据,例如电影票房排名、应用商店排名、社交媒体热搜趋势等。数据就在那里,关键在于如何获取并从中提炼出有价值的洞察。