㊗️本期内容已收录至专栏《Python爬虫实战》,持续完善知识体系与项目实战,建议先订阅收藏,后续查阅更方便~
㊙️本期爬虫难度指数:⭐⭐
🉐福利: 一次订阅后,专栏内的所有文章可永久免费看,持续更新中,保底1000+(篇)硬核实战内容。

全文目录:
-
-
- [🌟 开篇语](#🌟 开篇语)
- [0️⃣ 前言(Preface)](#0️⃣ 前言(Preface))
- [1️⃣ 摘要(Abstract)](#1️⃣ 摘要(Abstract))
- [2️⃣ 背景与需求(Why)](#2️⃣ 背景与需求(Why))
- [3️⃣ 合规与注意事项(必写)](#3️⃣ 合规与注意事项(必写))
- [4️⃣ 技术选型与整体流程(What/How)](#4️⃣ 技术选型与整体流程(What/How))
- [5️⃣ 环境准备与依赖安装(可复现)](#5️⃣ 环境准备与依赖安装(可复现))
- [6️⃣ 核心实现:请求层(Fetcher)](#6️⃣ 核心实现:请求层(Fetcher))
- [7️⃣ 核心实现:解析层(Parser)](#7️⃣ 核心实现:解析层(Parser))
- [8️⃣ 数据存储与导出:核心增量比对算法(Storage & Delta)](#8️⃣ 数据存储与导出:核心增量比对算法(Storage & Delta))
- [9️⃣ 运行方式与结果展示(必写)](#9️⃣ 运行方式与结果展示(必写))
- [🔟 常见问题与排错(强烈建议写)](#🔟 常见问题与排错(强烈建议写))
- [1️⃣1️⃣ 进阶优化(可选但加分)](#1️⃣1️⃣ 进阶优化(可选但加分))
- [1️⃣2️⃣ 总结与延伸阅读](#1️⃣2️⃣ 总结与延伸阅读)
- [🌟 文末](#🌟 文末)
-
- [✅ 专栏持续更新中|建议收藏 + 订阅](#✅ 专栏持续更新中|建议收藏 + 订阅)
- [✅ 互动征集](#✅ 互动征集)
- [✅ 免责声明](#✅ 免责声明)
-
🌟 开篇语
哈喽,各位小伙伴们你们好呀~我是【喵手】。
运营社区: C站 / 掘金 / 腾讯云 / 阿里云 / 华为云 / 51CTO
欢迎大家常来逛逛,一起学习,一起进步~🌟
我长期专注 Python 爬虫工程化实战 ,主理专栏 《Python爬虫实战》:从采集策略 到反爬对抗 ,从数据清洗 到分布式调度 ,持续输出可复用的方法论与可落地案例。内容主打一个"能跑、能用、能扩展 ",让数据价值真正做到------抓得到、洗得净、用得上。
📌 专栏食用指南(建议收藏)
- ✅ 入门基础:环境搭建 / 请求与解析 / 数据落库
- ✅ 进阶提升:登录鉴权 / 动态渲染 / 反爬对抗
- ✅ 工程实战:异步并发 / 分布式调度 / 监控与容错
- ✅ 项目落地:数据治理 / 可视化分析 / 场景化应用
📣 专栏推广时间 :如果你想系统学爬虫,而不是碎片化东拼西凑,欢迎订阅专栏👉《Python爬虫实战》👈,一次订阅后,专栏内的所有文章可永久免费阅读,持续更新中。
💕订阅后更新会优先推送,按目录学习更高效💯~
0️⃣ 前言(Preface)
哈喽各位音乐极客们!今天我们要挑战的是全球流媒体巨头------Apple Music。我们将手写一个健壮的 Python 爬虫,每天定时访问"华语流行新歌榜",抓取最新的排名情况。更酷的是,我们会引入强大的 pandas 库,把每天的增量数据与历史数据进行碰撞,自动计算出每首歌的"历史峰值(Peak)"和"上榜周数(Weeks on Chart)" 。最终,我们会产出一份纯净的数据表 apple_music_mandopop_daily.csv,并画出一张全英文的打榜趋势图。
读完这篇,你将直接拿下:
- 应对国际大厂前端重度渲染页面的数据定位技巧。
- 掌握基于 Pandas 的"日更增量对比"与"历史峰值追踪"算法。
- 打造一个脱离手工统计的自动化音乐打榜监控系统。
1️⃣ 摘要(Abstract)
本文以 Apple Music 网页端榜单为抓取目标,使用 requests 结合 BeautifulSoup 提取网页中预载入的 JSON 数据或 DOM 节点。随后,通过 Python 的数据处理神器 pandas 读取本地历史存量 CSV,将当日抓取的新数据与之进行左连接(Merge)比对,动态更新每首歌曲的 Peak(峰值)与上榜生命周期,最终完成数据的闭环更新及可视化图表的生成。
读者将获得:
- 一套工业级的历史状态留存与更新逻辑。
- 规避重复抓取、高效利用存量数据的实操模板。
2️⃣ 背景与需求(Why)
为什么要死磕 Apple Music 的增量?因为相比于国内某些充斥着流量打榜的平台,Apple Music 的算法更加纯粹,其榜单含金量在业内公认极高。
我们的目标站点 :Apple Music 网页版(如:华语流行榜单区 https://music.apple.com/cn/playlist/...)
我们要精准拿下的目标字段:
rank: 当前排名(1-100)song_name: 歌曲名artist: 歌手名weeks_on_chart: 上榜周数(通过我们的每日增量算法自动累加)peak_position: 历史峰值(记录该歌曲自上榜以来的最高排名)
3️⃣ 合规与注意事项(必写)
大厂的法务部极其严格,咱们做爬虫必须是谦卑的"绅士":
- robots.txt 限制:Apple Music 允许搜索引擎收录其公开的页面元数据,但严禁抓取音频流文件本身。我们只提取公开的文本排名,属于合理的数据聚合。
- 频率控制、不要攻击式并发:一天只抓一次!因为这是日榜/周榜,你每秒钟抓100次除了把自己的 IP 送进黑名单外,毫无意义。代码里必须写好单次请求机制。
- 国际化防线 :Apple 的服务器会根据你的 IP 或 URL 中的
cc=cn(地区代码) 动态下发数据。保持中立,不要试图绕过其付费 DRM 保护机制。
4️⃣ 技术选型与整体流程(What/How)
这篇实战属于典型的 静态页隐式 JSON 抓取 + 本地状态机更新 。
Apple Music 的网页非常有趣,它表面上是 HTML,但为了加速前端渲染,它会把完整的结构化数据(JSON)藏在页面的 <script id="shoebox-media-api-cache-amp-music"> 标签里。我们直接用正则表达式或者 BS4 把这块 JSON 抠出来,比解析 DOM 树爽一万倍!
流程图/文字拆解:
发起 HTTP 请求 → 提取 HTML 隐藏的 JSON (Fetcher/Parser) → 清洗出今日 Top 50 → 加载本地历史 CSV → 碰撞比对(更新 Peak 和 Weeks) → 覆写存入 CSV (Storage) → 英文走势可视化
5️⃣ 环境准备与依赖安装(可复现)
开始干活!请确保你的开发环境已经准备好,这次 pandas 是绝对的 C 位主角。
-
Python 版本:推荐 3.9 及以上。
-
依赖安装:
bashpip install requests beautifulsoup4 pandas matplotlib -
项目结构推荐:
textapple_music_radar/ ├── config.py # 存放目标 Playlist URL 和 User-Agent ├── daily_scraper.py # 抓取与增量比对核心逻辑 ├── trend_visualizer.py # 趋势制图脚本 └── data/ # 存放 CSV 和趋势图片
6️⃣ 核心实现:请求层(Fetcher)
Apple 对空 User-Agent 或者老旧浏览器的拦截率极高,有时候还会返回 403。所以我们需要把自己伪装成一个正常的 MacOS 用户。
python
import requests
import time
import random
class AppleMusicFetcher:
def __init__(self):
self.session = requests.Session()
# 伪装成真实的 Mac Safari 浏览器
self.headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15',
'Accept-Language': 'zh-CN,zh-Hans;q=0.9',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
}
def get_playlist_html(self, url):
try:
# 优雅的延时,模拟人类起床看榜单
time.sleep(random.uniform(1.5, 3.5))
print(f"📡 正在连接 Apple Music 星云节点: {url}")
response = self.session.get(url, headers=self.headers, timeout=15)
response.raise_for_status()
response.encoding = 'utf-8'
return response.text
except requests.exceptions.RequestException as e:
print(f"❌ 糟糕!连接 Apple 服务器失败: {e}")
return None
7️⃣ 核心实现:解析层(Parser)
为了保证代码在不同环境下都能运行,我们准备两套方案。考虑到 Apple 网页结构的复杂性,我们在这里展示一套基于通用 DOM 解析的鲁棒容错方案(同时模拟我们抠取到了排名)。
python
from bs4 import BeautifulSoup
import datetime
class AppleMusicParser:
@staticmethod
def parse_chart(html_content):
"""解析榜单 HTML,返回今日最新的歌曲列表"""
if not html_content:
return []
soup = BeautifulSoup(html_content, 'html.parser')
daily_songs = []
# 定位歌曲列表行 (这里的 CSS class 为示意,实战中需通过 F12 审查元素获取最新类名)
# Apple Music 常用的列表容器如 div.songs-list-row 或类似结构
song_rows = soup.select('div[role="row"]')
# 如果网页结构过于复杂,我们可以利用模拟数据做增量逻辑的完美演示
# 下方为防崩溃的健壮提取逻辑:
for index, row in enumerate(song_rows):
# 假设我们只取 Top 50
if index >= 50: break
title_node = row.select_one('.song-name')
artist_node = row.select_one('.artist-name')
if title_node and artist_node:
song_name = title_node.get_text(strip=True)
artist = artist_node.get_text(strip=True)
daily_songs.append({
'Rank': index + 1,
'Song': song_name,
'Artist': artist,
'Date_Scraped': datetime.date.today().strftime('%Y-%m-%d')
})
# 【重要兜底】:如果上面没抓到(比如 Apple 前端改版了),我们启用备用的硬核演示数据,
# 确保你可以完美跑通后面的"核心增量算法"!
if not daily_songs:
print("⚠️ 警告:DOM 结构可能发生变化,启用备用数据流以展示核心的增量更新逻辑...")
daily_songs = [
{'Rank': 1, 'Song': '搁浅', 'Artist': '周杰伦', 'Date_Scraped': datetime.date.today().strftime('%Y-%m-%d')},
{'Rank': 2, 'Song': '乌梅子酱', 'Artist': '李荣浩', 'Date_Scraped': datetime.date.today().strftime('%Y-%m-%d')},
{'Rank': 3, 'Song': '大艺术家', 'Artist': '蔡依林', 'Date_Scraped': datetime.date.today().strftime('%Y-%m-%d')},
]
return daily_songs
8️⃣ 数据存储与导出:核心增量比对算法(Storage & Delta)
全场最硬核的部分来了!如何计算上榜周数和峰值?
逻辑是:用今天的抓取结果,去读取本地历史的 apple_music_mandopop_daily.csv。
- 如果是一首新歌:初始化 Weeks=0.14(即 1/7 周),Peak = 当前排名。
- 如果历史已有记录:对比历史 Peak,取最小值(排名越小越靠前);上榜周数 = 历史周数 + 0.14(每天算 0.14 周)。
python
import pandas as pd
import os
import math
class ChartDeltaManager:
def __init__(self, filename="data/apple_music_mandopop_daily.csv"):
self.filename = filename
os.makedirs(os.path.dirname(self.filename), exist_ok=True)
def process_daily_update(self, today_data_list):
# 将今日数据转为 DataFrame
df_today = pd.DataFrame(today_data_list)
df_today['Unique_ID'] = df_today['Song'] + " - " + df_today['Artist'] # 联合主键防止同名歌曲
# 如果是第一次运行,直接初始化建库
if not os.path.exists(self.filename):
print("🆕 发现新宇宙!正在初始化历史数据库...")
df_today['Weeks_On_Chart'] = 0.14 # 第一天上榜,算 1/7 周
df_today['Peak_Position'] = df_today['Rank']
# 按照要求的字段整理并保存
final_df = df_today[['Rank', 'Song', 'Artist', 'Weeks_On_Chart', 'Peak_Position', 'Date_Scraped', 'Unique_ID']]
final_df.to_csv(self.filename, index=False, encoding='utf-8-sig')
return final_df
# 如果已有历史库,进行精彩的碰撞计算
print("🔄 正在加载历史数据,进行增量计算与峰值追踪...")
df_history = pd.read_csv(self.filename)
# 准备一个空列表来存更新后的数据
updated_records = []
for _, today_row in df_today.iterrows():
uid = today_row['Unique_ID']
current_rank = today_row['Rank']
# 在历史库中寻找这首歌
hist_match = df_history[df_history['Unique_ID'] == uid]
if not hist_match.empty:
# 已经是老歌了,更新数据
past_weeks = hist_match['Weeks_On_Chart'].values[0]
past_peak = hist_match['Peak_Position'].values[0]
new_weeks = round(past_weeks + 0.14, 2) # 加一天
new_peak = current_rank if current_rank < past_peak else past_peak # 取最小排名作为巅峰
updated_records.append({
'Rank': current_rank,
'Song': today_row['Song'],
'Artist': today_row['Artist'],
'Weeks_On_Chart': new_weeks,
'Peak_Position': new_peak,
'Date_Scraped': today_row['Date_Scraped'],
'Unique_ID': uid
})
else:
# 这是一首今天刚杀入榜单的新歌!
print(f"🔥 空降新歌警报! {today_row['Song']} - {today_row['Artist']} 空降第 {current_rank} 名!")
updated_records.append({
'Rank': current_rank,
'Song': today_row['Song'],
'Artist': today_row['Artist'],
'Weeks_On_Chart': 0.14,
'Peak_Position': current_rank,
'Date_Scraped': today_row['Date_Scraped'],
'Unique_ID': uid
})
# 生成今日最新的一张榜单表
df_final = pd.DataFrame(updated_records)
df_final.sort_values(by='Rank', inplace=True)
# 将今天的结果覆盖保存(如果你想保留所有日期的流水,可以存另外一个流水表)
df_final.to_csv(self.filename, index=False, encoding='utf-8-sig')
print(f"💾 增量对比完毕!历史最高 Peak 与生存周数已完美更新至 {self.filename}!")
return df_final
9️⃣ 运行方式与结果展示(必写)
我们将它们串联成一个优雅的主函数,每天只要用命令行执行一下 python daily_scraper.py,你的音乐雷达就会自动扫描整个华语乐坛的动向!
python
# 文件名: daily_scraper.py
def main():
print("🚀 启动 Apple Music 华语新歌趋势雷达...")
# Apple Music 中国区华语流行榜单(示例链接)
target_url = "https://music.apple.com/cn/playlist/top-100-china/pl.xxx"
fetcher = AppleMusicFetcher()
parser = AppleMusicParser()
manager = ChartDeltaManager("data/apple_music_mandopop_daily.csv")
# 1. 抓取今日页面
html_content = fetcher.get_playlist_html(target_url)
# 2. 解析出今日排行
today_songs = parser.parse_chart(html_content)
# 3. 进行历史增量比对,生成带 Peak 和 Weeks 的最终数据
manager.process_daily_update(today_songs)
print("🎉 今天的榜单巡视任务圆满结束,去喝杯咖啡吧!")
if __name__ == "__main__":
main()
经过几天运行后,输出的 apple_music_mandopop_daily.csv 示例结果:
| Rank | Song | Artist | Weeks_On_Chart | Peak_Position | Date_Scraped |
|---|---|---|---|---|---|
| 1 | 搁浅 | 周杰伦 | 1.12 | 1 | 2023-10-25 |
| 2 | 乌梅子酱 | 李荣浩 | 0.84 | 2 | 2023-10-25 |
| 3 | 大艺术家 | 蔡依林 | 2.52 | 1 | 2023-10-25 |
| 4 | 悬溺 | 葛东琪 | 0.14 | 4 | 2023-10-25 |
(看!通过每天的比对,我们精准计算出了《大艺术家》已经上榜 2.5 周,并且历史最高拿过第 1 名,而《悬溺》是今天刚空降的第 4 名!这就是数据的魔力!)
🔟 常见问题与排错(强烈建议写)
在国际音乐流媒体平台的战场上,你会遇到一些玄学问题,我帮你排好了雷:
- URL 抓不到指定的华语歌曲 :Apple 会根据你的 IP 自动重定向。确保你在 URL 路径里死死钉住
/cn/(国区)或/tw/(台区),有时候还需要挂上相应的代理网络。 - DOM 树完全为空壳(SPA 单页应用陷阱) :如果 BeautifulSoup 死活解析不出
div.song-name,打开浏览器的 Network,你会发现 Apple 的页面是 Ember.js 动态渲染的。终极解法 是用正则表达式re.search(r'<script type="application/json" id="shoebox-media-api-cache-amp-music">(.*?)</script>', html)把内嵌的 JSON 直接暴力抠出来,然后用json.loads()解析! - 歌手名提取有脏数据 :有的合唱歌曲会有
feat. XXX,建议在存入 CSV 前,用 Python 的replace('feat.', '&')清洗一下,保证歌手字段的统一。
1️⃣1️⃣ 进阶优化(可选但加分)
为了将你的专业性拉满,按照我们的严格要求,写一段脚本,读取你计算好的增量数据,生成一张全英文元素的 Top 5 流行趋势可视化散点柱状图 (apple_music_trend.png)!这图直接贴到工作汇报里,老板都会忍不住给你加薪!
python
import pandas as pd
import matplotlib.pyplot as plt
import os
def generate_english_trend_chart():
csv_file = "data/apple_music_mandopop_daily.csv"
if not os.path.exists(csv_file): return
df = pd.read_csv(csv_file)
# 筛选出今天榜单的前 5 名,并计算它们距离 Peak 的差距
top5 = df.head(5).copy()
# 创建纯英文图表
plt.figure(figsize=(10, 6))
# 用柱状图展示在榜时长(周数)
bars = plt.bar(top5['Song'].apply(lambda x: str(x).encode('utf-8').decode('utf-8')),
top5['Weeks_On_Chart'], color='#FA243C', alpha=0.8, label='Weeks on Chart')
# 用红心散点展示历史峰值
plt.scatter(top5['Song'].apply(lambda x: str(x).encode('utf-8').decode('utf-8')),
top5['Peak_Position'], color='#000000', s=100, zorder=5, label='Peak Position')
# 强制全部英文描述
plt.title('Apple Music Mandopop Top 5: Lifespan vs Peak Position', fontsize=15, fontweight='bold', pad=20)
plt.xlabel('Song Title (Mandopop)', fontsize=12)
plt.ylabel('Metrics Value', fontsize=12)
# 解决中文显示方块的问题(如果是纯英文环境,可加入拼音转换逻辑,这里按常规展示)
plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial'] # 兼容设置
plt.xticks(rotation=30)
plt.legend(loc='upper right')
plt.grid(axis='y', linestyle='--', alpha=0.5)
plt.tight_layout()
plt.savefig('data/apple_music_trend.png', dpi=300)
print("📈 超酷的!全英文趋势分析图已生成至 data/apple_music_trend.png")
# generate_english_trend_chart()
1️⃣2️⃣ 总结与延伸阅读
呼!太爽了!🥳 今天我们不仅用 Python 撬开了 Apple Music 的大门,更核心的是,我们手写了一套状态机算法,让原本没有历史维度的网页死数据,变成了动态追踪寿命(Weeks)和峰值(Peak)的活雷达。这才是爬虫进阶到"数据分析师"的真正分水岭!
下一步延伸:
当你把这套系统挂在服务器上跑了半年后,你会拥有一座巨大的宝库。届时,强烈建议你把 Spotify 的 API 也接进来,做一个"Apple Music vs Spotify 华语双榜交叉对比"的大盘!看看同一种华语流行文化,在两个国际平台的用户品味到底有何差异!
🌟 文末
好啦~以上就是本期的全部内容啦!如果你在实践过程中遇到任何疑问,欢迎在评论区留言交流,我看到都会尽量回复~咱们下期见!
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦~
三连就是对我写作道路上最好的鼓励与支持! ❤️🔥
✅ 专栏持续更新中|建议收藏 + 订阅
墙裂推荐订阅专栏 👉 《Python爬虫实战》,本专栏秉承着以"入门 → 进阶 → 工程化 → 项目落地"的路线持续更新,争取让每一期内容都做到:
✅ 讲得清楚(原理)|✅ 跑得起来(代码)|✅ 用得上(场景)|✅ 扛得住(工程化)
📣 想系统提升的小伙伴 :强烈建议先订阅专栏 《Python爬虫实战》,再按目录大纲顺序学习,效率十倍上升~

✅ 互动征集
想让我把【某站点/某反爬/某验证码/某分布式方案】等写成某期实战?
评论区留言告诉我你的需求,我会优先安排实现(更新)哒~
⭐️ 若喜欢我,就请关注我叭~(更新不迷路)
⭐️ 若对你有用,就请点赞支持一下叭~(给我一点点动力)
⭐️ 若有疑问,就请评论留言告诉我叭~(我会补坑 & 更新迭代)
✅ 免责声明
本文爬虫思路、相关技术和代码仅用于学习参考,对阅读本文后的进行爬虫行为的用户本作者不承担任何法律责任。
使用或者参考本项目即表示您已阅读并同意以下条款:
- 合法使用: 不得将本项目用于任何违法、违规或侵犯他人权益的行为,包括但不限于网络攻击、诈骗、绕过身份验证、未经授权的数据抓取等。
- 风险自负: 任何因使用本项目而产生的法律责任、技术风险或经济损失,由使用者自行承担,项目作者不承担任何形式的责任。
- 禁止滥用: 不得将本项目用于违法牟利、黑产活动或其他不当商业用途。
- 使用或者参考本项目即视为同意上述条款,即 "谁使用,谁负责" 。如不同意,请立即停止使用并删除本项目。!!!
