ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python数据抓取与可视化实战:构建音乐榜单追踪分析工具

Python数据抓取与可视化实战:构建音乐榜单追踪分析工具 这次我们来看一个音乐榜单数据可视化项目它聚焦于追踪和分析流行歌手 Olivia Rodrigo 的单曲《Good 4 u》在 Spotify 全球日榜和 Billboard Hot 100 榜单上的排名变化。对于音乐行业从业者、数据分析爱好者或歌迷来说手动追踪一首歌的榜单走势既繁琐又难以形成直观认知。这个项目通过数据抓取、处理和可视化将复杂的榜单数据转化为清晰的图表让你能一眼看清歌曲的“冲榜”轨迹和生命周期。本文的核心是带你从零开始复现一个类似的榜单追踪分析工具。我们将重点关注几个实用问题数据从哪里来合规的公开数据源、怎么自动化获取避免手动复制、如何处理和存储、以及最终如何用图表呈现其位次推移。整个过程不涉及复杂算法重点在于数据流程的构建和可视化技巧的应用适合有一定 Python 和数据分析基础的读者实践。1. 核心能力速览能力项说明项目类型音乐榜单数据抓取、分析与可视化工具核心功能自动获取 Spotify/ Billboard 榜单数据清洗处理生成排名推移趋势图数据来源基于公开的榜单页面或API需遵守平台条款技术栈Python (Requests, BeautifulSoup, Pandas, Matplotlib/Plotly)输出形式交互式图表或静态图片展示每日/每周排名变化适合场景音乐市场分析、艺人宣传效果追踪、粉丝数据研究、个人数据分析练习部署方式本地脚本运行可配置定时任务也可部署为轻量级Web应用硬件门槛极低普通电脑即可运行无需GPU2. 适用场景与使用边界这个工具主要适合以下几类人群音乐行业从业者AR、市场宣传人员可以量化评估单曲的打榜效果为宣传策略调整提供数据支持。数据分析和可视化爱好者作为一个完整的数据工程项目进行练手涵盖数据获取、清洗、分析和展示全流程。歌迷与乐评人希望超越感性认知用数据图表客观记录和展示喜爱歌曲的商业成绩轨迹。使用边界与注意事项数据合规性所有数据应来自公开、合法的渠道。严禁攻击、爬取受严格反爬机制保护的平台非公开数据或进行高频请求干扰对方服务器。版权与隐私本项目分析的是已公开的聚合榜单数据不涉及未公开的用户隐私信息或受版权保护的音频内容本身。分析局限性榜单排名受平台算法、市场活动、突发事件等多重因素影响可视化结果展示的是相关性而非因果性解读时需结合行业背景知识。工具性质本项目生成的分析图表可用于个人研究、内部汇报或公开讨论但若用于商业报告建议对数据源和计算方法进行标注说明。3. 环境准备与前置条件在开始构建之前请确保你的开发环境满足以下基础要求操作系统Windows 10/11, macOS, 或 Linux 发行版均可。Python 环境推荐使用 Python 3.8 及以上版本。使用conda或venv创建独立的虚拟环境是良好的实践。必备工具代码编辑器如 VS Code、PyCharm和命令行终端。网络环境需要能够正常访问 Spotify Charts 和 Billboard 官网或其提供数据的第三方镜像站。磁盘空间很小仅用于存储代码、少量配置文件和生成的图表。4. 项目结构与数据流程设计我们先规划整个项目的骨架。一个清晰的结构有助于后续开发和维护。spotify_billboard_tracker/ │ ├── config.yaml (或 .env) # 配置文件存放API密钥如果需要、目标歌曲、日期范围等 ├── main.py # 主程序入口 ├── requirements.txt # Python依赖列表 │ ├── src/ # 核心源代码目录 │ ├── __init__.py │ ├── data_fetcher.py # 数据抓取模块 │ ├── data_processor.py # 数据清洗与处理模块 │ ├── visualizer.py # 数据可视化模块 │ └── utils.py # 通用工具函数如日志、日期处理 │ ├── data/ # 数据存储目录 │ ├── raw/ # 原始抓取数据JSON/CSV │ └── processed/ # 清洗后的结构化数据 │ ├── outputs/ # 输出目录 │ └── charts/ # 生成的图表文件PNG/HTML │ └── logs/ # 日志文件目录核心数据流程抓取 (Fetch)从目标数据源获取原始榜单HTML或JSON数据。解析 (Parse)从原始数据中提取出歌曲名、艺人、排名、日期等关键字段。存储 (Store)将解析后的结构化数据保存到本地文件如CSV或数据库。处理 (Process)对历史数据进行整合、排序、计算变化如排名升降。可视化 (Visualize)利用处理后的数据生成排名随时间变化的折线图等。调度 (Schedule可选)配置定时任务如cron job自动执行抓取和分析。5. 数据抓取模块实现这是项目的基石。我们以 Billboard Hot 100 和 Spotify Global Daily Top 200 为例。请注意直接爬取网站需遵守robots.txt并设置合理请求间隔。更推荐使用官方API如Spotify Web API需注册开发者账号获取密钥或已获授权的第三方数据服务。5.1 Billboard Hot 100 数据获取示例通过网页解析Billboard官网结构可能变化以下代码为原理演示实际使用时需检查页面结构。# src/data_fetcher.py import requests from bs4 import BeautifulSoup import pandas as pd import time from datetime import datetime, timedelta import logging logging.basicConfig(levellogging.INFO) logger logging.getLogger(__name__) class BillboardFetcher: def __init__(self, start_date, end_date): self.start_date start_date self.end_date end_date self.base_url https://www.billboard.com/charts/hot-100/{date}/ self.headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } def fetch_chart_for_date(self, chart_date): 抓取指定日期的Hot 100榜单 url self.base_url.format(datechart_date) try: resp requests.get(url, headersself.headers, timeout10) resp.raise_for_status() # 添加延迟避免请求过快 time.sleep(1) return resp.text except requests.RequestException as e: logger.error(fFailed to fetch Billboard chart for {chart_date}: {e}) return None def parse_html_to_df(self, html_content, chart_date): 解析HTML提取排名、歌曲、艺人信息 if not html_content: return pd.DataFrame() soup BeautifulSoup(html_content, html.parser) records [] # 注意以下CSS选择器需要根据Billboard官网实际结构调整此处为示例 chart_items soup.select(ul.o-chart-results-list-row) # 示例选择器 for item in chart_items[:100]: # Hot 100 try: rank item.select_one(span.c-label).text.strip() song item.select_one(h3.c-title).text.strip() artist item.select_one(span.c-label.a-no-trucate).text.strip() records.append({ date: chart_date, rank: int(rank), song: song, artist: artist, chart: Billboard Hot 100 }) except AttributeError as e: logger.warning(fParsing error for an item on {chart_date}: {e}) continue return pd.DataFrame(records) def run(self): 执行一段日期范围内的抓取任务 all_data [] current_date self.start_date date_format %Y-%m-%d while current_date self.end_date: date_str current_date.strftime(date_format) logger.info(fFetching Billboard Hot 100 for {date_str}) html self.fetch_chart_for_date(date_str) if html: df self.parse_html_to_df(html, date_str) if not df.empty: all_data.append(df) current_date timedelta(days7) # Billboard每周更新 if all_data: final_df pd.concat(all_data, ignore_indexTrue) return final_df else: return pd.DataFrame()5.2 Spotify 榜单数据获取示例概念性说明Spotify Charts 网站 (https://charts.spotify.com) 提供每日、每周榜单数据通常以 CSV 或 JSON 格式直接提供下载链接比解析HTML更友好。你可以通过分析其网络请求找到数据文件的直接链接。更规范的方式是使用Spotify Web API。你需要在 Spotify Developer Dashboard 创建应用获取Client ID和Client Secret。通过 API 获取访问令牌。调用Get Several Tracks或Get Track端点获取歌曲详情但注意Web API 主要提供音乐元数据和音频特征不直接提供历史榜单数据。历史榜单数据通常需要通过其他合规渠道获取。因此对于 Spotify 全球日榜一个可行的方案是定期从charts.spotify.com的公开数据文件下载。这需要你编写脚本自动发现和下载每日更新的 CSV 文件。# 概念性代码下载Spotify Charts的CSV数据 import requests import pandas as pd def fetch_spotify_daily_csv(target_date): 尝试下载指定日期的Spotify全球日榜CSV # 日期格式需要匹配Spotify Charts的URL模式例如2023-10-01 date_str target_date.strftime(%Y-%m-%d) # 此URL模式仅为示例实际URL需通过分析网站获得 url fhttps://charts.spotify.com/charts/view/regional-global-daily/{date_str} # 或者可能是某个固定的数据端点 # url https://spotifycharts.com/regional/global/daily/latest/download headers {User-Agent: Your User Agent} try: # 有时需要先访问一个页面再获取CSV链接这里简化处理 response requests.get(url, headersheaders, streamTrue) # 如果响应内容是CSV if response.headers.get(Content-Type, ).lower() text/csv: df pd.read_csv(io.StringIO(response.text)) df[date] date_str df[chart] Spotify Global Daily return df except Exception as e: print(fError fetching Spotify data for {date_str}: {e}) return pd.DataFrame()重要提醒在实施抓取前务必仔细阅读目标网站的robots.txt文件和服务条款尊重网站的爬虫协议设置合理的请求间隔如time.sleep(2-5)避免对对方服务器造成负担。6. 数据处理与存储模块抓取到的原始数据需要被清洗、整合并存储起来以便后续分析。# src/data_processor.py import pandas as pd import os from pathlib import Path class DataProcessor: def __init__(self, raw_data_dir./data/raw, processed_data_dir./data/processed): self.raw_data_dir Path(raw_data_dir) self.processed_data_dir Path(processed_data_dir) self.processed_data_dir.mkdir(parentsTrue, exist_okTrue) def load_raw_data(self, source): 从原始数据目录加载指定数据源的CSV文件 data_files list(self.raw_data_dir.glob(f*{source}*.csv)) dfs [] for file in data_files: try: df pd.read_csv(file) dfs.append(df) except Exception as e: print(fError reading {file}: {e}) if dfs: return pd.concat(dfs, ignore_indexTrue) return pd.DataFrame() def filter_track(self, df, track_name, artist_name): 从数据框中筛选出特定歌曲的记录 # 实现简单的模糊匹配因为榜单上的歌曲名或艺人名可能有细微差别 mask df[song].str.contains(track_name, caseFalse, naFalse) \ df[artist].str.contains(artist_name, caseFalse, naFalse) filtered_df df[mask].copy() return filtered_df def calculate_rank_change(self, df): 计算排名变化与前一次排名比较 if df.empty: return df df df.sort_values(date) df[previous_rank] df[rank].shift(1) df[rank_change] df[previous_rank] - df[rank] # 正数表示上升 df[rank_change] df[rank_change].fillna(0).astype(int) return df def save_processed_data(self, df, filename): 保存处理后的数据 output_path self.processed_data_dir / filename df.to_csv(output_path, indexFalse) print(fProcessed data saved to {output_path}) def process_for_visualization(self, billboard_df, spotify_df, track_info): 整合两个数据源为目标歌曲生成用于可视化的数据集 # 1. 分别筛选目标歌曲 bb_track self.filter_track(billboard_df, track_info[name], track_info[artist]) sp_track self.filter_track(spotify_df, track_info[name], track_info[artist]) # 2. 统一列名和格式 bb_track bb_track[[date, rank, chart]].copy() sp_track sp_track[[date, rank, chart]].copy() # 3. 合并 combined_df pd.concat([bb_track, sp_track], ignore_indexTrue) combined_df[date] pd.to_datetime(combined_df[date]) combined_df combined_df.sort_values([chart, date]) # 4. 按数据源分别计算排名变化 final_dfs [] for chart_name in combined_df[chart].unique(): chart_df combined_df[combined_df[chart] chart_name].copy() chart_df self.calculate_rank_change(chart_df) final_dfs.append(chart_df) final_df pd.concat(final_dfs, ignore_indexTrue) return final_df7. 数据可视化模块实现这是将数据转化为洞察的关键一步。我们将使用matplotlib和seaborn生成静态图表也可以使用plotly生成交互式图表。# src/visualizer.py import pandas as pd import matplotlib.pyplot as plt import seaborn as sns from pathlib import Path import matplotlib.dates as mdates class ChartVisualizer: def __init__(self, output_dir./outputs/charts): self.output_dir Path(output_dir) self.output_dir.mkdir(parentsTrue, exist_okTrue) # 设置中文字体如果需要显示中文标签 # plt.rcParams[font.sans-serif] [SimHei, Arial Unicode MS] # plt.rcParams[axes.unicode_minus] False sns.set_style(whitegrid) def plot_rank_trend(self, df, track_name, artist_name, save_pathNone): 绘制排名趋势图。 df 应包含列date, rank, chart, rank_change if df.empty: print(No data to visualize.) return fig, axes plt.subplots(2, 1, figsize(14, 10), sharexTrue) fig.suptitle(f{track_name} by {artist_name} - Chart Performance, fontsize16, y1.02) colors {Billboard Hot 100: royalblue, Spotify Global Daily: coral} # 子图1排名折线图排名越低越好 ax1 axes[0] for chart_type, group in df.groupby(chart): color colors.get(chart_type, gray) ax1.plot(group[date], group[rank], markero, labelchart_type, colorcolor, linewidth2) ax1.invert_yaxis() # 让排名1在顶部 ax1.set_ylabel(Chart Rank (Lower is Better)) ax1.set_title(Rank Position Over Time) ax1.legend() ax1.grid(True, linestyle--, alpha0.7) # 格式化X轴日期 ax1.xaxis.set_major_formatter(mdates.DateFormatter(%Y-%m-%d)) ax1.xaxis.set_major_locator(mdates.WeekdayLocator(interval2)) plt.setp(ax1.xaxis.get_majorticklabels(), rotation45) # 子图2排名变化柱状图 ax2 axes[1] # 为每个数据源绘制变化 for chart_type, group in df.groupby(chart): color colors.get(chart_type, gray) # 使用条形图显示每日变化正值上升用绿色负值下降用红色 bars ax2.bar(group[date], group[rank_change], width0.8, labelf{chart_type} Change, colorcolor, alpha0.6) # 可选在条形上标注变化值 # for bar, change in zip(bars, group[rank_change]): # if change ! 0: # ax2.text(bar.get_x() bar.get_width()/2, bar.get_height(), # f{change:d}, hacenter, vabottom if change0 else top, fontsize8) ax2.axhline(y0, colorblack, linewidth0.8, linestyle-) ax2.set_xlabel(Date) ax2.set_ylabel(Rank Change (Positive Improved)) ax2.set_title(Daily/Weekly Rank Change) ax2.legend() ax2.grid(True, linestyle--, alpha0.7, axisy) ax2.xaxis.set_major_formatter(mdates.DateFormatter(%Y-%m-%d)) ax2.xaxis.set_major_locator(mdates.WeekdayLocator(interval2)) plt.setp(ax2.xaxis.get_majorticklabels(), rotation45) plt.tight_layout() if save_path: save_path self.output_dir / save_path plt.savefig(save_path, dpi300, bbox_inchestight) print(fChart saved to {save_path}) else: plt.show() plt.close(fig)8. 主程序与配置示例将各个模块串联起来并提供一个配置文件。# config.yaml track: name: Good 4 u artist: Olivia Rodrigo date_range: start: 2021-05-01 # 歌曲发行日期附近 end: 2021-12-31 # 追踪一段时间 data_sources: - name: Billboard Hot 100 enabled: true # 可能需要配置URL模板或API端点 - name: Spotify Global Daily enabled: true output: chart_filename: olivia_rodrigo_good4u_chart_trend.png# main.py import yaml import pandas as pd from datetime import datetime from src.data_fetcher import BillboardFetcher from src.data_processor import DataProcessor from src.visualizer import ChartVisualizer # 假设有一个SpotifyFetcher类 # from src.spotify_fetcher import SpotifyFetcher def load_config(config_pathconfig.yaml): with open(config_path, r, encodingutf-8) as f: config yaml.safe_load(f) return config def main(): config load_config() track_info config[track] start_date datetime.strptime(config[date_range][start], %Y-%m-%d) end_date datetime.strptime(config[date_range][end], %Y-%m-%d) print(fStarting chart analysis for: {track_info[name]} by {track_info[artist]}) print(fDate Range: {start_date.date()} to {end_date.date()}) # 1. 抓取数据 (这里以Billboard为例Spotify需类似实现) print(\n[Step 1] Fetching Billboard Hot 100 data...) bb_fetcher BillboardFetcher(start_date, end_date) bb_raw_df bb_fetcher.run() if not bb_raw_df.empty: bb_raw_df.to_csv(./data/raw/billboard_hot100_raw.csv, indexFalse) print(fFetched {len(bb_raw_df)} records from Billboard.) # 2. 处理数据 print(\n[Step 2] Processing data...) processor DataProcessor() # 加载原始数据这里简化实际中可能直接使用bb_raw_df bb_df pd.read_csv(./data/raw/billboard_hot100_raw.csv) if not bb_raw_df.empty else pd.DataFrame() # 假设我们也有Spotify数据 sp_df pd.read_csv(./data/raw/spotify_global_daily_raw.csv) # 需要提前抓取并保存 combined_processed_df processor.process_for_visualization(bb_df, sp_df, track_info) if combined_processed_df.empty: print(No data found for the specified track. Please check the track name and date range.) return processor.save_processed_data(combined_processed_df, processed_chart_data.csv) # 3. 可视化 print(\n[Step 3] Generating visualization...) visualizer ChartVisualizer() output_filename config[output].get(chart_filename, chart_trend.png) visualizer.plot_rank_trend( combined_processed_df, track_nametrack_info[name], artist_nametrack_info[artist], save_pathoutput_filename ) print(\nAnalysis complete!) if __name__ __main__: main()9. 部署与自动化运行项目本地运行成功后你可以考虑将其自动化。本地定时任务在 Linux/macOS 上使用cron在 Windows 上使用“任务计划程序”定期如每周一执行main.py脚本自动抓取最新数据并更新图表。部署到云服务器将代码部署到云服务器如 AWS EC2、Google Cloud VM同样配置定时任务并可以通过简单的 Web 服务器如 Flask将最新图表展示在网页上。依赖管理确保生产环境安装了所有依赖。# requirements.txt pandas1.3.0 requests2.26.0 beautifulsoup44.10.0 matplotlib3.4.3 seaborn0.11.2 pyyaml6.0安装命令pip install -r requirements.txt10. 常见问题与排查方法问题现象可能原因排查方式解决方案抓取不到 Billboard 数据1. 网页结构已更新2. 请求被屏蔽反爬3. 网络问题1. 检查robots.txt2. 打印响应状态码和内容前500字符3. 手动访问目标URL确认1. 更新BeautifulSoup解析逻辑中的CSS选择器2. 添加更真实的请求头设置代理增加请求间隔3. 检查网络连接Spotify CSV 链接失效数据源URL模式改变手动访问 Spotify Charts 网站使用浏览器开发者工具Network标签页查找最新的CSV文件下载请求更新fetch_spotify_daily_csv函数中的URL构建逻辑生成的图表中数据点缺失1. 歌曲在特定日期未上榜2. 数据抓取/解析失败3. 日期格式不匹配1. 检查processed_chart_data.csv确认缺失日期的数据是否存在2. 查看抓取阶段的日志文件3. 统一所有数据源的日期格式为YYYY-MM-DD1. 这是正常现象可在图表上用虚线或缺口表示2. 重新运行抓取脚本或检查解析逻辑3. 在数据处理阶段强制转换日期格式脚本运行速度慢1. 请求间隔太短触发反爬2. 抓取日期范围过大1. 查看日志中是否有429太多请求错误2. 统计抓取的日期数量1. 增加time.sleep()的间隔时间如3-5秒2. 对于历史数据考虑分批次抓取matplotlib图表中文乱码系统缺少中文字体或未正确配置检查visualizer.py中的字体设置代码是否被注释取消注释中文字体设置行并确保系统已安装相应字体如SimHei11. 最佳实践与扩展建议数据备份与版本控制定期备份抓取的原始数据 (data/raw/) 和处理后的数据。使用 Git 管理代码但将data/和outputs/目录加入.gitignore。错误处理与日志为每个关键步骤网络请求、文件读写添加完善的try...except错误处理并使用logging模块记录运行日志便于排查问题。配置化将所有可配置项如歌曲名、日期范围、数据源开关、图表样式放入config.yaml文件避免硬编码。扩展更多数据源本项目框架易于扩展。你可以添加其他榜单如 Apple Music 排行榜、YouTube Music 排行榜等只需实现对应的Fetcher类。丰富可视化除了排名趋势还可以计算并可视化歌曲在榜周数、最高排名、排名稳定性等指标。使用plotly库可以创建交互式图表支持缩放和悬停查看详情。构建简单看板使用Flask或Streamlit快速构建一个本地Web应用在一个页面上展示多首歌曲的榜单表现对比。合规与伦理始终将数据抓取行为控制在合理、合法的范围内。对于商业用途或大规模抓取优先考虑购买官方API服务或授权数据。通过这个项目你不仅能得到一份关于《Good 4 u》或其他任何歌曲的直观榜单分析报告更重要的是掌握了一套从数据获取到可视化呈现的完整自动化流程。这套方法可以迁移到任何你感兴趣的、数据公开可得的分析场景中。
返回列表