公司动态
Python爬虫与数据分析实战:复现Taylor Swift《Midnights》Billboard榜单数据
最近在分析音乐榜单数据时发现了一个非常有意思的现象泰勒·斯威夫特Taylor Swift的专辑《Midnights》在美国公告牌百强单曲榜Billboard Hot 100上的表现堪称“统治级”。这张专辑发行后一度有十首歌曲同时进入Hot 100前十名几乎“霸榜”。对于从事数据分析、爬虫开发或者对流行文化数据感兴趣的朋友来说这背后不仅是一个娱乐新闻更是一个绝佳的数据分析实战案例。本文将带你从零开始完整复现《Midnights》专辑歌曲在Billboard Hot 100榜单上的周榜推移数据分析项目。我们将涵盖数据采集、清洗、分析、可视化到结论解读的全流程使用Python作为主要工具。无论你是想学习如何从网站抓取结构化数据还是希望掌握时间序列数据的可视化技巧这篇文章都能提供一套可直接复用的代码和清晰的思路。1. 项目背景与目标公告牌百强单曲榜Billboard Hot 100是美国乃至全球最具权威性的单曲排行榜之一其排名综合了实体销量、数字下载、流媒体播放量以及电台点播量等多个维度数据每周更新一次。因此追踪一首歌在Hot 100上的排名变化是衡量其商业成功和流行热度的重要指标。泰勒·斯威夫特的《Midnights》专辑于2022年10月21日发行随即创造了历史记录在发行后的首周专辑内的歌曲包揽了Hot 100榜单的前十名。这不仅是她个人的里程碑也是公告牌历史上首次由一位艺术家实现此壮举。我们的项目目标如下数据获取自动化采集《Midnights》专辑中所有进入过Hot 100的歌曲自专辑发行日起至今或指定时间段内的每周排名数据。数据处理与存储将采集到的杂乱数据清洗、整理成结构化的格式如CSV、数据库便于分析。可视化分析绘制每首歌曲的排名随时间周次变化的折线图直观展示其“周榜推移”轨迹。深度洞察通过数据分析回答诸如“哪首歌在榜时间最长”、“哪首歌的排名下滑最快”、“歌曲排名变化与哪些事件如MV发布、现场表演相关”等问题。通过这个项目你将掌握一套处理时序排名数据的通用方法论并可轻松迁移到分析其他歌手、专辑或任何榜单数据上。2. 环境准备与工具说明在开始编码前请确保你的开发环境已就绪。本项目主要使用Python以下是核心库及其作用Python 3.8基础编程语言环境。requests用于向目标网站发送HTTP请求获取网页HTML内容。BeautifulSoup4 (bs4)用于解析HTML文档从中提取我们需要的结构化数据如排名、歌曲名、周次。pandas数据处理的核心库用于数据清洗、整合、分析和存储为CSV文件。matplotlib / seaborn数据可视化库用于绘制专业、美观的排名推移折线图。sqlite3 (Python内置)可选用于将数据存储到轻量级数据库中进行更复杂查询。安装依赖库打开你的终端或命令提示符使用pip命令安装所需库。pip install requests beautifulsoup4 pandas matplotlib seaborn项目结构建议创建一个清晰的项目文件夹便于管理代码和数据。midnights_billboard_analysis/ ├── data/ # 存放原始和清洗后的数据 │ ├── raw_html/ # 可存放下载的原始HTML页面可选 │ └── processed/ # 存放清洗后的CSV文件 ├── src/ # 存放源代码 │ ├── scraper.py # 爬虫脚本负责数据采集 │ ├── cleaner.py # 数据清洗脚本 │ ├── analyzer.py # 数据分析与可视化脚本 │ └── config.py # 配置文件如URL、请求头 └── main.py # 主程序入口协调整个流程3. 核心步骤一数据采集爬虫开发数据源是分析的基础。我们需要找到可靠、稳定地提供Billboard Hot 100历史周榜数据的网站。请注意直接爬取Billboard官网可能违反其服务条款且会遇到反爬机制。因此我们可以选择一些聚合了历史榜单数据的第三方音乐数据网站并在爬取时务必遵守robots.txt协议设置合理的请求间隔避免对目标服务器造成压力。这里我们以一个假设的、结构清晰的榜单数据网站为例讲解爬虫思路。在实际操作中你需要根据目标网站的实际HTML结构调整解析代码。步骤拆解确定目标URL和参数找到能通过歌手名Taylor Swift、专辑名Midnights或歌曲名筛选并展示每周排名历史的页面。观察URL规律例如https://example-chart-site.com/history?artistTaylorSwiftsongAnti-Hero。模拟浏览器请求使用requests库发送GET请求。务必设置User-Agent等请求头模拟真实浏览器访问。解析HTML内容使用BeautifulSoup解析返回的HTML定位到包含排名数据的表格table或列表ul/ol。提取结构化数据遍历表格的行tr从每个单元格td中提取日期周、排名、歌曲名等信息。处理分页与循环如果数据有多页需要找到“下一页”的链接或构造翻页参数循环抓取。数据暂存将每页提取的数据临时存入一个列表list of dictionaries或pandas DataFrame。增加延迟在请求之间使用time.sleep()添加随机延迟如2-5秒体现良好的爬虫礼仪。示例代码框架 (src/scraper.py):import requests from bs4 import BeautifulSoup import pandas as pd import time import random def fetch_song_chart_history(song_name): 获取指定歌曲的榜单历史数据 :param song_name: 歌曲名称 :return: 包含周次、排名等信息的DataFrame # 1. 构造请求URL和头部示例需替换为真实URL base_url https://example-chart-site.com/history params { artist: Taylor Swift, song: song_name, chart: hot-100 } headers { 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 } all_data [] page 1 has_next_page True while has_next_page: params[page] page print(f正在抓取 {song_name} 第 {page} 页...) try: response requests.get(base_url, paramsparams, headersheaders, timeout10) response.raise_for_status() # 检查请求是否成功 except requests.RequestException as e: print(f请求失败: {e}) break soup BeautifulSoup(response.text, html.parser) # 2. 解析页面定位数据表格 # 假设数据在一个id为chart-history的table中 chart_table soup.find(table, {id: chart-history}) if not chart_table: print(未找到榜单数据表格可能页面结构已变化或数据已抓取完毕。) break # 3. 提取表格行数据 for row in chart_table.find(tbody).find_all(tr): cols row.find_all(td) if len(cols) 3: # 假设至少有日期、排名、歌曲名列 week_date cols[0].text.strip() rank cols[1].text.strip() # 歌曲名已知可从参数获取这里可能还有其他信息如播放量等 song song_name peak cols[3].text.strip() if len(cols) 3 else N/A # 假设第4列是峰值排名 weeks_on_chart cols[4].text.strip() if len(cols) 4 else N/A # 假设第5列是在榜周数 all_data.append({ song: song, week: week_date, rank: int(rank) if rank.isdigit() else None, peak_rank: peak, weeks_on_chart: weeks_on_chart }) # 4. 检查是否存在下一页示例查找带有‘next’类名的链接 next_button soup.find(a, class_next) has_next_page bool(next_button) page 1 # 5. 礼貌性延迟避免请求过快 time.sleep(random.uniform(2, 4)) # 将列表转换为DataFrame df pd.DataFrame(all_data) if not df.empty: df[week] pd.to_datetime(df[week]) # 转换日期格式 df.sort_values(week, inplaceTrue) # 按周次排序 return df def main(): # 《Midnights》专辑进入过热百的歌曲列表示例 midnights_songs [ Anti-Hero, Lavender Haze, Maroon, Snow On The Beach, You‘re On Your Own, Kid, Midnight Rain, Question...?, Vigilante Shit, Bejeweled, Labyrinth, Karma, Sweet Nothing, Mastermind # 可根据实际情况增减 ] all_songs_data pd.DataFrame() for song in midnights_songs: print(f\n开始处理歌曲: {song}) song_df fetch_song_chart_history(song) if not song_df.empty: all_songs_data pd.concat([all_songs_data, song_df], ignore_indexTrue) # 每首歌抓取后稍作长时间休息 time.sleep(random.uniform(5, 8)) # 保存所有歌曲的原始数据 if not all_songs_data.empty: all_songs_data.to_csv(../data/raw/midnights_hot100_history_raw.csv, indexFalse, encodingutf-8-sig) print(f\n所有数据已保存至 CSV 文件共 {len(all_songs_data)} 条记录。) else: print(未能抓取到任何数据。) if __name__ __main__: main()重要提示合法性始终尊重robots.txt仅抓取允许公开访问的数据用于个人学习和分析。反爬应对真实网站可能有JavaScript渲染、验证码或API加密。对于复杂情况可能需要使用Selenium或Playwright等浏览器自动化工具或寻找官方/第三方提供的API接口。数据存储上述代码将数据保存为CSV。对于大量或需要频繁查询的数据可考虑使用SQLite或MySQL。4. 核心步骤二数据清洗与整理爬取到的原始数据往往存在缺失值、格式不一致、重复记录等问题需要清洗后才能用于分析。常见清洗任务处理缺失值检查rank等关键字段是否为NaN或None。根据情况可以删除缺失行或用前后周的平均值、中位数填充对于排名数据删除通常是更安全的选择。统一格式确保week字段为统一的datetime格式。将rank字段转换为整数型int。处理peak_rank可能包含‘Peak’字样或非数字字符。去重由于爬虫可能因网络问题重复抓取需根据song和week组合去除重复行。数据增强可以计算一些衍生字段如rank_change: 本周排名与上周排名的差值正值为下降负值为上升。chart_entry_week: 该歌曲首次进入榜单的周次。weeks_in_top10: 累计进入前10名的周数。示例清洗代码 (src/cleaner.py):import pandas as pd import numpy as np def clean_chart_data(raw_data_path, output_path): 清洗原始榜单数据 :param raw_data_path: 原始CSV文件路径 :param output_path: 清洗后输出路径 :return: 清洗后的DataFrame df pd.read_csv(raw_data_path, encodingutf-8-sig) print(f原始数据形状: {df.shape}) print(原始数据预览:) print(df.head()) print(\n原始数据信息:) print(df.info()) # 1. 处理缺失值删除排名为NaN的行 df_clean df.dropna(subset[rank]).copy() print(f删除排名缺失行后: {df_clean.shape}) # 2. 统一格式 df_clean[week] pd.to_datetime(df_clean[week], errorscoerce) df_clean[rank] pd.to_numeric(df_clean[rank], errorscoerce).astype(Int64) # 可空整数类型 # 清理peak_rank列提取数字假设格式如‘#1’ df_clean[peak_rank] df_clean[peak_rank].astype(str).str.extract(r(\d)).astype(Int64) # 3. 去重按歌曲和周次去重保留最后一条假设 df_clean df_clean.drop_duplicates(subset[song, week], keeplast) # 4. 按歌曲和周次排序为计算变化做准备 df_clean.sort_values([song, week], inplaceTrue) # 5. 计算排名变化 (本周排名 - 上周排名) df_clean[rank_change] df_clean.groupby(song)[rank].diff(-1) * -1 # 解释diff(-1)是当前行减下一行因为时间倒序根据你的数据排序方向调整。这里假设数据按时间升序排列。 # 更稳妥的方式先确保按时间升序排列然后计算 shift后的差值。 df_clean.sort_values([song, week], inplaceTrue) # 确保升序 df_clean[prev_rank] df_clean.groupby(song)[rank].shift(1) df_clean[rank_change] df_clean[prev_rank] - df_clean[rank] # 正数表示排名上升数字变小负数表示下降 df_clean.drop(columns[prev_rank], inplaceTrue) # 6. 标记是否在Top 10 df_clean[in_top10] df_clean[rank] 10 # 7. 计算每首歌的在榜周数从数据中计算 df_clean[week_number] df_clean.groupby(song).cumcount() 1 print(f\n清洗后数据形状: {df_clean.shape}) print(清洗后数据预览:) print(df_clean.head()) # 保存清洗后的数据 df_clean.to_csv(output_path, indexFalse, encodingutf-8-sig) print(f\n清洗后的数据已保存至: {output_path}) return df_clean if __name__ __main__: # 使用示例路径 raw_file ../data/raw/midnights_hot100_history_raw.csv clean_file ../data/processed/midnights_hot100_history_clean.csv cleaned_df clean_chart_data(raw_file, clean_file)5. 核心步骤三数据分析与可视化数据清洗完毕后就可以进行有趣的分析和可视化了。我们将使用pandas进行数据聚合和统计并用matplotlib或seaborn绘制图表。分析方向与可视化示例每首歌的排名推移折线图这是最核心的视图可以清晰看到每首歌的生命周期。歌曲排名对比图将多首歌的排名曲线放在同一张图中比较其走势。在榜时长统计统计每首歌停留在Hot 100榜单上的总周数。Top 10停留周数统计每首歌停留在前10名的周数。峰值排名分布查看所有歌曲达到的最高排名Peak Position。示例分析与可视化代码 (src/analyzer.py):import pandas as pd import matplotlib.pyplot as plt import seaborn as sns from matplotlib.ticker import MaxNLocator import numpy as np # 设置中文字体和图表样式可选 plt.rcParams[font.sans-serif] [SimHei, Arial Unicode MS, DejaVu Sans] # 解决中文显示问题 plt.rcParams[axes.unicode_minus] False sns.set_style(whitegrid) def plot_song_trajectory(df, song_listNone, output_path../data/processed/chart_trajectory.png): 绘制指定歌曲的排名随时间推移的折线图 :param df: 清洗后的DataFrame :param song_list: 需要绘制的歌曲列表None则绘制所有 :param output_path: 输出图片路径 if song_list is not None: plot_df df[df[song].isin(song_list)].copy() else: plot_df df.copy() plt.figure(figsize(14, 8)) # 为每首歌绘制一条线 songs plot_df[song].unique() colors plt.cm.tab20c(np.linspace(0, 1, len(songs))) # 使用色彩映射 for song, color in zip(songs, colors): song_data plot_df[plot_df[song] song].sort_values(week) plt.plot(song_data[week], song_data[rank], markero, labelsong, colorcolor, linewidth2, markersize4) plt.gca().invert_yaxis() # 排名第1在上方更符合直观 plt.xlabel(日期 (周), fontsize12) plt.ylabel(Billboard Hot 100 排名, fontsize12) plt.title(《Midnights》专辑歌曲 Billboard Hot 100 排名周榜推移, fontsize16, fontweightbold) plt.legend(bbox_to_anchor(1.05, 1), locupper left, borderaxespad0., title歌曲) plt.xticks(rotation45) plt.tight_layout() plt.savefig(output_path, dpi300, bbox_inchestight) plt.show() print(f排名推移图已保存至: {output_path}) def generate_summary_statistics(df, output_path../data/processed/summary_stats.csv): 生成每首歌的汇总统计数据 :param df: 清洗后的DataFrame :param output_path: 输出CSV路径 :return: 汇总统计DataFrame summary_list [] for song, group in df.groupby(song): entry_week group[week].min() latest_week group[week].max() total_weeks group[week].nunique() weeks_top10 group[in_top10].sum() peak_rank group[rank].min() # 排名数字越小越好 current_rank group.sort_values(week).iloc[-1][rank] if not group.empty else None best_rank_week group.loc[group[rank].idxmin(), week] if not group.empty else None summary_list.append({ song: song, entry_date: entry_week, latest_date: latest_week, total_weeks_on_chart: total_weeks, weeks_in_top10: weeks_top10, peak_rank: peak_rank, current_rank: current_rank, best_rank_date: best_rank_week }) summary_df pd.DataFrame(summary_list) summary_df.sort_values(peak_rank, inplaceTrue) # 按峰值排名排序 # 保存汇总数据 summary_df.to_csv(output_path, indexFalse, encodingutf-8-sig) print(f汇总统计数据已保存至: {output_path}) print(\n汇总统计预览:) print(summary_df.head(10).to_string()) return summary_df def plot_top10_weeks_bar(summary_df, output_path../data/processed/top10_weeks_bar.png): 绘制每首歌在Top 10周数的条形图 :param summary_df: 汇总统计DataFrame :param output_path: 输出图片路径 # 按Top10周数排序 plot_data summary_df.sort_values(weeks_in_top10, ascendingFalse) plt.figure(figsize(12, 6)) bars plt.barh(plot_data[song], plot_data[weeks_in_top10], colorsns.color_palette(viridis, len(plot_data))) plt.xlabel(停留在 Top 10 的周数, fontsize12) plt.title(《Midnights》歌曲在 Billboard Hot 100 Top 10 停留时长, fontsize14, fontweightbold) plt.gca().invert_yaxis() # 让最高的条在最上面 # 在条形末端添加数值标签 for bar in bars: width bar.get_width() plt.text(width 0.3, bar.get_y() bar.get_height()/2, f{int(width)}, haleft, vacenter) plt.tight_layout() plt.savefig(output_path, dpi300, bbox_inchestight) plt.show() if __name__ __main__: # 加载清洗后的数据 clean_file ../data/processed/midnights_hot100_history_clean.csv df pd.read_csv(clean_file, parse_dates[week]) # 1. 绘制核心歌曲的排名推移例如主打歌 hit_songs [Anti-Hero, Lavender Haze, Karma, Bejeweled] plot_song_trajectory(df, song_listhit_songs, output_path../data/processed/hit_songs_trajectory.png) # 2. 生成并查看汇总统计 stats_df generate_summary_statistics(df) # 3. 绘制Top 10周数条形图 plot_top10_weeks_bar(stats_df)运行上述代码后你将得到hit_songs_trajectory.png: 显示几首主打歌排名如何随时间变化。你可以看到“Anti-Hero”可能长期稳居高位而其他歌曲在发行几周后排名逐渐下滑。summary_stats.csv: 包含每首歌的详细统计数据表格。top10_weeks_bar.png: 直观对比哪首歌在Top 10中生命力最顽强。6. 常见问题与排查思路在实施本项目过程中你可能会遇到以下问题问题现象可能原因解决思路爬虫脚本返回空数据或403错误1. 网站反爬虫检查请求头、User-Agent。2. 目标网页结构已更新CSS选择器失效。3. 需要处理Cookie或Session。1. 使用更真实的浏览器请求头添加Referer等信息。2. 用浏览器开发者工具重新检查元素更新BeautifulSoup查找逻辑。3. 使用requests.Session()保持会话或考虑使用Selenium。pd.to_datetime转换日期失败原始日期字符串格式与默认解析格式不匹配。指定日期格式如pd.to_datetime(df[‘week’], format‘%Y-%m-%d’)。或用errors‘coerce’参数将无法解析的设为NaT再检查原始数据。排名折线图非常杂乱线条交叉严重同时绘制的歌曲太多且排名区间重叠。1. 分开展示每次只绘制3-5首歌曲进行对比。2. 使用子图plt.subplots每首歌一个子图。3. 使用交互式图表库如Plotly实现鼠标悬停查看详情。计算rank_change排名变化时逻辑错误数据排序方向升序/降序与diff()或shift()函数使用不匹配。在计算变化前务必用df.sort_values([‘song’, ‘week’], inplaceTrue)确保数据按歌曲和时间升序排列。然后使用df[‘prev_rank’] df.groupby(‘song’)[‘rank’].shift(1)获取上周排名。图表中文显示为方框系统或Matplotlib未配置中文字体。按示例代码设置plt.rcParams[‘font.sans-serif’]或使用英文标签。在服务器环境可能需要安装中文字体。7. 最佳实践与项目扩展建议完成基础分析后你可以从以下方向深化项目这更能体现工程和数据思维数据源可靠性多源验证尝试从另一个公开数据源如另一个音乐数据网站抓取相同数据进行交叉验证确保数据的准确性。官方API寻找Billboard或音乐数据公司如Spotify、Last.fm的官方API虽然可能有调用限制或费用但数据最权威。自动化与部署定时任务使用cronLinux或schedule库Python设置每周定时任务自动抓取最新榜单数据更新你的数据集和图表。简单看板使用Flask或Streamlit快速搭建一个本地Web应用将分析图表集成到一个动态看板中每次运行脚本后自动更新页面。分析维度扩展关联事件将排名变化与真实世界事件如音乐视频发布日、大型现场表演、社交媒体热点在时间轴上对齐分析外部事件对排名的影响。歌曲属性分析如果能获取歌曲的音频特征如节奏、能量、情绪可以分析这些特征与榜单表现如爬升速度、在榜时长是否存在相关性。对比分析将《Midnights》的数据与泰勒·斯威夫特的其他专辑如《1989》、《folklore》或同期其他热门专辑进行对比从历史角度评估其成功程度。代码优化错误处理与日志在爬虫脚本中添加更完善的异常处理try…except和日志记录logging模块便于长期运行和排错。配置化将歌曲列表、目标URL、请求头等参数抽取到单独的配置文件如config.yaml或config.py中提高代码可维护性。数据库存储使用SQLAlchemy等ORM库将数据存入SQLite或PostgreSQL便于执行更复杂的SQL查询如“找出所有发行后连续三周排名上升的歌曲”。这个项目不仅让你获得一份关于《Midnights》专辑的深度榜单分析报告更重要的是它提供了一个从数据获取到洞察呈现的完整实战框架。你可以将这套方法应用到任何你感兴趣的榜单、时间序列数据上无论是音乐、电影、体育还是加密货币价格。动手尝试调整代码以适应真实的数据源你会在解决一个个具体问题的过程中大幅提升你的数据工程和数据分析能力。