拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Python爬虫实战:音乐平台歌单数据抓取与解析

简介这是一份以网易云音乐为示例的Python爬虫完整实例适合学习网络数据抓取、存储与可视化的Python初学者。项目利用requests和BeautifulSoup/lxml解析华语歌手信息通过歌手ID获取热门50首音乐及评论数并以SQLite/MySQL保存数据最终用Matplotlib或HTML图表展示最受欢迎歌曲。压缩包共18个文件、仅35KB涵盖6个Python脚本如artists.py、charts.py、music_by_artist.py、sql.py、decrypt.py、Jupyter Notebook过程记录、HTML可视化结果及PyCharm项目配置结构清晰便于对照学习。已有226人学习浏览读者可从中掌握网页解析、数据入库、评论筛选到可视化的实现方式以及面对网页结构变化和反爬机制时的基本应对思路。整个项目演示了从网页结构分析、请求发送、HTML解析、数据清洗入库到最终图表呈现的完整链路适合作为课程设计或入门项目的参考。 前阵子有个朋友找我帮忙说他在某音乐平台存了十几个歌单加起来上千首歌想把歌曲信息统一整理成表格方便按歌手、年份、曲风做统计顺便迁移到别的播放器里。我劝他先用现成的导出工具结果试了两个都不太满意要么只能导出歌单名要么需要授权第三方服务心里总觉得不踏实。后来干脆自己写了个Python爬虫几十行代码搞定顺手把踩过的坑也整理了出来。这篇博文就围绕“Python爬取某音乐平台实例”来展开把整套思路、关键代码、反爬应对和排错经验都讲透。它能解决什么问题简单说就是把网页上公开的歌单、歌曲名、歌手、专辑、时长这类信息批量抓下来并整理成结构化数据。适合刚学完Python基础、想拿真实项目练手的同学也适合有音乐数据整理需求、但不想手动复制粘贴的人参考。整个项目我会拆成几个部分来讲先聊思路和选型再讲页面分析和请求细节然后给出完整代码最后整理一批我实际踩过的问题。为了保证内容合规本文所有示例URL都使用占位域名核心讲的是通用实现方法你只需要把目标地址替换成自己需要分析的页面即可。1. 项目整体设计与思路拆解1.1 动手之前先把目标拆成一份清单写爬虫最容易犯的错就是一上来就写代码。我见过太多人打开网页一看结构复杂直接懵了然后开始瞎试选择器试到哪儿算哪儿。正确做法是先花十分钟把目标拆清楚。这个项目的核心目标很明确给定一个歌单页面提取歌曲信息支持翻页最后输出结构化文件。围绕这个目标我拆成了四个子问题如何拿到歌单页面的HTML内容如何从HTML中解析出歌曲标题、歌手、专辑、时长如何处理歌单内多页数据避免重复和遗漏如何把结果保存成方便后续分析的格式这四个问题对应四个模块请求模块、解析模块、调度模块、存储模块。模块化最大的好处是出问题时好排查——比如今天发现某个字段解析不到了只用改解析函数明天平台改了请求校验也只需要动请求函数其他部分完全不受影响。拆完目标后我还习惯顺手做一个字段清单。这个项目的字段设计很简单就是歌名、歌手、专辑、时长四项。字段清单写得越早后面写代码越省事因为解析完的数据结构从一开始就是确定的代码不会反复推翻重写。1.2 技术选型轻量方案优先技术选型方面网上很多教程上来就是Selenium或者Playwright动辄启动一个浏览器看着很酷实际项目里其实很不划算。页面渲染慢、资源占用高、容易被识别而且一旦平台改版基于坐标和元素的写法维护成本极高。我的选择是requests加BeautifulSoup两个库都是轻量方案requests负责发HTTP请求拿HTMLBeautifulSoup负责解析HTML结构。这套组合对绝大多数静态渲染的歌单页面都够用了启动快、代码短、依赖少适合快速出结果。什么时候才需要考虑Selenium或者直接调接口我的经验是当页面内容靠JavaScript动态渲染HTML源码里看不到歌曲数据时有两种替代思路。第一种是打开浏览器开发者工具翻到Network面板刷新页面找到返回JSON数据的XHR请求直接模拟这个接口这是最优解既稳定又高效。第二种才是上Selenium用于接口加密严重、找不到JSON的场景。但通常来说音乐平台的歌单页为了SEO考虑歌手名、歌名这些核心数据都会渲染在HTML里所以requests方案基本够用。额外说一句如果目标平台开放了官方API优先用API爬网页永远是兜底方案。API有正式文档、有速率限制说明用起来踏实得多。1.3 合规边界爬虫也要讲规矩聊爬虫绕不开合规问题这个部分我必须多说两句。我在项目里定了几条铁律只爬公开可见的数据不做登录绕过遵守目标网站的robots协议以及页面里的服务条款控制请求频率绝不给对方服务器造成压力数据只用于个人学习、整理和研究不用于任何商业用途。更具体一点我在代码里会故意把请求间隔设得比较保守比如1.5到3.5秒随机延时。一方面是为了降低被封风险另一方面也是最基本的网络礼仪。音乐平台上的歌曲本身有版权歌单数据结构也有平台的心血把它们当成学习素材没问题但别拿去搞成公开的数据库或者商业产品。想明白这些边界再动手心里有底代码写起来也踏实。2. 核心细节解析与实操要点2.1 页面结构分析先看数据藏在哪页面结构分析是整个项目里最费时间、也最考耐心的环节。我习惯用浏览器自带的开发者工具快捷键F12打开找到Elements面板直接查看歌单页面里歌曲列表的HTML结构。目标平台虽然不同但歌单页的结构大体有规律歌曲列表通常在一个容器节点内每首歌对应一个列表项列表项里会有歌名、歌手、专辑、时长这些信息分别嵌套在不同的标签中。比如歌名一般在带链接的标签里歌手和专辑常见的是文本标签时长则可能是单独的类名。我看结构时最关心的是能不能找到一个稳定的父容器选择器。比如某个页面的歌曲列表可以通过ul.song-list定位每一首歌是li元素那就先把这个层级关系记下来。选择器的稳定程度直接决定了解析代码能活多久。写得太具体比如div div ul li div span a这种特别长的路径平台只要微调布局就全废了。我一般更倾向于给关键标签加类名靠类名而不是层级去定位。如果发现页面数据在HTML源码里找不到只有一堆JavaScript变量那大概率是走了动态渲染。这时候回到开发者工具的Network面板过滤XHR请求找里面带song、playlist这种关键词的请求点开看响应是不是JSON。如果是直接复制请求地址加好请求头用requests去请求解析JSON比解析HTML还要简单。2.2 请求头伪装与基础反爬规避拿到URL之后第一件要做的就是发送请求。直接用默认requests请求往往很快就会被拦因为普通爬虫的请求头一眼就能识别出来。我习惯手动设置一组完整的请求头至少包含User-Agent和Referer。User-Agent就是你浏览器类型的名片我用的一般是Chrome浏览器的默认UA字符串。Referer也非常重要很多平台会校验这个字段必须把目标网站的首页地址或歌单页地址填进去。这两个字段配合大多数基础的反爬校验就能通过。另外有些平台会定期更新UA校验池所以我会把UA写成一个列表每次请求随机选一个减少特征积累。还有一个小细节超时时间一定要设置。requests.get(url, timeout10)这个参数救了不知道多少次如果不设超时某个请求卡住了程序就一直卡在那里整个爬虫任务就废了。设置合理超时后配合异常捕获顶多是当前请求失败不影响后续执行。2.3 翻页逻辑、字段规范与去重歌单里的歌曲数量通常不止一页翻页逻辑是躲不掉的。翻页方式大致分两种一种是URL参数翻页比如?page1、?page2这样另一种是点击“加载更多”按钮然后异步请求数据。前者简单直接循环URL就行后者需要在Network面板里找到对应的请求参数模拟发送JSON请求。翻页容易遇到的问题就是重复数据。我处理的方式很简单每首歌曲生成一个唯一键比如“歌名加歌手”的组合。因为同一个歌单里同一首歌不太可能由同一歌手唱两次这个键的区分度已经足够。每次解析完先检查键是否出现过没出现过才真正保存。字段清洗也是一环。直接从网页上抓下来的文本经常带着空格、换行、日期的零填充等问题。我的原则是统一用strip清理空白字符时长格式统一成分:秒专辑名如果为空就填“未知专辑”。字段规范好了后面做统计、做去重、做导入都会顺利很多。3. 实操过程与核心代码实现3.1 环境准备与依赖安装环境这块建议直接用Anaconda它自带Python和常用科学计算库省去很多配置麻烦。没有也没关系普通Python环境完全够用只需要把两个依赖装上pip install requests beautifulsoup4如果用的是Anacondaconda安装方式也顺手写一下conda install requests beautifulsoup4安装完成后建议顺手验证一下版本避免和旧版本冲突。命令很简单在Python交互式环境里输入import requests和from bs4 import BeautifulSoup不报错就说明没问题。3.2 实现页面请求函数请求函数最主要的任务就两个带伪装发起请求处理异常情况。我用一个单独的函数封装它方便统一管理。import requests import time import random HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Referer: https://music.example.com/ } def fetch_page(url, retries3): for attempt in range(retries): try: resp requests.get(url, headersHEADERS, timeout10) if resp.status_code 200: resp.encoding resp.apparent_encoding return resp.text print(f请求失败状态码: {resp.status_code}) except requests.RequestException as e: print(f第{attempt 1}次请求异常: {e}) time.sleep(random.uniform(1.5, 3.5)) return None这里有两个细节值得说明。第一resp.encoding resp.apparent_encoding是处理乱码最省事的办法apparent_encoding会根据页面内容推断实际编码比手动猜编码要靠谱得多。第二重试次数我设为3次每次重试之间随机延时避免被识别为固定频率的机器行为。3.3 实现页面解析函数解析函数的核心思路就是BeautifulSoup的选择器定位。我以一个常见的歌单页面结构为例列表项是li元素歌名在.song-title a里歌手在.singer-name里专辑在.album-name里时长在.duration里。这个结构不是固定模板实际使用时请按F12查看你目标页面的真实类名但定位思路完全一样。from bs4 import BeautifulSoup def parse_playlist(html): soup BeautifulSoup(html, html.parser) songs [] items soup.select(ul.song-list li) for item in items: title_tag item.select_one(.song-title a) singer_tag item.select_one(.singer-name) album_tag item.select_one(.album-name) duration_tag item.select_one(.duration) if not title_tag: continue songs.append({ title: title_tag.text.strip(), singer: singer_tag.text.strip() if singer_tag else 未知歌手, album: album_tag.text.strip() if album_tag else 未知专辑, duration: duration_tag.text.strip() if duration_tag else 未知时长, }) return songs这里我特别想强调的是写解析逻辑时不到万不得已不要用索引或者说find_all再取[0]。因为页面结构一变动索引就失效了而类名定位相对稳定配合select_one取第一个匹配项不容易出错。即使某个字段缺失也只是这一项数据变成默认值不会导致整个解析函数崩溃。3.4 实现翻页、去重与CSV落盘这三个功能可以拆成三个小函数然后在一个主流程里串联。先看翻页加延时def fetch_all_pages(base_url, total_pages): all_songs [] for page in range(1, total_pages 1): url f{base_url}?page{page} html fetch_page(url) if not html: print(f第{page}页获取失败跳过) continue songs parse_playlist(html) print(f第{page}页解析到{len(songs)}首歌) all_songs.extend(songs) time.sleep(random.uniform(1.5, 3.5)) return all_songs注意每次翻页后我仍然加了随机延时因为即使fetch_page内部已经有一次延时但翻页循环之间再留点间隔更稳妥。有些平台对特定接口的访问频率特别敏感宁慢勿快。去重函数我之前已经讲过思路直接用集合记录键值对def dedupe(songs): seen set() result [] for song in songs: key (song[title], song[singer]) if key not in seen: seen.add(key) result.append(song) return result最后是保存CSV。这里有一个特别常见的坑直接用encodingutf-8保存Excel打开中文会乱码。正确做法是使用utf-8-sig编码它会额外写入一个BOM头Excel就能正确识别中文了。import csv def save_to_csv(songs, filenamesongs.csv): with open(filename, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[title, singer, album, duration]) writer.writeheader() writer.writerows(songs) print(f已保存{len(songs)}条记录到{filename})3.5 把整个流程串起来跑一遍把上面的模块拼起来主流程就非常简洁了if __name__ __main__: base_url https://music.example.com/playlist/123456 total_pages 5 # 根据歌单实际页数调整 songs fetch_all_pages(base_url, total_pages) songs dedupe(songs) save_to_csv(songs) print(全部任务完成)跑完之后你会看到一个songs.csv文件里面每一行就是一首歌的信息。我建议在本地用Excel或者pandas打开看一眼确认数据格式正确、无乱码、无重复再进入下一步使用。整个过程虽然简单但已经把爬虫项目最核心的几个环节都覆盖到了。4. 常见问题与排查技巧实录4.1 请求被拒与访问频率问题请求被拒恐怕是爬虫项目里最常见的故障现象也五花八门有的直接返回403有的返回200但内容是一段验证页面还有的第一次请求成功第二次就失败。我把常见的现象、原因和解决方案整理成了一张表现象可能原因解决方案返回403请求头不完整被识别出非浏览器补全User-Agent和Referer使用浏览器完整请求头返回200但内容是验证码页IP或请求频率触发风控降低频率增加请求间隔必要时使用代理IP池前几次正常后续全失败临时限流触发程序退出等待几分钟再试把延时调大请求超时网络波动或目标响应慢设置timeout参数增加重试机制我自己实际遇到最多的情况是第一个请求头不完整。很多爬虫新手只设置了User-Agent没设置Referer结果平台直接拒绝服务。补上Referer后问题马上消失。所以不管遇到什么反爬第一反应都应该是检查请求头而不是急着上代理。4.2 乱码、空数据与结构变更第二个高发问题就是乱码和解析为空。乱码的根源基本都在编码判断我之前用resp.encoding resp.apparent_encoding就是为了解决这个。如果还是乱码可以手动指定页面meta标签里声明的字符集常见的有utf-8和gbk两个选项。解析为空就比较麻烦了。如果你明明在浏览器里看到歌单里有数据但解析结果就是空列表建议按这个顺序排查先确认HTML内容真的抓到了把html的前500个字符打印出来看看。检查页面是否用了JavaScript动态渲染HTML源码里根本没有歌曲节点。用开发者工具验证你的选择器。直接在Elements面板里CtrlF搜索类名确认页面里确实存在这个类。看是否存在多个页面版本比如PC版和移动版结构不同检查你请求的是不是正确的URL。页面结构变更只能靠长期维护我见过不少歌单页隔几个月就改一次布局。对策是解析函数尽量抽象选择器集中统一管理方便改一处就全局生效。4.3 运行效率与任务自动化建议爬虫跑得慢也是常见抱怨尤其是歌曲数量大时几千首歌可能要跑十几分钟。我的建议是不要盲目追求速度安全永远是第一位。所谓“快”更应该体现在流程设计上——比如断点续爬、日志记录、失败重试。这里分享一个我后来加上去的改进把每次解析完的数据立刻写进CSV而不是最后统一保存。这样即使程序中途崩溃已经爬取的部分还在重新运行不会从头再来。实现方式很简单就在解析函数后面追加写入而不是放进列表最后一次性写。如果歌单数量特别多还可以考虑用pandas处理分析读入CSV后按歌手分组统计、按年份筛选都很方便。到了这一步这个爬虫项目就真正闭环了从网页抓取到结构化存储再到数据分析一条完整的数据管道跑通。最后再分享一点个人经验。这类爬虫任务建议保存在单独的Python文件里文件名写清楚用途比如song_crawler.py。以后平台改版导致爬虫失效时只需要回来改解析函数里的选择器就行其他模块完全不用动。我自己的习惯是把这类脚本放在一个叫crawlers的文件夹里每个网站单独一个子目录README里记录目标页面结构和上次更新时间。这样过几个月再看也不会陌生维护成本几乎为零。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门