ARTICLE DETAIL

资讯详情

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

用Python爬虫抓取酷狗音乐热歌榜:从接口分析到CSV保存

用Python爬虫抓取酷狗音乐热歌榜:从接口分析到CSV保存 用Python爬虫去抓酷狗音乐热歌榜的歌曲名这件事在刚接触爬虫的朋友看来可能有点玄乎。最直观的想法是写个requests请求拿到页面HTML再用正则把歌名抠出来结果实际动手才发现网页源代码里根本没有歌名数据唯一能看到的是一个又一个的JS脚本和空荡荡的div标签。这篇文章就是我从页面分析、抓包定位接口、请求伪装、JSON解析到最终把结果保存成CSV的完整记录适合刚学完Python基础、想搞懂动态网页爬虫的读者也适合那些已经能爬静态页面、想进阶接口分析的老哥。我把这个项目的完整过程、踩坑记录和后续扩展方案都整理在下面代码可以直接拿去跑但更建议你跟着思路走一遍因为酷狗这类网站改版频繁接口和字段说不定哪天就变了掌握方法比复制代码重要得多。1. 项目定位与整体思路拆解1.1 为什么拿酷狗热歌榜当练手项目爬虫练手的网站很多博客、新闻、招聘网站都能爬但我最终选了酷狗音乐热歌榜不是因为它简单而是因为它非常适合用来完成“从静态网页爬虫到动态网页爬虫”的跨越。静态网页的特点是数据直接写在HTML里右键查看源代码就能看到内容新手学爬虫时爬豆瓣、爬天气都属于这一类。但现实中真正有价值的站点几乎都不会把核心数据直接放在HTML里因为那样既不好维护也不好做前端渲染。酷狗的热歌榜就是典型榜单的歌曲名、歌手、排名都是通过异步请求加载的数据以JSON形式返回再由JS逻辑渲染到页面上。选这种网站做练手项目能一次性接触到爬虫实战中最重要的几个环节开发者工具抓包、XHR请求分析、JSON数据解析、请求头伪装。这套技能在后续爬取其他网站时基本是通用的可以说学会了这一套市面上大部分动态加载的网页你都能自己分析着爬。另外一个现实原因是热歌榜的数据量适中榜单本身有固定的总条数不会动辄几百万条需要分布式爬虫对个人电脑和初学阶段非常友好。每天榜单还会更新意味着程序写好了不是跑一次就完事而是可以长期定时运行顺便练习数据采集的持续性。1.2 技术选型模拟浏览器还是直接抓接口面对一个动态加载的网页常见的爬虫方案主要有两条路。第一条是用Selenium或Playwright这类浏览器自动化工具直接驱动一个完整的浏览器去加载页面等JS执行完再把渲染后的HTML拿回来解析。这条路的好处是几乎不费脑子浏览器能看到的你就能抓到坏处是启动慢、内存占用高、代码笨重而且很多网站会对自动化浏览器做识别反而更容易触发反爬。第二条是直接分析网页发出的XHR请求找到返回JSON数据的那个接口用requests模拟这个请求拿数据。这条路的门槛在于你得会看开发者工具能从一堆请求里认出哪个才是真正干活的接口。但一旦找到运行效率是浏览器方案的几十倍一个循环能在几秒内抓完整个榜单而且更接近商业爬虫的做法。我自己在初学阶段两条路都走过最后在这个项目里毫不犹豫选择了第二条。原因是热歌榜这种场景非常典型数据请求和数据渲染是分离的接口返回的JSON结构通常很规整解析起来比从HTML里抠标签省事得多。以下是两种方案的对比对比维度浏览器自动化方案直接请求接口方案抓取速度慢每个页面都要等浏览器渲染快毫秒级完成请求代码复杂度简单但啰嗦需要大量等待逻辑中等需要解析JSON资源占用高动辄几百MB内存极低几十MB足够反爬识别风险容易被检测为自动化伪装好请求头后风险很低学习收益低主要是工具调用高能深入理解HTTP请求稳定性对网速和浏览器版本敏感只依赖接口是否变更当然这不代表Selenium没有用。有些网站做了非常强的前端加密接口参数是动态生成甚至混淆过的直接抓接口会很痛苦这时候浏览器自动化反而是更快的路。但就酷狗热歌榜这个项目来说接口方案明显更合适抓取效率和代码可维护性都好一个档次。2. 核心拆解从网页地址到真实数据接口2.1 动态加载页面的特征判断打开酷狗音乐的热歌榜页面时能看到完整的榜单信息歌曲名、歌手、排名全都展示得好好的但如果你直接在浏览器里右键“查看网页源代码”会惊讶地发现整个页面HTML里只有一个页面框架歌曲数据丝毫不见踪影。这就是动态加载的典型特征。网页的HTML只是一个空壳真正的数据是页面加载完成后通过一段段JS代码向后端发起XHR请求拿回来的。数据到手后再由JS动态创建DOM节点把内容“填”进页面里。用户看到的是渲染后的结果但爬虫抓到的原始HTML里什么也没有。判断一个页面是不是动态加载有个很简单的方法把浏览器的JS执行关掉或者用一种不支持执行JS的命令行工具去访问页面看看关键内容是否还存在。如果内容消失了说明数据靠JS加载直接GET页面是拿不到的必须去分析XHR请求。我在这个项目里的做法是直接打开开发者工具看请求。不用做太复杂的判断因为热歌榜页面切换歌曲分类、翻页的时候URL并没有发生明显变化但内容刷新了这已经足够说明数据是通过异步请求动态获取的。2.2 开发者工具抓包一步一步锁定接口打开Chrome浏览器进入酷狗热歌榜页面按F12打开开发者工具切到Network面板。这步操作是所有动态页面爬虫的起点。在Network面板里能看到页面加载过程中发出的所有请求有图片、CSS、JS、字体还有XHR和Fetch。关键是要从这一堆请求中找出哪个返回了歌曲数据。我的做法分成三步。第一步先把Network面板里的请求类型全部清空让面板处于干净的监听状态。然后手动在页面上触发一个数据刷新动作比如切换热歌榜的排序方式或者点击翻页。此时面板里会新出现一批请求重点关注类型为XHR的请求这些就是异步接口。第二步逐个点击这些XHR请求在Preview或Response标签页里查看响应内容。刚才页面明明刷新出了新的榜单所以响应里一定存在歌曲信息。找到那个响应内容是JSON且包含歌曲名字段的请求基本就是目标数据接口了。第三步确认接口的完整请求URL看Request Headers里的信息尤其注意Headers、Query String Parameters这些部分。酷狗热歌榜的接口通常是一个PHP或动态路由地址URL后面跟着ranktype、page、pagesize、rank等参数这些参数直接决定了返回的是哪个榜单、第几页数据、每页多少条。我实测下来这个接口返回的是标准的JSON格式数据里面有一个lists数组每个数组元素就是一首歌的信息歌曲名、歌手名、排名等字段都包含在内。有几个元素还包含时长、专辑名、HQ音质标识等额外信息后续要做数据分析也能用得上。2.3 JSON响应结构的解读与字段确认找到接口之后下一步就是看懂返回的JSON结构。直接在开发者工具的Response标签页里查看或者先把响应内容复制出来放到本地看一眼。我遇到的酷狗热歌榜接口返回结构大致是这样的{ status: 1, error_msg: , data: { lists: [ { ranking: 1, filename: 从前从前 - 某某歌手, songname: 从前从前, singername: 某某歌手, duration: 260 }, { ranking: 2, filename: 晚风 - 某某, songname: 晚风, singername: 某某, duration: 230 } ], total: 100 } }这里的filename字段很有意思它通常是“歌曲名 - 歌手名”的组合格式中间用空格加减号空格分隔。有些接口版本里没有songname和singername这两个独立字段只有filename一个字段这时候就得自己拆分字符串。我在代码里做了兼容处理优先取songname取不到再解析filename这样不管网站怎么改版代码都不容易直接崩掉。在看JSON的时候还有一个小技巧如果返回的内容特别长、不好定位直接在开发者工具的Response面板里按CtrlF搜索某个歌名比如你正在看的榜单第一首歌叫什么就搜什么搜到了就能快速跳转到歌曲数据所在的位置顺着往前看就能找到整个JSON的结构。3. 请求细节请求头与参数设计3.1 请求头不是玄学而是服务端识别身份的依据写爬虫的人都知道requests.get时要加上headers但很多新手不明白为什么非得加。道理其实和去实体店买东西类似你进店不打招呼、不看店员直接拿了东西就走服务员肯定觉得你不对劲。服务端也是这样一个正常的浏览器访问页面时会自带一堆请求头信息包括User-Agent、Referer、Accept等这些都是识别客户端身份的依据。我在这个项目里第一次发送请求时就吃了亏直接用requests.get没有带任何请求头结果返回的状态码根本不是200而是一串被拒绝的提示。后来把开发者工具里Request Headers的关键字段复制到代码里再发送请求就正常了。具体来说请求头里最重要的是User-Agent和Referer。User-Agent告诉服务端你用的是哪种浏览器、什么版本服务端会把它当作判断“你是不是正常人”的第一道门槛。Referer则是告诉服务端你从哪个页面跳转过来的酷狗这类音乐网站的接口通常只允许从自己的页面发起请求Referer不匹配就会拒绝访问。我在代码里使用的请求头长这样headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36, Referer: https://www.kugou.com/yy/rank/home/, Accept: application/json, text/javascript, */*; q0.01, Accept-Language: zh-CN,zh;q0.9, Connection: keep-alive, }这个User-Agent字符串是Chrome浏览器的标准UA几乎能通过绝大多数网站的初级反爬检测。注意不要用Python-requests自带的默认UA那个UA太明显了一眼就会被识别成爬虫。3.2 关键参数从哪里来以抓包结果为标准接口URL后面跟着的参数每一项都有它的作用。我抓到的酷狗热歌榜接口大致是下面这样https://www.kugou.com/yy/html/rank/inc/ranklist_music.html?ranktype1page1pagesize20rank1逐个解释一下这些参数。ranktype表示榜单类型不同数字对应不同榜单比如热歌榜、新歌榜、飙升榜各有各的编号。page表示页码决定返回第几页的数据。pagesize表示每页的条数我测试下来填20或30都没问题。rank的含义和具体榜单的配置有关。这些参数不是凭经验猜出来的而是从开发者工具里看的。点击翻页时去Network面板里看新发出的XHR请求URL后面跟的参数就是最准确的信息。有的接口参数做了加密看起来是一串乱码这种情况下直接以抓包结果为准能复现出来的请求就能拿到数据。在写代码时我习惯把参数单独用一个字典维护这样以后想改榜单、改页码都很方便。注意不要在代码里写死完整的URL尽量用params参数让requests自己拼因为参数多了之后手写URL容易出错。3.3 翻页逻辑与请求频率控制理解了page和pagesize之后翻页就变得非常自然循环里改变page的值就行。但要记住一个原则爬虫请求的频率要控制在合理范围内。我做这个项目时抓完整个榜单大约只需要请求5到10次。这也得益于接口方案的优势每次请求拿回来的JSON里就是20到100条结构化的数据没有必要频繁发请求。即便如此我还是在每轮请求之间加了time.sleep让整体节奏更接近人工浏览。这里多说一句很多新手爬虫被封IP不是因为爬了不该爬的内容而是因为请求太频繁。你想想一个人正常浏览网站每秒钟点五次按钮这本身就反常。爬虫的自我修养就是尽量低调宁可慢一点也不要触发反爬。4. 实操过程与核心代码实现4.1 环境准备与依赖安装这个项目用到的Python库非常少标准库加上requests就够了。环境是Python 3.9以上requests库用pip安装一下就行pip install requests如果你用的是Anaconda也可以用conda install requests不过一般直接pip就完事了。代码里还会用到csv、time、json这三个标准库这些是Python自带的不需要额外安装。有朋友问过我要不要装BeautifulSoup或者lxml答案是这个项目用不到。因为我们直接解析的是JSON不用处理HTMLrequests内置的json方法就能搞定一切。4.2 第一版代码只抓取热歌榜第一页先给出一版最精简的代码只抓取热歌榜第一页的数据目标是跑通整个流程、看到输出结果。代码里的核心逻辑是发送请求、把响应解析成JSON、遍历榜单列表提取歌曲名和歌手名。import requests import json import time url https://www.kugou.com/yy/html/rank/inc/ranklist_music.html params { ranktype: 1, page: 1, pagesize: 20, rank: 1, } headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36, Referer: https://www.kugou.com/yy/rank/home/, Accept: application/json, text/javascript, */*; q0.01, } resp requests.get(url, paramsparams, headersheaders, timeout10) resp.encoding utf-8 data resp.json() song_list data[data][lists] for item in song_list: songname item.get(songname, ) singername item.get(singername, ) if not songname: filename item.get(filename, ) if - in filename: songname, singername filename.rsplit( - , 1) print(item.get(ranking), songname, singername)运行之后控制台会打印出榜单第一页的歌曲排行。如果这一步能正常跑出结果说明接口地址和参数都是对的可以继续做多页抓取。如果运行时报错先不要怀疑代码优先检查headers里的User-Agent和Referer是否完整。我遇到过好几次都是因为少了一个Referer接口直接返回空数据。4.3 升级版多页抓取并保存到CSV第一版跑通之后我马上做了升级核心是增加两个能力支持循环翻页、把结果保存到CSV文件。CSV的好处是可以用Excel直接打开后续做数据分析或者导入数据库都很方便。import requests import csv import time url https://www.kugou.com/yy/html/rank/inc/ranklist_music.html headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36, Referer: https://www.kugou.com/yy/rank/home/, Accept: application/json, text/javascript, */*; q0.01, } def fetch_rank_page(page): params { ranktype: 1, page: page, pagesize: 20, rank: 1, } resp requests.get(url, paramsparams, headersheaders, timeout10) resp.encoding utf-8 data resp.json() songs [] for item in data[data][lists]: songname item.get(songname, ) singername item.get(singername, ) if not songname: filename item.get(filename, ) if - in filename: songname, singername filename.rsplit( - , 1) songs.append({ ranking: item.get(ranking), songname: songname, singername: singername, }) return songs def save_to_csv(all_songs, filenamekugou_hot_rank.csv): with open(filename, w, newline, encodingutf-8-sig) as fp: writer csv.DictWriter(fp, fieldnames[ranking, songname, singername]) writer.writeheader() writer.writerows(all_songs) def main(): all_songs [] for page in range(1, 6): print(f正在抓取第 {page} 页...) page_songs fetch_rank_page(page) if not page_songs: print(f第 {page} 页返回为空停止抓取) break all_songs.extend(page_songs) time.sleep(1) save_to_csv(all_songs) print(f抓取完成共保存 {len(all_songs)} 首歌曲) if __name__ __main__: main()代码里有两个细节值得说一下。一是CSV写入时用了utf-8-sig编码因为直接用utf-8编码保存的文件用Excel打开会出现中文乱码utf-8-sig带BOM头Excel才能正确识别。二是每抓完一页就sleep一秒既避免请求过快也给服务器留出响应时间。我在实际运行中五页数据大概花了五六秒抓下来一百首歌效率非常理想。如果只想抓前二十首pagesize改成20、只跑第一页就行。如果想抓完整榜单把range范围调大或者直接从接口返回的total字段里读取总条数再反推总页数让代码自动决定循环几次。4.4 运行结果与数据示例代码跑完之后当前目录下会生成一个kugou_hot_rank.csv文件用Excel或者文本编辑器打开能看到类似这样的内容排名歌曲名歌手1从前从前某歌手A2晚风某歌手B3如果的事某歌手C4小美满某歌手D5我会等某歌手E数据非常规整拿去做词云、做歌手热度排行、做榜单变化追踪都是现成的素材。而且因为接口返回的是结构化JSON相较于从HTML里做正则匹配解析出错的可能性低很多。5. 常见问题与排查技巧实录5.1 请求被拒绝或返回403这是新手最容易碰到的问题表现是requests.get的返回码直接是403或者响应内容里面提示访问被拒绝。绝大多数情况下原因就是请求头不完整。服务端对没有User-Agent的请求会直接判定为机器人对Referer不匹配的请求也会果断拒绝。解决办法很简单把浏览器开发者工具里看到的请求头原样复制到代码里。尤其注意User-Agent和Referer这两项缺一不可。我第二次遇到403时是因为Referer复制错了少写了结尾的斜杠补齐之后马上恢复正常。5.2 JSON解析时提示KeyError或数据为空代码能正常返回但resp.json()之后找不到想要的字段提示KeyError或者data[data][lists]拿到的是一个空列表。这种问题多半是接口改了返回结构或者参数不对导致返回了错误提示。我用了一个通用解决方案在解析之前先把返回的原始文本打印出来看一眼确认当前接口返回的到底是什么格式。有些接口在请求失败时也会返回200状态码但响应内容是一串错误提示字符串而不JSON格式此时直接调用json()就会报错。打印原始文本能让你快速定位问题。另外要记得给字段提取加保险。代码里优先取songname取不到就解析filename这看起来只是一个小细节实际上救了程序好几次命。因为同一个网站在不同版本的接口中字段命名是有差异的兼容性的代码能让爬虫活得更久。5.3 翻页抓取时某页数据为空我在测试过程中发现循环翻页的时候有时候某一页的数据返回为空导致爬虫中断。后来我查了一下发现是page参数的问题。有些接口的页码从0开始计数有些从1开始两种情况下第一页的数据是不同的。从0和从1的区别虽然只有一行代码但如果不注意你按自己的习惯写了一个range循环而接口恰好是从0开始的就会漏掉第一页或者最后一页。我的建议是不要猜手动在浏览器里翻到不同页观察URL里的page参数直接照着参数写。代码里加上判断如果某一页返回为空就停止循环这也是一种自我保护。5.4 中文乱码问题爬下来歌曲名全是乱码的可能性不大但如果用requests直接获取响应再手动decode非常容易搞出乱码。问题的根源是编码处理方式不对。请求网页时不要自己手动猜编码会非常被动。接口返回的是UTF-8编码直接用resp.json()方法让requests自动处理编码就不会有乱码问题。保存CSV文件时注意用utf-8-sig编码否则Excel打开会乱。5.5 常见问题速查表我把这个项目里最容易踩的坑整理成一张表方便大家排查问题的时候快速对照现象可能原因解决办法返回403缺少User-Agent或Referer不匹配补全请求头从开发者工具复制JSON解析报错响应不是JSON或接口参数错误打印原始文本核对参数数据为空接口变更或字段名变化搜索歌名重新定位字段Excel中文乱码CSV编码不是utf-8-sig保存时使用utf-8-sig编码翻页重复或漏页page起始值写错以抓包结果为准打印页码核对被封IP请求频率过高增加sleep降低请求频率6. 项目延伸从抓取到数据应用6.1 把榜单数据变成可视化作品爬下来的热歌榜如果只是躺在CSV里其实有点浪费。我把它顺手做了很多延伸处理第一个是统计歌手出现次数。热歌榜一百首歌可能只对应六七十个歌手用collections.Counter一统计就能得到谁的歌上榜最多这本身就是一种内容价值。第二个是我把歌曲名做成了词云。用jieba分词处理歌曲名再用wordcloud生成词云图能直观看出这一段时间大家都爱听什么旋律关键词。整个过程加起来不到五十行代码但视觉效果很直观发到朋友圈也很有成就感。这里面用的库是jieba和wordcloud需要额外安装。6.2 定时抓取追踪榜单变化热歌榜每天都会更新如果只跑一次脚本确实没什么意思。我的做法是写一个定时任务每天固定时间抓一次数据然后存到SQLite数据库里字段里加上抓取日期。这样累积一个月的榜单数据就能做“歌曲排名变化曲线”看出哪首歌是突然爆红哪首歌在榜单上持续霸榜。实现方式有两种简单的是用Windows计划任务或Linux系统的crontab每天调用一次python脚本轻量粗暴。复杂一点的是直接在Python代码里用APScheduler设置定时任务好处是跨平台逻辑都在一个脚本里维护。我个人更推荐后者因为APScheduler的配置非常直观from apscheduler.schedulers.blocking import BlockingScheduler def job(): print(开始抓取今日榜单...) scheduler BlockingScheduler() scheduler.add_job(job, cron, hour9, minute30) scheduler.start()每天上午九点半自动抓取配合前一步的CSV或SQLite存储能做到完全无人值守。6.3 合理抓取尊重网站资源最后说点实际的。爬虫写出来容易但用起来要注意边界。我个人在这个项目里积累了几条自律规则也建议大家在练手时遵守。第一条是控制频率。抓一个几百条的榜单没必要并发、没必要开多线程单线程加sleep已经足够快。高频请求既容易触发反爬也会给目标服务器增加负担得不偿失。第二条是注意用途。你自己做数据分析、做学习研究完全没有问题但如果要把数据用在商业化产品里就必须先了解网站的用户协议和相关版权规定。榜单数据的收集和使用都是有边界的别因为爬虫技术让你惹上不必要的麻烦。第三条是保持克制。每次写爬虫的时候多想一想目标网站是否对服务器有保护机制接口是否有使用限制。真正成熟的爬虫工程师从来不追求把网站数据“爬穿”而是懂得适可而止。技术是工具工具本身不负责判断对错使用工具的人心里得有一杆秤。这个项目整体做下来我个人最大的体会是爬虫项目里最有价值的部分真不是代码本身而是抓包分析的那十几分钟。你盯着开发者工具里的一条条请求试着把返回来的一大段JSON梳理出层次这种分析过程才是真正能迁移到其他网站的核心能力。酷狗热歌榜的接口早晚会改版代码早晚会有失效的一天但只要你会打开Network面板、会从XHR请求里锁定目标接口、会处理JSON字段换个网站一样能从头开始自己分析。最后再分享一个实用的小技巧如果某一天页面结构变了、代码跑不出来了记得先用开发者工具的搜索功能在网页源代码里搜一个当前页面上可见的歌曲名往往能顺着JS代码直接找到新接口的位置。
返回列表