Python实战:Billboard榜单数据抓取与可视化分析 在实际音乐数据分析和可视化项目中我们经常需要处理像 Billboard Hot 100 这样的排行榜数据。分析一张专辑中所有单曲在榜单上的历史走势不仅能揭示专辑的整体商业影响力还能观察单曲的生命周期和听众的持续兴趣。Billie Eilish 的首张录音室专辑《WHEN WE ALL FALL ASLEEP, WHERE DO WE GO?》是一张现象级的作品其多首单曲曾进入美国公告牌 Hot 100 榜单。本文将带你从零开始完成一个技术项目获取、处理并可视化这张专辑所有进榜单曲的周榜排名推移数据。通过这个案例你将掌握使用 Python 进行网络数据采集、数据清洗、结构化存储以及生成动态可视化图表的核心技能。本文适合有一定 Python 基础希望学习数据抓取和数据分析实战的开发者。我们将使用requests和BeautifulSoup进行网页数据抓取用pandas进行数据处理并用plotly库生成交互式图表。最终你将得到一个清晰展示每首单曲在 Hot 100 榜单上起伏波折的走势图并能从中解读出一些有趣的商业和文化现象。1. 理解项目目标与数据源分析在开始写代码之前明确我们要做什么以及数据从哪里来至关重要。这个项目的核心是追踪一张特定专辑中所有单曲在 Billboard Hot 100 周榜上的历史排名。1.1 项目目标拆解我们的目标可以分解为以下几个技术任务数据获取从互联网上可靠地获取 Billie Eilish 首张专辑中所有单曲的 Billboard Hot 100 历史周榜数据。数据清洗与整合原始数据可能包含缺失值、格式不一致等问题需要清洗并整合成结构化的格式如 CSV 文件或数据库表。数据可视化将清洗后的数据以时间序列图的形式呈现X 轴为时间周Y 轴为榜单排名1-100每条线代表一首单曲。分析与洞察基于生成的图表观察单曲的登顶速度、在榜时长、排名稳定性等模式。1.2 数据源选择与评估Billboard 官网提供了详细的榜单数据但其页面结构复杂且对自动化抓取可能有限制。因此我们可以选择一些聚合了 Billboard 数据的第三方音乐数据网站这些网站通常结构更清晰。例如Billboard的官方数据可以通过其 API 或特定页面获取但这里我们以一个假设的、结构清晰的第三方数据页面为例进行教学。重要提示在实际操作中你必须检查目标网站的robots.txt文件确认是否允许爬虫抓取目标路径。遵守网站的服务条款不要进行高频、并发请求避免对服务器造成压力。考虑使用官方 API如果存在且满足需求作为首选方案。本教程示例代码仅用于教育目的演示技术流程实际数据抓取请确保合法合规。假设我们目标页面的 URL 模式为https://example-music-data.com/billboard-hot-100/song/{song-name}其中包含一个表格记录了该歌曲每周的排名和日期。2. 环境准备与依赖配置我们将创建一个独立的 Python 虚拟环境来管理项目依赖确保环境的纯净和可复现性。2.1 创建项目目录与虚拟环境打开终端Linux/macOS或命令提示符/PowerShellWindows执行以下命令# 创建项目目录 mkdir billboard_eilish_analysis cd billboard_eilish_analysis # 创建虚拟环境使用 venv python3 -m venv venv # 激活虚拟环境 # Linux/macOS: source venv/bin/activate # Windows: # venv\Scripts\activate激活后命令行提示符前通常会显示(venv)表示已进入虚拟环境。2.2 安装必要的 Python 库在虚拟环境激活状态下使用pip安装我们所需的库pip install requests beautifulsoup4 pandas plotlyrequests用于发送 HTTP 请求获取网页内容。beautifulsoup4用于解析 HTML 或 XML 文档提取结构化数据。pandas用于数据处理、分析和存储是数据科学的核心工具。plotly用于创建交互式、出版质量的图表。安装完成后可以通过pip list确认这些包已正确安装。2.3 确认专辑曲目列表我们需要明确专辑《WHEN WE ALL FALL ASLEEP, WHERE DO WE GO?》中哪些歌曲进入过 Hot 100。根据公开资料这些歌曲通常包括“bad guy”“when the party’s over”“wish you were gay”“xanny”“you should see me in a crown”“all the good girls go to hell”“my strange addiction”“bury a friend”“ilomilo”“listen before i go”“i love you”“goodbye”注意这个列表可能需要根据最新的榜单历史进行核实。我们的代码设计应能灵活处理歌曲列表。3. 核心代码实现数据抓取与处理我们将把代码模块化分别实现数据抓取、数据清洗和主流程控制。3.1 网页抓取模块 (scraper.py)创建一个名为scraper.py的文件。这个模块负责从网络获取原始 HTML 并解析出我们需要的数据表格。import requests from bs4 import BeautifulSoup import time import pandas as pd from typing import List, Dict, Optional class BillboardScraper: def __init__(self, base_url: str, delay: float 1.0): 初始化爬虫 :param base_url: 榜单数据页面的基础URL模式 :param delay: 每次请求之间的延迟秒用于礼貌爬取 self.base_url base_url self.delay delay self.session requests.Session() # 设置一个常见的用户代理头模拟浏览器访问 self.session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 }) def fetch_song_chart_data(self, song_name: str) - Optional[pd.DataFrame]: 获取指定歌曲的榜单数据 :param song_name: 歌曲名称将用于构造URL :return: 包含date和rank列的DataFrame如果失败则返回None # 构造具体歌曲的URL。这里需要根据实际网站URL模式调整。 # 例如将歌曲名转换为小写并用连字符连接 formatted_song_name song_name.lower().replace( , -) url self.base_url.format(song_nameformatted_song_name) print(f正在抓取: {song_name} - {url}) try: response self.session.get(url, timeout10) response.raise_for_status() # 如果状态码不是200抛出HTTPError异常 except requests.exceptions.RequestException as e: print(f请求失败 for {song_name}: {e}) return None # 解析HTML soup BeautifulSoup(response.content, html.parser) # 这里需要根据目标网站的实际HTML结构来定位表格 # 假设数据在一个id为chart-data的table中 chart_table soup.find(table, idchart-data) if not chart_table: # 如果id不对尝试其他选择器比如特定的class chart_table soup.find(table, class_chart-table) if not chart_table: print(f未找到榜单数据表格 for {song_name}) return None # 提取表格数据到列表 data [] # 假设第一行是表头我们跳过。实际中可能需要更健壮的逻辑。 rows chart_table.find_all(tr)[1:] # 跳过表头行 for row in rows: cols row.find_all(td) if len(cols) 2: # 至少包含日期和排名两列 date_str cols[0].text.strip() rank_str cols[1].text.strip() # 简单的数据清洗移除排名中的‘#’等符号并转换为整数 try: rank int(rank_str.replace(#, ).replace(—, 0)) # 处理未上榜情况 # 注意有些网站可能用‘-’或空值表示未上榜这里需要根据实际情况调整 # 如果未上榜rank为0或大于100我们可以选择跳过或保留 if 1 rank 100: data.append({date: date_str, rank: rank}) except ValueError: # 如果转换失败跳过这行数据 continue if not data: print(f未从表格中解析出有效数据 for {song_name}) return None # 转换为DataFrame并处理日期格式 df pd.DataFrame(data) df[date] pd.to_datetime(df[date], errorscoerce) # 尝试转换日期错误则设为NaT df df.dropna(subset[date]) # 删除日期无效的行 df df.sort_values(date) # 按日期排序 df[song] song_name # 添加歌曲名列 print(f成功抓取 {song_name} 的 {len(df)} 条数据。) time.sleep(self.delay) # 请求间隔避免被封 return df # 示例用法在模块内测试用 if __name__ __main__: # 假设的基础URL实际需要替换 BASE_URL https://example-music-data.com/billboard-hot-100/song/{song_name} scraper BillboardScraper(BASE_URL, delay2.0) test_df scraper.fetch_song_chart_data(bad guy) if test_df is not None: print(test_df.head())关键点解释User-Agent设置请求头模拟浏览器是绕过简单反爬机制的基础。错误处理使用try-except捕获网络请求和解析过程中的异常使程序更健壮。数据解析BeautifulSoup的find和find_all方法是核心。你需要使用浏览器的“开发者工具”F12来检查目标网页的实际 HTML 结构并调整选择器如id,class_,table标签等。延迟time.sleep(delay)是“礼貌爬虫”的基本要求避免短时间内发送大量请求。日期处理pd.to_datetime可以智能解析多种日期格式errors’coerce’会将解析失败的设为NaTNot a Time便于后续清理。3.2 数据整合与存储模块 (data_manager.py)创建一个data_manager.py文件负责管理歌曲列表、调用抓取模块并将所有数据合并保存。import pandas as pd from scraper import BillboardScraper from typing import List import os class ChartDataManager: def __init__(self, song_list: List[str], base_url: str): self.song_list song_list self.base_url base_url self.all_data pd.DataFrame() self.scraper BillboardScraper(base_url) def fetch_all_songs(self) - pd.DataFrame: 遍历歌曲列表抓取所有数据 :return: 合并后的DataFrame dfs [] for song in self.song_list: df self.scraper.fetch_song_chart_data(song) if df is not None and not df.empty: dfs.append(df) else: print(f警告歌曲 {song} 的数据抓取失败或为空。) if dfs: self.all_data pd.concat(dfs, ignore_indexTrue) print(f所有数据抓取完成。总计 {len(self.all_data)} 条记录。) else: print(错误未能抓取到任何数据。) self.all_data pd.DataFrame() return self.all_data def save_to_csv(self, filename: str billboard_eilish_data.csv): 将数据保存到CSV文件 :param filename: 输出文件名 if self.all_data.empty: print(没有数据可保存。请先运行 fetch_all_songs。) return # 确保输出目录存在 os.makedirs(data, exist_okTrue) filepath os.path.join(data, filename) self.all_data.to_csv(filepath, indexFalse, encodingutf-8-sig) # utf-8-sig 支持Excel中文 print(f数据已保存至: {filepath}) def load_from_csv(self, filename: str billboard_eilish_data.csv) - pd.DataFrame: 从CSV文件加载数据 :param filename: 输入文件名 :return: 加载的DataFrame filepath os.path.join(data, filename) try: self.all_data pd.read_csv(filepath, parse_dates[date]) print(f数据已从 {filepath} 加载。总计 {len(self.all_data)} 条记录。) except FileNotFoundError: print(f文件 {filepath} 未找到。) self.all_data pd.DataFrame() return self.all_data # 定义专辑曲目列表需要根据实际情况更新 EILISH_DEBUT_ALBUM_SONGS [ bad guy, when the partys over, wish you were gay, xanny, you should see me in a crown, all the good girls go to hell, my strange addiction, bury a friend, ilomilo, listen before i go, i love you, goodbye ]3.3 主程序入口 (main.py)创建main.py作为项目启动脚本它协调整个流程。from data_manager import ChartDataManager, EILISH_DEBUT_ALBUM_SONGS def main(): # 1. 配置 # 注意你需要替换成真实可用的、符合网站规则的URL模式 # 这里是一个示例实际中可能需要查询参数如 ?songbad-guy BASE_URL https://example-music-data.com/chart-history/hot-100/{song_name} # 2. 初始化管理器 manager ChartDataManager(EILISH_DEBUT_ALBUM_SONGS, BASE_URL) # 3. 抓取数据首次运行或需要更新数据时使用 # print(开始抓取数据...) # all_data manager.fetch_all_songs() # manager.save_to_csv() # 4. 从本地CSV加载数据如果已经抓取过 print(从本地文件加载数据...) all_data manager.load_from_csv() if all_data.empty: print(数据加载失败程序退出。) return # 5. 简单查看数据 print(\n数据预览:) print(all_data.head()) print(f\n数据形状: {all_data.shape}) print(f\n包含的歌曲: {all_data[song].unique()}) # 6. 可以在这里进行一些基本分析 # 例如计算每首歌的在榜周数 song_stats all_data.groupby(song).agg( first_date(date, min), last_date(date, max), weeks_on_chart(rank, count), peak_rank(rank, min) # 排名数字越小越好所以用min ).sort_values(peak_rank) print(\n歌曲榜单表现统计:) print(song_stats) # 7. 将统计结果也保存下来 song_stats.to_csv(data/song_statistics.csv) # 8. 准备进行可视化下一步 # 将处理好的数据传递给可视化模块 return all_data if __name__ __main__: df main()4. 数据可视化生成交互式周榜推移图数据抓取和清洗完成后我们使用plotly来创建交互式图表。创建一个visualizer.py文件。import plotly.graph_objects as go import plotly.express as px import pandas as pd from typing import List def create_chart_timeline(df: pd.DataFrame, output_html: str chart_timeline.html): 创建歌曲排名时间线图 :param df: 包含 date, rank, song 列的DataFrame :param output_html: 输出的HTML文件名 if df.empty: print(数据框为空无法生成图表。) return # 确保日期列是datetime类型 df[date] pd.to_datetime(df[date]) # 使用plotly express创建线条图 # 排名数字越小如1表示成绩越好但Y轴默认从上到下增大。 # 为了让图表更直观顶部表示排名更高我们可以用101-rank进行转换或者反转Y轴。 fig px.line(df, xdate, yrank, colorsong, titleBillie Eilish - 《WHEN WE ALL FALL ASLEEP》 单曲 Hot 100 周榜排名推移, labels{date: 日期, rank: 排名, song: 歌曲}, hover_namesong, hover_data{date: |%Y-%m-%d, rank: True}) # 优化图表样式 fig.update_layout( # 反转Y轴让排名1在最上方 yaxisdict(autorangereversed, title排名 (1冠军)), xaxisdict(title日期, tickformat%Y-%m), hovermodex unified, # 鼠标悬停时显示同一X轴日期的所有数据点 legenddict(title歌曲, yanchortop, y0.99, xanchorleft, x0.01), templateplotly_white ) # 添加一条标注线表示冠军位置排名第1 fig.add_hline(y1, line_dashdash, line_colorgold, annotation_text冠军线, annotation_positiontop left, annotation_font_size10, annotation_font_colorgold) # 保存为交互式HTML文件 fig.write_html(output_html) print(f交互式图表已生成: {output_html}) # 也可以在Jupyter notebook中直接显示 # fig.show() def create_peak_rank_bar(df: pd.DataFrame, output_html: str peak_rank_bar.html): 创建每首歌峰值排名的条形图 :param df: 包含 date, rank, song 列的DataFrame :param output_html: 输出的HTML文件名 # 计算每首歌的峰值排名即rank的最小值 peak_ranks df.groupby(song)[rank].min().reset_index().sort_values(rank) fig px.bar(peak_ranks, xsong, yrank, colorrank, titleBillie Eilish 首专单曲 Hot 100 峰值排名, labels{song: 歌曲, rank: 最佳排名}, color_continuous_scaleViridis_r) # 使用反向色阶排名越前颜色越深 fig.update_layout( xaxisdict(tickangle-45), # 倾斜x轴标签避免重叠 yaxisdict(autorangereversed, title排名 (越小越好)), templateplotly_white ) fig.write_html(output_html) print(f峰值排名条形图已生成: {output_html}) # 在主程序中调用可视化函数 if __name__ __main__: # 假设我们已经有了数据DataFrame df # 这里演示从CSV加载 df pd.read_csv(data/billboard_eilish_data.csv, parse_dates[date]) create_chart_timeline(df, output/chart_timeline.html) create_peak_rank_bar(df, output/peak_rank_bar.html)现在更新main.py的末尾加入可视化调用# 在 main() 函数末尾return all_data 之前添加 # ... (之前的统计代码) # 9. 生成可视化图表 print(\n生成可视化图表...) import visualizer # 确保输出目录存在 import os os.makedirs(output, exist_okTrue) visualizer.create_chart_timeline(all_data, output/eilish_hot100_timeline.html) visualizer.create_peak_rank_bar(all_data, output/eilish_peak_rank.html) print(图表已保存至 output/ 目录。用浏览器打开对应的 .html 文件查看交互式图表。) return all_data5. 运行验证与结果分析5.1 项目结构完成后的项目目录结构应如下所示billboard_eilish_analysis/ ├── venv/ # Python虚拟环境目录 ├── data/ # 数据存储目录 │ ├── billboard_eilish_data.csv │ └── song_statistics.csv ├── output/ # 图表输出目录 │ ├── eilish_hot100_timeline.html │ └── eilish_peak_rank.html ├── scraper.py ├── data_manager.py ├── visualizer.py ├── main.py └── requirements.txt # 可使用 pip freeze requirements.txt 生成5.2 运行程序确保虚拟环境已激活。由于我们尚未有真实的可抓取 URL首先需要模拟或获取数据。你可以手动从一些音乐数据网站确保合规查找并整理一份 CSV 数据放入data/billboard_eilish_data.csv。数据格式应为三列date(YYYY-MM-DD),rank(整数),song(字符串)。运行主程序python main.py程序会从data/目录加载 CSV 文件进行基本统计并在output/目录生成两个 HTML 图表文件。用浏览器打开output/eilish_hot100_timeline.html你将看到一个交互式时间线图。可以将鼠标悬停在线上查看具体某周某首歌的排名点击图例可以显示或隐藏特定歌曲的曲线。5.3 预期结果与洞察生成的图表应能清晰展示“bad guy”的曲线可能最靠上排名数字小且持续在高位很长时间反映了其作为爆款单曲的统治力。其他单曲如“when the party‘s over”、“bury a friend”可能也有不错的进榜表现和较长的在榜周期。一些歌曲的曲线可能起伏较大或仅在榜几周这反映了单曲不同的市场反响和推广策略。从整体看专辑发行前后一段时间多条曲线会集中出现体现了专辑发布带来的整体热度。6. 常见问题排查在实际运行中你可能会遇到以下问题问题现象可能原因检查与解决方式ModuleNotFoundError: No module named ‘requests’依赖未安装或不在虚拟环境中1. 确认终端提示符前有(venv)。2. 在项目根目录下执行pip install -r requirements.txt或重新安装pip install requests beautifulsoup4 pandas plotly。运行main.py提示FileNotFoundErrorCSV 数据文件不存在1. 确认data/billboard_eilish_data.csv文件是否存在。2. 如果首次运行需要先注释掉load_from_csv取消注释fetch_all_songs和save_to_csv部分并配置正确的BASE_URL进行抓取。抓取函数返回None或数据为空1. 目标网站结构已变。2. 网络请求被阻止。3. 歌曲名URL格式化不对。1. 用浏览器开发者工具重新检查目标页面的HTML结构更新scraper.py中的chart_table查找逻辑。2. 检查robots.txt增加请求延迟delay或添加更多请求头如Referer。3. 打印出构造的完整URL手动在浏览器访问看是否有效。图表Y轴排名顺序反了1在底部默认坐标轴方向在visualizer.py的create_chart_timeline函数中我们已经通过yaxisdict(autorange‘reversed’)进行了反转。如果未生效检查 plotly 版本或手动设置range[100, 1]。图表中数据点缺失或断线数据中存在缺失周次Billboard 榜单每周更新如果某首歌在某周未上榜即不在1-100名我们的示例抓取代码可能未记录。在抓取逻辑中需要决定是否记录“未上榜”例如记为101或NaN。在可视化时plotly.line默认会断开缺失值处的线。若需连接可考虑使用插值或使用散点图模式。生成的HTML图表在浏览器中不显示浏览器安全策略或路径问题1. 尝试从文件管理器双击打开HTML文件而非通过某些IDE的内置预览。2. 确保HTML文件路径没有中文字符或特殊符号。3. 检查浏览器控制台F12是否有错误信息。7. 最佳实践与扩展方向7.1 数据抓取最佳实践尊重网站始终遵守robots.txt设置合理的请求延迟如delay2秒以上避免在高峰时段抓取。错误处理与重试实现重试机制如使用tenacity库以应对偶发的网络错误。缓存数据将抓取到的数据立即保存到本地文件或数据库避免重复抓取相同内容。我们的save_to_csv和load_from_csv就是为此设计。使用API优先如果目标网站提供官方API务必优先使用。API通常更稳定、高效且合规。标识自己在请求头中设置一个清晰的User-Agent例如YourProjectName/1.0 (your-emailexample.com)方便网站管理员联系。7.2 数据处理与可视化增强数据完整性处理“未上榜”数据可以用NaN或一个超出范围的值如101表示并在可视化时妥善处理如将线断开或标记不同颜色。更多维度除了排名还可以尝试抓取和可视化“流媒体播放量”、“电台播放量”、“销量”等细分指标如果数据源提供。图表交互利用plotly的交互功能可以添加下拉菜单选择特定歌曲、时间范围滑块等。部署为Web应用使用Dash基于plotly或Streamlit可以快速将整个分析项目部署成一个交互式Web应用方便分享。7.3 项目扩展方向多专辑对比将代码扩展为可以比较不同艺术家或不同专辑的单曲榜单表现。预测模型基于历史排名数据尝试构建简单的机器学习模型来预测歌曲未来的排名趋势这是一个具有挑战性的时间序列预测问题。情感分析关联抓取歌曲发行前后的社交媒体情绪或乐评数据分析与榜单排名的相关性。自动化报告使用Jupyter Notebook或Python脚本结合邮件库定期如每周自动生成榜单分析报告并发送。通过这个完整的项目你不仅学会了如何抓取和处理 Billboard 榜单数据更掌握了一套从需求分析、环境搭建、代码实现、问题排查到结果展示的数据分析工程化流程。这套方法可以迁移到无数类似的数据获取与可视化场景中。