Python爬虫实战:音乐榜单数据采集与分析

作者:起个名字好难2026.11.07 01:23浏览量:0

简介:本文以音乐榜单为练手场景,系统讲解如何用Python完成页面结构分析、分页规律识别、请求与解析、数据清洗与持久化,并延伸出歌手热度、时长分布等分析思路。读者可掌握一套可复用的榜单采集方法论,理解反爬应对与工程化取舍,并将其迁移到其他结构化列表页场景。

用 Python 爬虫分析音乐榜单并采集歌曲数据:从页面分析到数据洞察

音乐排行榜是典型的“结构规整、字段清晰、分页有规律”的列表型页面,非常适合作为爬虫能力的综合训练场。本文不以某个具体站点为推广对象,而是以通用音乐榜单为技术背景,完整拆解一套可复用的采集与分析方案:先做页面结构与分页规律分析,再实现请求、解析、清洗与持久化,最后延伸到数据洞察与工程化改进。文中的 URL、字段与选择器均以示意方式给出,读者可按目标页面的实际结构调整。

一、为什么值得做这样一个项目

1. 对初学者:一条完整的爬虫能力链路

榜单类页面通常没有复杂的加密参数与高频风控,适合把爬虫的核心环节一次性打通:

  • 基础请求:使用 requests 发送 HTTP 请求,设置请求头、超时与重试;
  • 页面解析:用 BeautifulSoup 提取 DOM 中的歌曲名、歌手、时长等字段;
  • 数据清洗:处理空值、去重、字段类型转换与格式归一;
  • 数据持久化:落地为 CSV/Excel,或写入数据库做长期跟踪;
  • 异常处理:应对请求超时、状态码异常、标签缺失与结构变更。

这五个环节构成的链路,是后续所有复杂采集任务的骨架。把这条链路跑通,比记忆某个库的 API 更有价值。

2. 对音乐爱好者与从业者:把榜单变成可分析的数据

采集到的“歌曲名 + 歌手 + 时长”三要素,看似简单,却能支撑多维度分析:

  • 歌手热度:统计榜单中上榜次数最多的歌手,观察头部艺人的集中度;
  • 时长规律:分析流行歌曲的主流时长区间,理解听众的消费习惯;
  • 风格关联:结合歌手流派标签,观察不同风格在榜单中的占比;
  • 个人参考:按热度与时长偏好筛选歌曲,定制私人歌单。

3. 数据的延伸用途

  • 可视化:用 Matplotlib/Seaborn 绘制歌手上榜频次柱状图、时长分布直方图;
  • 长期跟踪:写入数据库,按周期采集,观察歌曲排名的变化趋势;
  • 指标衍生:计算上榜持续天数、排名波动幅度等二级指标。

二、页面分析与分页规律识别

1. 确认目标内容

打开榜单页面,确认需要采集的字段:歌曲名、歌手名、歌曲时长。同时观察页面是否为服务端渲染——若关键信息直接出现在 HTML 中,解析成本最低;若由前端脚本异步填充,则需考虑接口抓取或渲染方案。

2. 识别分页规律

榜单页面常见两种分页形式:显式翻页按钮,或通过 URL 路径中的页码参数控制。后者更常见也更易处理:形如 /rank/home/{page}-8888.html 的路径,把 {page} 从 1 递增即可获得不同页面的数据。

识别分页规律的关键是验证边界:先确认每页条目数(例如每页 22 首),再用“总条目数 ÷ 每页条目数”推算总页数。若榜单共 500 首、每页 22 首,则前 22 页各 22 首、第 23 页为剩余 16 首,共 23 个页面。这个推算过程要在代码中用常量显式表达,避免魔法数字散落。

3. 字段定位

在浏览器开发者工具中定位歌曲条目的容器元素,确认歌曲名、歌手、时长分别位于哪个子节点。优先选择语义稳定的选择器(如列表项容器 + 类名),避免依赖易变的层级路径。

三、代码实现:请求、解析与清洗

1. 配置与常量

  1. import time
  2. import requests
  3. import pandas as pd
  4. from bs4 import BeautifulSoup
  5. from requests.exceptions import RequestException
  6. BASE_URL = "https://example.com/rank/home/{page}-8888.html" # 示意地址
  7. HEADERS = {
  8. "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
  9. "AppleWebKit/537.36 (KHTML, like Gecko) "
  10. "Chrome/120.0.0.0 Safari/537.36",
  11. "Accept-Language": "zh-CN,zh;q=0.9",
  12. }
  13. PAGE_NUM = 23 # 总页数,由条目总数与每页条数推算
  14. DATA_LIST = []

2. 单页抓取:超时、重试与状态码

  1. def fetch_page(page: int, retries: int = 3) -> str | None:
  2. url = BASE_URL.format(page=page)
  3. for attempt in range(1, retries + 1):
  4. try:
  5. resp = requests.get(url, headers=HEADERS, timeout=10)
  6. if resp.status_code == 200:
  7. resp.encoding = resp.apparent_encoding
  8. return resp.text
  9. print(f"[WARN] page={page} status={resp.status_code}")
  10. except RequestException as e:
  11. print(f"[ERROR] page={page} attempt={attempt} {e}")
  12. time.sleep(1.5 * attempt) # 退避,避免密集重试
  13. return None

这里有两个工程细节值得强调:超时必须显式设置,否则个别慢请求会拖垮整体流程;重试要有退避,线性或指数退避均可,但不要在失败后立即高频重试。编码方面,优先使用响应头声明的编码,必要时用 apparent_encoding 兜底,避免中文乱码。

3. 解析与清洗

  1. def parse_page(html: str) -> list[dict]:
  2. soup = BeautifulSoup(html, "html.parser")
  3. rows = []
  4. for item in soup.select("ul.rank_list > li"):
  5. name_el = item.select_one(".song_name")
  6. singer_el = item.select_one(".singer_name")
  7. time_el = item.select_one(".song_time")
  8. if not name_el:
  9. continue # 关键字段缺失则跳过,保证数据质量
  10. rows.append({
  11. "song": name_el.get_text(strip=True),
  12. "singer": singer_el.get_text(strip=True) if singer_el else "未知",
  13. "duration": time_el.get_text(strip=True) if time_el else "",
  14. })
  15. return rows

清洗环节建议在解析后集中处理,而不是散落在各处:

  • 空值:歌手或时长缺失时填默认值,并记录缺失计数;
  • 去重:以“歌曲名 + 歌手”为业务主键去重,避免跨页重复;
  • 格式归一:把 mm:ss 形式的时长统一转换为秒数,便于后续统计;
  • 类型转换:排名、时长等字段转为数值类型,方便排序与聚合。

4. 主流程与持久化

  1. def main():
  2. for page in range(1, PAGE_NUM + 1):
  3. html = fetch_page(page)
  4. if not html:
  5. print(f"[SKIP] page={page}")
  6. continue
  7. DATA_LIST.extend(parse_page(html))
  8. time.sleep(0.5) # 控制请求节奏
  9. df = pd.DataFrame(DATA_LIST).drop_duplicates(subset=["song", "singer"])
  10. df["duration_sec"] = df["duration"].apply(to_seconds)
  11. df.to_excel("rank_songs.xlsx", index=False)
  12. print(f"共采集 {len(df)} 条记录")

to_seconds 可用简单的字符串切分实现:

  1. def to_seconds(text: str) -> int | None:
  2. parts = text.strip().split(":")
  3. try:
  4. if len(parts) == 2:
  5. return int(parts[0]) * 60 + int(parts[1])
  6. if len(parts) == 3:
  7. return int(parts[0]) * 3600 + int(parts[1]) * 60 + int(parts[2])
  8. except ValueError:
  9. return None
  10. return None

四、从数据到洞察:三类分析思路

歌手热度:按歌手分组计数并排序,取前若干名绘制柱状图。注意“同一歌手多首上榜”是热度信号,但也要留意合作曲目带来的归属歧义,可在清洗阶段约定归属规则。

时长分布:以秒为单位的时长做直方图或分箱统计,观察主流区间是否集中在 3~4 分钟;同时可计算中位数与分位数,比单一均值更能反映分布形态。

风格关联:若数据中包含流派标签,按流派统计占比并做交叉分析;若标签缺失,可借助公开的歌手信息做映射,但要明确标注数据来源与推断边界,避免把推断当事实。

五、工程化与合规建议

  • 控制请求节奏:加入固定间隔与随机抖动,避免对目标站点造成压力;
  • 结构变更防御:解析函数中对选择器做存在性判断,页面结构变化时降级而非崩溃;
  • 可观测性:记录每页的成功/失败、解析条数、缺失字段计数,便于快速定位问题;
  • 长期跟踪:将采集结果写入数据库并附加采集时间戳,形成时间序列,才能观察排名变化;
  • 合规与边界:只采集公开可见的列表信息,遵守目标站点的 robots 协议与服务条款,不采集个人隐私数据,不进行高频压测式抓取。

结语

榜单采集的价值不在“抓到数据”本身,而在于把页面分析、分页规律、请求控制、解析清洗、持久化与分析串成一条可复用的工程链路。掌握这套方法论后,面对结构化列表页的采集需求,你只需要替换选择器与字段定义,就能快速搭建出一条稳定、可维护的数据管道。