
在音樂數據分析與可視化領域Billie Eilish的首張錄音室專輯《WHEN WE ALL FALL ASLEEP, WHERE DO WE GO?》無疑是一個現象級的案例。這張專輯不僅定義了Z世代的流行音樂審美更在商業榜單上創造了驚人的記錄。對于開發者、數據分析師或音樂愛好者而言如何通過技術手段系統地追蹤、分析并可視化這樣一張“神專”中所有單曲在Billboard Hot 100榜單上的歷史排名變化是一個極具挑戰性和趣味性的實戰項目。本文將帶你從零開始構建一個完整的音樂榜單數據分析流程涵蓋數據獲取、清洗、存儲、分析與可視化全鏈路最終生成類似“周榜推移”的動態圖表。無論你是想學習Python數據分析還是希望掌握一套處理時序數據的通用方法這篇文章都能提供一套可復現的代碼方案。1. 項目背景與核心概念1.1 項目價值為什么分析榜單數據在流媒體時代音樂榜單數據是反映歌曲流行度、公眾接受度和文化影響力的重要量化指標。Billboard Hot 100榜單綜合了實體銷量、數字下載、電臺點播和流媒體播放量是公認的美國流行音樂風向標。分析一張專輯所有單曲的榜單軌跡可以幫助我們量化專輯影響力觀察“單曲帶飛全專”效應的具體數據表現。理解單曲生命周期識別歌曲的爬升期、峰值期和衰退期。對比單曲表現在同一時間維度下比較專輯內不同單曲的市場反響。數據驅動決策為音樂行業從業者提供市場分析的參考模型。1.2 技術目標我們要實現什么本項目的終極目標是生成一張多曲線時序圖清晰展示碧梨首專中所有進入Hot 100的單曲其每周排名如何隨時間周次變化。這涉及到以下幾個核心技術環節數據采集如何獲取準確、完整的歷史榜單數據。數據處理如何清洗、整理非結構化的榜單數據并將其轉化為結構化的時間序列數據。數據存儲如何高效地存儲和查詢歷史數據。數據分析如何計算關鍵指標如最高排名、在榜周數、平均排名等。數據可視化如何將時間序列數據繪制成直觀、美觀的圖表。1.3 核心數據概念Billboard Hot 100榜單周期每周更新一次數據統計周期通常為上周五至本周四。排名Rank1到100的數字1代表當周最熱門歌曲。在榜周數Weeks on Chart歌曲累計出現在榜單上的周數。峰值排名Peak Position歌曲歷史上達到的最高排名數字最小。上榜日期Chart Date歌曲首次進入榜單的日期。2. 環境準備與工具說明本項目主要使用Python生態中的數據科學工具鏈。以下版本為推薦版本實際操作中可根據情況調整。操作系統: Windows 10/11, macOS, 或 Linux (Ubuntu 20.04)編程語言: Python 3.8核心工具包:數據獲取與處理pandas(數據分析核心),numpy(數值計算)網絡請求requests(用于API調用或網頁抓取)數據可視化matplotlib(基礎繪圖),seaborn(增強樣式)可選-高級可視化plotly(交互式圖表)可選-網絡爬蟲beautifulsoup4(解析HTML)集成開發環境IDE: Jupyter Notebook (非常適合數據分析探索), VS Code, 或 PyCharm。安裝依賴: 在項目根目錄下可以使用requirements.txt文件管理依賴或直接使用pip安裝。# 創建并激活虛擬環境推薦 python -m venv venv source venv/bin/activate # Linux/macOS venv\Scripts\activate # Windows # 安裝核心包 pip install pandas numpy requests matplotlib seaborn # 如果需要網絡爬蟲 pip install beautifulsoup4 lxml # 如果需要交互式圖表 pip install plotly3. 數據獲取策略與實戰獲取歷史Billboard數據是第一步也是關鍵一步。這里提供兩種主流方案。3.1 方案一使用公開API推薦一些網站提供了音樂榜單數據的API接口。以billboard-api或chart-data類服務為例注意實際使用時需尋找可用、穩定的數據源并遵守其使用條款。假設我們找到一個返回JSON格式數據的API端點。import requests import pandas as pd from datetime import datetime, timedelta def fetch_hot_100_by_date(chart_date): 獲取指定日期的Hot 100榜單數據。 參數: chart_date (str): 格式為 YYYY-MM-DD 返回: pandas.DataFrame: 包含排名、歌曲名、藝人等信息的 DataFrame # 示例API URL實際URL需替換為真實可用的數據源 # 例如: https://api.example.com/billboard/hot-100?date2024-01-01 url fhttps://api.example.com/billboard/hot-100?date{chart_date} headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } try: response requests.get(url, headersheaders, timeout10) response.raise_for_status() # 檢查請求是否成功 data response.json() # 假設API返回的JSON中榜單數據在 data 字段下是一個列表 chart_data data.get(data, []) # 將數據轉換為DataFrame df pd.DataFrame(chart_data) # 添加圖表日期列 df[chart_date] pd.to_datetime(chart_date) return df except requests.exceptions.RequestException as e: print(f獲取 {chart_date} 數據失敗: {e}) return pd.DataFrame() # 返回空DataFrame # 示例獲取2024年第一周的榜單 df_sample fetch_hot_100_by_date(2024-01-06) if not df_sample.empty: print(df_sample[[rank, title, artist]].head())3.2 方案二網絡爬蟲備用方案如果無穩定API可以考慮從Billboard官網或其他數據歸檔網站抓取。請注意務必遵守網站的robots.txt協議控制請求頻率避免對目標服務器造成壓力。以下是一個使用BeautifulSoup解析HTML的示例框架網站結構可能隨時變化此代碼需調整。import requests from bs4 import BeautifulSoup import pandas as pd import time def scrape_hot_100_for_date(date_str): 從Billboard官網抓取指定日期的Hot 100榜單。 警告此代碼僅為示例Billboard官網結構復雜且可能反爬實際應用難度大。 # 構建URL例如https://www.billboard.com/charts/hot-100/2024-01-06/ url fhttps://www.billboard.com/charts/hot-100/{date_str}/ headers {User-Agent: 你的瀏覽器User-Agent} try: resp requests.get(url, headersheaders) soup BeautifulSoup(resp.content, html.parser) # 以下選擇器是假設的實際需要仔細分析網頁HTML結構 chart_items soup.select(li.chart-list__element) records [] for item in chart_items: rank item.select_one(.chart-element__rank).text.strip() song item.select_one(.chart-element__song).text.strip() artist item.select_one(.chart-element__artist).text.strip() records.append({ rank: int(rank), title: song, artist: artist, chart_date: pd.to_datetime(date_str) }) return pd.DataFrame(records) except Exception as e: print(f抓取 {date_str} 數據時出錯: {e}) return pd.DataFrame() # 禮貌性延遲避免請求過快 time.sleep(1) # 使用示例 # df scrape_hot_100_for_date(2024-01-06)3.3 構建歷史數據集要分析整張專輯的軌跡我們需要獲取專輯發行后相當長一段時間內每周的榜單數據。我們可以生成一個日期序列然后循環獲取數據。def build_historical_dataset(start_date, end_date): 構建指定時間范圍內的歷史榜單數據集。 日期應為每周六Billboard榜單發布日。 all_weeks_data [] # 生成每周六的日期序列 date_range pd.date_range(startstart_date, endend_date, freqW-SAT) for chart_date in date_range: date_str chart_date.strftime(%Y-%m-%d) print(f正在獲取 {date_str} 的數據...) # 使用API方案 weekly_df fetch_hot_100_by_date(date_str) # 或使用爬蟲方案 # weekly_df scrape_hot_100_for_date(date_str) if not weekly_df.empty: all_weeks_data.append(weekly_df) # 可選添加延遲避免請求過快 # time.sleep(0.5) # 合并所有周的數據 if all_weeks_data: full_dataset pd.concat(all_weeks_data, ignore_indexTrue) return full_dataset else: return pd.DataFrame() # 碧梨首專于2019年3月29日發行我們分析發行后一年的數據 # 注意實際運行此代碼需要可靠的API這里僅為流程演示 # historical_data build_historical_dataset(2019-03-30, 2020-03-28) # historical_data.to_csv(billboard_hot_100_2019_2020.csv, indexFalse)4. 數據處理與專輯歌曲篩選獲取原始數據后下一步是清洗數據并篩選出目標專輯的所有歌曲。4.1 數據清洗與整理# 假設我們已經從CSV文件加載了歷史數據 # df pd.read_csv(billboard_hot_100_2019_2020.csv) # 1. 確保日期列是datetime類型 df[chart_date] pd.to_datetime(df[chart_date]) # 2. 處理可能的重復或缺失值 print(f數據形狀: {df.shape}) print(f日期范圍: {df[chart_date].min()} 到 {df[chart_date].max()}) # 3. 查看數據結構 print(df.info()) print(df.head())4.2 定義目標歌曲列表我們需要明確碧梨首專《WHEN WE ALL FALL ASLEEP, WHERE DO WE GO?》中所有進入過Hot 100的歌曲。根據公開資料這些歌曲通常包括bad guy(主打單曲)when the party‘s overwish you were gayxannyyou should see me in a crownall the good girls go to hellmy strange addictionbury a friendilomilolisten before i goi love yougoodbye注意!!!!(intro) 和8通常不計入。我們需要根據實際榜單數據中的歌曲名進行精確匹配或模糊匹配。# 定義目標專輯的歌曲列表注意大小寫和標點最好與數據源保持一致 album_tracks [ 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 ] # 由于數據源中歌曲名可能有不同寫法如包含feat.信息我們可以進行模糊篩選 # 方法1精確匹配可能漏掉 df_album df[df[title].str.lower().isin([t.lower() for t in album_tracks])].copy() # 方法2模糊匹配更安全但可能引入無關歌曲 # 使用字符串包含的方法 pattern |.join(album_tracks) # 創建正則表達式模式 df_album df[df[title].str.contains(pattern, caseFalse, naFalse)].copy() print(f篩選出專輯相關記錄: {df_album.shape[0]} 條) print(f涉及歌曲: {df_album[title].unique()})4.3 構建歌曲周榜推移數據表我們的目標是得到每個歌曲、每個日期的排名。如果某周歌曲未進榜我們需要用NaN或一個標志值如101表示。# 獲取所有唯一的圖表日期和歌曲名 all_dates sorted(df[chart_date].unique()) all_songs_in_data df_album[title].unique() # 創建一個以日期為行、歌曲為列的DataFrame初始值為NaN表示未上榜 chart_history pd.DataFrame(indexall_dates, columnsall_songs_in_data) # 填充數據 for song in all_songs_in_data: song_data df_album[df_album[title] song] # 將歌曲數據按日期設置到對應位置 for _, row in song_data.iterrows(): chart_history.at[row[chart_date], song] row[rank] # 將索引重置為列便于后續分析 chart_history_reset chart_history.reset_index().rename(columns{index: chart_date}) # 查看數據 print(chart_history_reset.head()) print(chart_history_reset.tail())5. 數據分析與關鍵指標計算有了規整的數據我們可以計算每首歌的關鍵表現指標。def calculate_song_stats(series): 計算單首歌曲的榜單統計數據 # 去除NaN值未上榜的周次 ranked_weeks series.dropna() if ranked_weeks.empty: return { peak_pos: None, weeks_on_chart: 0, avg_rank: None, first_chart_date: None, last_chart_date: None } return { peak_pos: int(ranked_weeks.min()), # 排名數字越小越好 weeks_on_chart: len(ranked_weeks), avg_rank: round(ranked_weeks.mean(), 1), first_chart_date: ranked_weeks.index.min(), last_chart_date: ranked_weeks.index.max() } # 計算每首歌的統計數據 song_stats {} for song in all_songs_in_data: # 注意這里使用之前創建的 chart_history DataFrame (索引為日期) song_series chart_history[song] song_stats[song] calculate_song_stats(song_series) # 將統計數據轉換為DataFrame便于查看 stats_df pd.DataFrame.from_dict(song_stats, orientindex) stats_df stats_df.sort_values(peak_pos) # 按最高排名排序 print(stats_df)6. 數據可視化繪制周榜推移圖這是項目的核心成果展示。我們將使用matplotlib繪制多曲線圖。6.1 基礎多曲線圖import matplotlib.pyplot as plt import matplotlib.dates as mdates import numpy as np plt.figure(figsize(16, 9)) # 為每條曲線設置不同的顏色和樣式 colors plt.cm.Set3(np.linspace(0, 1, len(all_songs_in_data))) # 使用色彩映射 for idx, song in enumerate(all_songs_in_data): # 獲取該歌曲的排名序列 rank_series chart_history[song] # 繪制曲線未上榜的點NaN會被斷開 plt.plot(rank_series.index, rank_series.values, markero, markersize4, linewidth2, colorcolors[idx], labelsong, alpha0.8) # 圖表裝飾 plt.gca().invert_yaxis() # 反轉Y軸讓排名1在頂部 plt.title(Billie Eilish - WHEN WE ALL FALL ASLEEP, WHERE DO WE GO?\nHot 100 Chart Trajectory (2019-2020), fontsize16, fontweightbold) plt.xlabel(Chart Week, fontsize12) plt.ylabel(Hot 100 Rank (Lower is Better), fontsize12) plt.grid(True, alpha0.3, linestyle--) # 優化X軸日期顯示 plt.gca().xaxis.set_major_formatter(mdates.DateFormatter(%b %Y)) plt.gca().xaxis.set_major_locator(mdates.MonthLocator(interval2)) plt.xticks(rotation45) # 添加圖例 plt.legend(titleAlbum Tracks, bbox_to_anchor(1.05, 1), locupper left, fontsize10) plt.tight_layout() plt.show()6.2 進階優化突出顯示“bad guy”我們可以將主打單曲bad guy的曲線加粗、高亮顯示以直觀展示其“帶飛”效應。plt.figure(figsize(16, 9)) highlight_song bad guy # 假設數據中存在此精確名稱 other_songs [s for s in all_songs_in_data if s ! highlight_song] # 1. 先繪制其他歌曲的曲線顏色較淺 for song in other_songs: rank_series chart_history[song] plt.plot(rank_series.index, rank_series.values, marker, linewidth1.5, colorgray, alpha0.4, label_nolegend_) # 2. 再高亮繪制目標歌曲 if highlight_song in chart_history.columns: highlight_series chart_history[highlight_song] plt.plot(highlight_series.index, highlight_series.values, markero, markersize6, linewidth3, color#FF6B6B, labelhighlight_song, alpha0.9, zorder5) # zorder確保在最上層 # 3. 添加標注標記最高排名峰值點 if highlight_song in chart_history.columns: peak_rank stats_df.loc[highlight_song, peak_pos] peak_date stats_df.loc[highlight_song, first_chart_date] # 簡化處理實際應找峰值日期 # 找到峰值日期可能有多個日期達到峰值取第一個 peak_dates highlight_series[highlight_series peak_rank].index if len(peak_dates) 0: plt.annotate(fPeak: #{int(peak_rank)}, xy(peak_dates[0], peak_rank), xytext(10, 10), textcoordsoffset points, arrowpropsdict(arrowstyle-, colorred), fontsize10, fontweightbold, colorred) plt.gca().invert_yaxis() plt.title(Billie Eilish Debut Album - Hot 100 Trajectory\nHighlighting bad guy, fontsize16, fontweightbold) plt.xlabel(Chart Week, fontsize12) plt.ylabel(Hot 100 Rank, fontsize12) plt.grid(True, alpha0.3, linestyle--) plt.gca().xaxis.set_major_formatter(mdates.DateFormatter(%b %Y)) plt.gca().xaxis.set_major_locator(mdates.MonthLocator(interval2)) plt.xticks(rotation45) # 只顯示高亮歌曲的圖例 from matplotlib.lines import Line2D legend_elements [Line2D([0], [0], color#FF6B6B, lw3, markero, labelbad guy (Lead Single)), Line2D([0], [0], colorgray, lw1.5, alpha0.4, labelOther Album Tracks)] plt.legend(handleslegend_elements, locupper right, fontsize10) plt.tight_layout() plt.show()6.3 使用Plotly創建交互式圖表可選交互式圖表允許讀者懸停查看每周具體數據體驗更佳。import plotly.graph_objects as go import plotly.express as px fig go.Figure() # 添加每條歌曲的軌跡 for song in all_songs_in_data: rank_series chart_history[song].dropna() fig.add_trace(go.Scatter( xrank_series.index, yrank_series.values, modelinesmarkers, namesong, hovertemplatefb{song}/bbr Date: %{x|%Y-%m-%d}br Rank: %{y}extra/extra )) # 更新布局 fig.update_layout( titleBillie Eilish Debut Album - Interactive Hot 100 Chart Trajectory, xaxis_titleChart Week, yaxis_titleHot 100 Rank, yaxis_autorangereversed, # 反轉Y軸 hovermodex unified, # 統一懸停模式 templateplotly_white, height600, legenddict(yanchortop, y0.99, xanchorleft, x0.01) ) fig.show() # 可以保存為HTML文件在瀏覽器中打開 # fig.write_html(billie_album_hot100_trajectory.html)7. 常見問題與排查思路在實施本項目過程中你可能會遇到以下問題問題現象可能原因解決思路獲取API數據返回403或404錯誤1. API端點已失效或變更。2. 請求頭信息不完整被服務器拒絕。3. 需要API密鑰或令牌。1. 檢查API文檔或尋找替代數據源。2. 完善請求頭特別是User-Agent。3. 查看是否需要注冊并獲取API Key。網絡爬蟲抓取不到數據或結構解析失敗1. 目標網站HTML結構已更新。2. 網站有反爬機制如JavaScript渲染。3. 請求頻率過高被限制。1. 使用瀏覽器開發者工具重新分析頁面元素更新CSS選擇器或XPath。2. 考慮使用Selenium或Playwright模擬瀏覽器。3. 增加請求間隔 (time.sleep)使用代理IP池。歌曲匹配失敗df_album為空1. 數據源中的歌曲名與本地列表不一致如包含“feat.”大小寫標點。2. 歌曲從未進入Hot 100。1. 打印數據源中的歌曲名樣本調整匹配邏輯如使用模糊匹配、正則表達式。2. 核對公開的榜單歷史記錄確認歌曲確實上榜過。圖表曲線混亂大量NaN值1. 時間序列日期不連續。2. 很多歌曲在大部分時間未上榜。1. 確保all_dates是連續的每周日期。2. 這是正常現象未上榜即為NaN圖表中會顯示為斷點。可以使用interpolate()進行插值但不推薦會扭曲事實。matplotlib圖表中文亂碼系統缺少中文字體。在繪圖前添加字體設置plt.rcParams[‘font.sans-serif’] [‘SimHei’, ‘Arial’](Windows)或指定具體字體路徑。圖表Y軸方向反了排名1在底部未反轉Y軸。在plt.plot后使用plt.gca().invert_yaxis()。Plotly圖表不顯示未在Jupyter Notebook中初始化或運行在靜態環境中。在Notebook中運行fig.show()。若在腳本中可輸出為HTML文件fig.write_html(“chart.html”)。8. 最佳實踐與項目擴展建議8.1 數據工程最佳實踐數據緩存歷史榜單數據變化不頻繁應避免重復請求。首次獲取后將數據持久化存儲如CSV、SQLite數據庫后續分析直接讀取本地文件。錯誤處理與重試網絡請求必須包含異常捕獲和重試機制如使用tenacity庫提高數據采集的魯棒性。數據版本化如果定期更新數據建議使用日期命名文件如hot100_20240101.csv便于追蹤數據版本和回滾。使用環境變量管理密鑰如果使用需要認證的API務必不要將API密鑰硬編碼在代碼中。使用python-dotenv等庫從.env文件讀取。8.2 分析深度擴展計算“在榜表現指數”可以設計一個綜合指標結合峰值排名、在榜周數、平均排名量化每首歌的整體榜單表現。# 示例簡單的表現評分數值越小越好 stats_df[performance_score] stats_df[peak_pos] * 0.5 stats_df[avg_rank] * 0.3 - stats_df[weeks_on_chart] * 0.2分析“專輯效應”計算主打單曲 (bad guy) 達到峰值后其他專輯歌曲進入榜單或排名提升的滯后周數量化“帶飛”效應。對比分析將碧梨專輯的數據與其他歌手的專輯如Taylor Swift的《Lover》進行對比分析不同的單曲發布策略和榜單軌跡模式。8.3 可視化增強添加注釋在圖表關鍵點如歌曲首發日、專輯發行日、排名峰值點添加文字注釋讓圖表信息更豐富。使用面積圖可以嘗試用面積圖展示排名區間的密度直觀顯示專輯歌曲在榜單上的“統治力”隨時間的變化。制作動態圖表使用plotly或matplotlib.animation制作排名隨周次變化的動態視頻更具沖擊力。8.4 工程化與自動化構建數據管道使用Apache Airflow或Prefect等工具將數據獲取、清洗、分析、可視化任務編排成定期如每周運行的自動化管道。創建Web應用使用Flask或Streamlit快速搭建一個Web應用用戶可以選擇不同專輯或時間范圍動態生成圖表。數據庫集成對于大規模歷史數據使用PostgreSQL或MySQL進行存儲和復雜查詢替代CSV文件。通過這個項目你不僅學會了如何分析一張特定專輯的榜單數據更掌握了一套處理任何時序排名數據的通用方法論。從數據獲取的堅韌到數據清洗的細致再到分析與可視化的洞察每一步都是數據科學家和數據分析師日常工作的縮影。