资讯详情

资讯详情

音乐搜索接口爬虫实战:从抓包定位到requests实现

很多朋友学Python爬虫学完requests库的基础用法之后最常问的一个问题就是为什么我拿到的网页源码里找不到我要的数据尤其是音乐搜索这类功能页面一打开就是一个输入框敲进去歌名结果列表瞬间就出来了但HTML源码里翻遍也找不到歌名。这不是你的解析代码有问题而是数据根本不在HTML里——它是通过JavaScript异步请求后端接口拿到的。今天这个案例就是围绕某音乐平台搜索接口展开的核心目标是带着大家完整走一遍如何从页面中定位真实的搜索接口、分析它的请求参数和返回结构最后用纯requests实现一个无加密音乐搜索的小爬虫。整个过程不需要逆向加密算法也不涉及复杂的签名逻辑非常适合作为每天一个爬虫练习案例的入门和进阶过渡。这个案例适合三类人一类是刚学会requests和JSON解析、想找个完整实战练手的新手一类是已经能写简单爬虫、但看到动态加载页面就不知道从哪下手的朋友还有一类是想理解接口分析这套通用方法论的人——因为这套思路可以平移到新闻搜索、商品搜索、视频搜索等一大堆场景。只要你会打开浏览器的开发者工具会看一点点网络请求面板剩下的就跟着这篇文章一步步来。1. 搜索接口案例的价值从页面源码到真实数据源1.1 为什么直接抓HTML行不通很多人在写爬虫的时候有个惯性思维拿到一个页面先用requests把HTML抓下来然后用正则或者解析库去匹配内容。这在纯服务端渲染的页面上确实成立但现在稍微像样一点的网站尤其是搜索类、音乐类、视频类平台几乎全是前后端分离的架构。前后端分离是什么意思简单说页面本身只是一个空壳子里面主要的HTML结构都是固定的框架——搜索框、按钮、列表容器都是写死的只有当你输入关键词、点击搜索按钮之后页面里的JavaScript才会向后端服务器发出一个异步请求通常叫XHR请求拿到JSON数据之后再由JavaScript动态地把歌名、歌手、封面图渲染到页面上。所以你直接抓HTML抓到的只是那个空壳里面当然什么都没有。这个案例选的某音乐平台搜索接口恰好就是这种典型结构。它的有意思之处在于接口无加密、无签名、无token参数构造非常直观非常适合用来说明通过浏览器开发者工具逆向分析接口的完整流程。把这一个案例吃透你就掌握了接口型爬虫这类问题的通用解法。1.2 这个案例的核心方法论先定位再分析后构造我平时带新人写爬虫一直强调一个顺序不要一上来就写代码先花十分钟把接口搞清楚再花五分钟写代码。代码永远是最简单的部分真正的工作量在于分析和理解。我们这次要做的就是先打开浏览器手动操作一遍搜索过程同时盯着网络面板看请求是怎么发出去的然后从这些请求里找到跟搜索有关的那个接口看它的URL、请求方式、参数、返回结果最后再用Python把同样的请求模拟一遍拿到数据后解析JSON。整个流程可以概括为六个字定位、分析、构造。下文就把每一个步骤拆开了讲尤其是几个容易卡住的地方比如请求参数里哪些是固定的、哪些是变化的、返回的JSON结构长什么样、怎么从里面把钱儿歌名和歌手给抠出来。2. 接口抓包定位开发者工具里五分钟找到真实请求2.1 打开网络面板后先做什么定位接口这件事不需要任何额外工具就用浏览器自带的开发者工具。以Chrome为例按F12打开开发者工具切到Network网络标签页然后把Preserve log保留日志勾选上这个细节很重要——很多页面在你输入关键词回车之后页面可能发生跳转或者重新加载如果不勾选保留日志前面的请求就会被清空你就看不到核心的那条搜索请求了。准备工作做好之后在搜索框里输入一个比较有辨识度的关键词比如晴天或者海阔天空点击搜索按钮。注意关键词一定要选那种比较特殊的方便待会儿在请求列表里快速找到它。因为Network面板里会有大量请求图片、CSS、JavaScript文件、各种统计接口如果没有辨识度你根本分不清哪条是搜索请求。一个非常实用的技巧是在Network面板的筛选框里直接输入你刚才搜索的关键词比如输入晴天。如果这条关键词出现在某个请求的URL参数里就会被过滤出来。一般来说音乐平台会把这些请求发到一个带search、sug、query之类名字的接口地址上。这个案例里的搜索请求名称就比较直观是一个相对简短的后端API路径一眼就能看出是搜索功能。2.2 重点观察Request Headers和Query String Parameters选中那条搜索请求之后右侧会展开详细面板这时候我们要看两个东西第一个是Request Headers请求头主要是看User-Agent、Referer、Origin这几个字段。很多平台的接口会校验请求头如果Referer不对或者User-Agent是默认的Python-requests可能直接被拒绝。所以待会儿写代码的时候Header要尽量完整地带上这是接口型爬虫最容易忽略的一个坑。第二个是Query String ParametersURL查询参数也就是URL问号后面跟的那一串键值对。这个案例的搜索接口是GET请求所有参数都光明正大地放在URL里。一般会有几个核心参数搜索关键词参数通常叫w、keyword、query之类分页参数常见的有page、offset、start每页数量参数常见的有pagesize、limit、n、num其他固定参数比如平台类型、请求来源、时间戳等。你会发现除了关键词和分页参数是变化的其余很多参数不管搜什么都是固定的。我们拿到的这个接口无加密意味着这些固定参数可以直接照抄不需要去模拟生成或者做签名。这在很多内部接口或低并发接口场景里很常见正好适合做入门案例。2.3 从响应JSON里确认这是不是我们要找的接口光看请求还不够最后一步是确认返回内容确实是我们想要的。在Network面板里切换到响应Response标签页或者预览Preview标签页就能看到这个接口返回的JSON数据。如果里面清清楚楚地包含了歌名、歌手、专辑信息那基本就可以锁定了。这个案例里接口返回的结构大致是这样一个层级外层有一个状态码字段比如code值为0或者200就代表请求成功下面有一个data字段里面包含一个列表通常叫list、items或songs列表每一项就是一个歌曲对象包含歌曲ID、歌曲名、歌手、专辑、时长等字段。我第一次分析这类接口的时候总是习惯性地把返回数据打印到控制台然后一层一层看字段名。这一步非常推荐大家自己操作一遍因为只有你真的看到了JSON的实际结构后面写解析代码时心里才有底不用瞎猜字段名。3. 接口的请求参数与返回结构拆解无加密不等于没规矩3.1 参数变化规律与固定规则的分工拿到接口之后不要急着写代码先花两分钟把参数捋一遍。我习惯在草稿纸上把参数按照固定和变化分成两列参数类型参数示例作用关键词语参数w晴天每次搜索必变传递用户想查的内容分页参数p1 / offset0控制返回第几页每页数量n10 / pagesize20控制一页返回多少条固定标识参数platformweb / sourceout标识请求来自哪里通常写死时间戳等杂项_1690000000000防缓存用可写死也可动态生成这样做的好处是写代码的时候思路非常清晰变化的参数就放到params字典里动态赋值固定的参数就原样抄进去。这个案例的接口还有一个很友好的特点它支持中文关键词直接放在URL参数里不需要手动做URL编码requests库会自动帮我们处理编码问题。这一点看起来不起眼但有些老平台如果参数编码处理不当中文关键词会抓回来一堆乱码处理起来很麻烦。3.2 返回JSON的字段层级从最外层扒到最里层JSON解析这件事很多人容易犯一个错误拿到数据之后不做结构分析上来就写result[data][data][list]一旦层级不对就报KeyError然后又回头瞎试。正确做法是先弄清楚整体结构。我们把这个案例返回的JSON做一次拆解为了便于理解我给结构加个简单注释{ code: 0, data: { total: 128, list: [ { songId: 123456, songName: 晴天, singer: 某某歌手, album: 某某专辑, duration: 267 } ] } }从结构上看解析逻辑应该是先判断最外层的状态码字段是否表示成功再取data字段拿到列表数据遍历列表从每一项中提取歌曲名、歌手、专辑等字段。这里我特别想提醒一点不同音乐平台的字段命名习惯差异很大。有的是songName有的是name有的是title歌手有的叫singer有的叫artist有的甚至是嵌套对象。所以在拿到任何接口之后一定要先实际打印一次返回的JSON亲眼确认字段名而不是凭经验猜。经验能帮你猜中七成但剩下三成都是靠打印调试验证出来的。3.3 无加密的判断标准与为什么优先选这类接口练手很多人在群里看到别人讨论接口加密、签名逆向就觉得高深、羡慕其实没必要。爬虫的学习路径里最先应该掌握的恰恰是这种无加密接口。判断一个接口是否加密通常看几个点请求参数里有没有签名、token、auth这类字段返回数据是否是一段经过编码的密文请求头里是否要求携带动态生成的哈希字符串。这个案例的接口以上几点全都不沾边参数就是明晃晃的键值对返回也是规规矩矩的JSON。这种接口在平台的内测阶段、第三方合作渠道、或者一些前端老架构项目中非常常见。利用这类接口练手你可以把全部精力放在接口定位、参数理解、数据解析这些核心能力上而不是被加密算法劝退。我个人的建议是新手练爬虫尽量从这类接口入口有加密的接口放到后面再碰。先把无加密接口玩到滚瓜烂熟再去看加密原理思维就会顺畅很多。4. 完整代码实现从单次搜索到批量分页抓取4.1 基础搜索函数的封装搞清楚接口规则之后写代码就非常快了。我们先用requests库实现一个最基础的搜索函数。代码不长但有几个细节我会逐一解释。import requests import json # 搜索接口地址案例演示用实际使用时替换为目标地址 SEARCH_URL https://example-music.com/api/search # 请求头重点是User-Agent和Referer模拟正常浏览器行为 HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://example-music.com/, Origin: https://example-music.com } def search_song(keyword, page1, page_size10): params { w: keyword, # 搜索关键词 p: page, # 页码从1开始 n: page_size, # 每页数量 platform: web # 固定参数平台标识 } resp requests.get(SEARCH_URL, paramsparams, headersHEADERS, timeout10) # 如果返回的不是JSON说明被拦了或者接口地址有问题 resp.raise_for_status() data resp.json() if data.get(code) ! 0: print(f接口返回异常: {data.get(code)}) return [] return data.get(data, {}).get(list, []) if __name__ __main__: songs search_song(晴天) for song in songs: print(song[songId], song[songName], song[singer])这里面有三个细节值得展开说说。第一个是params参数的使用。requests库允许通过params传一个字典它会自动帮我们把字典里的键值对拼接到URL后面并且自动处理中文和特殊字符的URL编码。你不需要自己手动去做urlencode这一点能帮你减少很多编码相关的bug。第二个是timeout10。我给每个请求都设置了超时时间这是爬虫的自我保护机制。如果没有超时设置遇到接口响应慢或者网络抖动的情况requests可能会一直等下去程序就卡死了。设置超时之后超过10秒没响应就会抛出异常你可以在外面再加一层try-except做容错处理。第三个是data.get(code)的判断。很多接口即使请求成功也会在业务层面返回一个错误码比如参数错误、关键词非法、接口限流等。这时候HTTP状态码可能还是200但业务code不是0我们就要提前感知到这种异常否则会拿不到任何歌曲数据还以为是解析代码写错了。4.2 分页抓取与简单的去重处理单页搜索跑通之后下一步就是翻页。翻页的逻辑其实很简单在for循环里循环调用search_song函数每次把页码加一直到拿不到新数据或者达到预设的最大页码。但真实场景下你会遇到一个问题不同页面之间可能出现重复数据。所以我在批量抓取的时候惯例是维护一个全局的歌曲ID集合如果某个歌曲ID已经出现过就跳过。def batch_search(keyword, max_pages5, page_size10): seen_ids set() all_songs [] for page in range(1, max_pages 1): songs search_song(keyword, pagepage, page_sizepage_size) if not songs: print(f第{page}页没有数据停止翻页) break new_count 0 for song in songs: song_id song.get(songId) if song_id in seen_ids: continue seen_ids.add(song_id) all_songs.append(song) new_count 1 print(f第{page}页抓取完成新增{new_count}条累计{len(all_songs)}条) return all_songs这里用集合seen_ids来做去重复杂度是O(1)即使数据量上万也不会卡。要注意的是歌曲ID不一定叫songId有的平台叫rid、music_id或者直接用URL里的某个参数作为唯一标识。还是要以你实际抓到的JSON字段为准。跑完这个批处理函数你就拥有了一个能翻页、能去重、能自动停的搜索采集模块。把结果转成DataFrame或者直接存成CSV、JSON文件就是一套完整的迷你爬虫项目了。4.3 把结果保存到本地文件搜索采集之后保存方式我推荐两种。一种是不需要第三方依赖的JSON文件一种是方便后续分析用的CSV文件。顺手也把保存逻辑写出来def save_to_json(songs, filename): with open(filename, w, encodingutf-8) as f: json.dump(songs, f, ensure_asciiFalse, indent2) def save_to_csv(songs, filename): import csv if not songs: return fieldnames list(songs[0].keys()) with open(filename, w, encodingutf-8-sig, newline) as f: writer csv.DictWriter(f, fieldnamesfieldnames) writer.writeheader() writer.writerows(songs)这里有两个基础但非常关键的细节一是保存JSON时一定要加ensure_asciiFalse否则中文歌名会变成一串\uXXXX的Unicode转义字符虽然能还原但人直接看根本没法读。二是保存CSV时用utf-8-sig编码而不是utf-8。utf-8-sig会在文件开头写入一个BOM标记这样Excel直接打开CSV文件时中文不会乱码。这个坑我最早踩的时候完全没意识到后来发现同事用Excel打开CSV全是乱码才查出来是编码方式的问题。5. 稳定性优化请求头、频率控制与编码异常排查5.1 请求头完整性对接口成功率的影响很多接口对请求头非常敏感。你可能会遇到一种情况用浏览器手动搜索一切正常但用Python代码一请求就返回403或者空数据。这时候不要怀疑接口地址错了大概率是请求头的问题。这个案例的接口对User-Agent和Referer都有校验。User-Agent是告诉服务器我是什么浏览器如果留空或者使用requests默认的python-requests/2.x.x服务器很容易识别出这不是正常浏览器请求。Referer则告诉服务器我是从哪个页面跳转过来的音乐平台通常只允许自己的搜索页域名作为Referer来源。所以我的建议是尽量完整地复制浏览器中那条搜索请求的请求头至少包含User-Agent、Referer、Origin这三项。有些平台还会校验Accept、Accept-Language等字段如果遇到接口不稳定就逐步把浏览器的请求头字段补齐通常能解决八九成的问题。5.2 单线程爬虫也要做好频率控制无加密接口虽然好写但一般平台都会有频率限制。如果短时间内疯狂请求轻则返回验证码页面重则封IP。所以哪怕我们只是练习案例也要养成控制请求频率的习惯。最简单的做法是在循环里加随机延迟。为什么要随机因为固定间隔比如sleep(1)虽然也能控制频率但机器人的行为特征非常明显一看就是脚本在定时运行。随机延迟让请求时间更接近真实用户的节奏。import time import random def batch_search_with_delay(keyword, max_pages5): all_songs [] for page in range(1, max_pages 1): songs search_song(keyword, page) all_songs.extend(songs) # 随机延迟1到3秒模拟真人操作节奏 time.sleep(random.uniform(1, 3)) return all_songs除了请求频率我还会再加上一个重试机制。比如网络偶尔抖动某个请求失败了先不要立刻崩溃等两秒重试一次。重试两三次还是失败再放弃这一页继续下一页。这种容错逻辑在正式爬虫里几乎是标配因为越大的数据量越不能因为单次失败就全盘终止。5.3 编码与JSON解析的边界情况接口返回的JSON正常情况下用resp.json()就能直接解析但有几个边界情况是新手最容易懵的第一种情况是接口返回的不是标准JSON而是一段带回调函数的JSONP格式数据。JSONP在外观上长这样callback({...})。如果遇到这种格式直接resp.json()会报错。处理方法是用正则先提取括号内的内容再用json.loads解析。这个案例的接口没有用JSONP但你在其他平台很可能遇到。第二种情况是返回内容里包含非法字符比如某些歌曲名里带有不可见的控制字符可能导致JSON解析失败。一个稳妥的思路是在解析之前先对文本做一次编码清洗。不过这个案例用不到我就不把代码写复杂了。第三种情况是歌曲名或歌手名在JSON里是合法的但打印到控制台时显示乱码。这通常是终端编码问题不是数据本身的问题。Windows的cmd和部分终端对UTF-8支持不好建议在脚本开头加上下面两行代码把标准输出强制改成UTF-8import sys import io sys.stdout io.TextIOWrapper(sys.stdout.buffer, encodingutf-8)这两行是Windows环境下的老演员了虽然治标不治本但能让调试过程少很多痛苦。6. 版权、使用边界与搜索接口的后续扩展方向6.1 搜索接口爬虫的合规使用边界写到这里我必须把版权和合规的事情说清楚。音乐搜索接口背后对应的歌曲、歌词、专辑信息大多是有版权的数字内容。利用接口写爬虫用于个人学习技术理解HTTP请求和JSON解析这是完全没问题的。但如果用来批量抓取数据、搭建自己的歌曲库、二次分发、甚至商用那就涉及侵权和违反平台规则了。我的经验是做技术练习的时候尽量做到三件事第一抓取频率控制得低一些不要给目标服务器造成压力第二抓下来的数据自己分析用不要去传播第三批量抓取之前先判断一下有没有可能影响平台正常服务如果接口明显有登录鉴权或者风控提示就说明平台不允许这么干果断停手。爬虫这个技术方向学的是网络协议、数据解析、异常处理这些通用能力不代表一定要拿真实平台的海量数据来练手。用小体量、低频率的方式把流程跑通技术上的收获一点都不打折。6.2 从这个案例延伸出去的接口爬虫能力点这个某音乐平台搜索接口的案例虽然简单但它牵出来的能力点完全可以平移到其他领域我把它们梳理成了一张能力清单能力点本案例的体现平移到其他场景网络面板定位接口F12找搜索请求新闻搜索、商品搜索、视频搜索参数分析区分固定参数和变化参数所有带分页和关键词的GET接口JSON层级解析逐层取歌曲列表和字段各种数据型接口的通用解析方式请求头模拟复制浏览器UA和Referer几乎所有接口型爬虫分页与去重页码循环加集合去重批量采集通用逻辑频率控制随机延迟和重试防封禁通用手段从这个角度来说这个案例并不是一个孤立的某网站教程它实际上是一套接口爬虫的通用方法论。你把这个方法论练熟了以后碰到任何无加密的接口都能在十分钟内完成从分析到出数据的小流程。我个人在带新人时会让他们把这个案例重新做三遍第一遍照着代码敲目标是跑通第二遍关掉代码自己写目标是理解第三遍换一个不同类型的平台比如换一个视频搜索接口或者新闻搜索接口目标是验证方法论。三遍下来接口型爬虫的基本功就非常扎实了。再分享一个我实际踩过的坑很多人在保存搜索数据之后喜欢把所有歌曲都塞进一个Python列表里内存占用巨大导致程序越来越慢。正确的做法是边抓边写每抓完一页就把数据写入文件同时清空列表。这个习惯对大规模采集非常重要希望你现在就从这个小项目开始养成。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →