酷我音乐网络爬虫(如何使用python爬取酷我在线音乐)
酷我音乐网络爬虫(如何使用python爬取酷我在线音乐)
2024-06-08 05:55:56  作者:命样顾惜著  网址:https://m.xinb2b.cn/life/oox106687.html
前言

写这篇博客的初衷是加深自己对网络请求发送和响应的理解,仅供学习使用,请勿用于非法用途!文明爬虫,从我做起。下面进入正题。

获取歌曲信息列表

在酷我的搜索框中输入关键词 aiko,回车之后可以看到所有和 aiko 相关的歌曲。打开开发者模式,在网络面板下按下 ctrl f,搜索 二人,可以找到响应结果中包含 二人 的请求,这个请求就是用来获取歌曲信息列表的。

酷我音乐网络爬虫(如何使用python爬取酷我在线音乐)(1)

请求参数分析

请求的具体格式如下图所示,可以看到请求路径为 http://www.kuwo.cn/api/www/search/searchMusicBykeyWord,请求参数包括:

key: 搜索关键词,此处为 aikopn: 页码,page number 的缩写,此处为 1rn: 每页条目数,应该是 row number 的缩写,默认为 30httpsStatus:https 的状态?感觉没啥大用,看了源代码里面是直接写死 t.url = t.url "?reqId=".concat(n, "&httpsStatus=1")reqId:请求标识,刷新页面之后值会发生改变,不知道有啥用,待会儿模拟请求的时候试着不带上他会怎么样

酷我音乐网络爬虫(如何使用python爬取酷我在线音乐)(2)

打开 Apifox(当然 postman 也行),新建一个接口,把请求路径和参数设置为下图所示的样子,为了让响应结果简短点,这里把每页的条目数设置为 1 而非默认的 30:

酷我音乐网络爬虫(如何使用python爬取酷我在线音乐)(3)

在没有设置额外请求头的情况下发个请求试试,发现 403 Forbidden 了,emmmmm,应该是防盗链所致:

酷我音乐网络爬虫(如何使用python爬取酷我在线音乐)(4)

可以看到浏览器发出的请求的请求头中有设置 Referer 字段,把它加上,应该不会再报错了吧:

酷我音乐网络爬虫(如何使用python爬取酷我在线音乐)(5)

这次状态码为 200,但是没有收到任何数据,success 为 false 说明请求失败了,message 指明了失败原因是缺少 csrf token。问题不大,接着把浏览器发出的请求中的 csrf 加到 Apifox 请求头中,再发请求,还是报错 CSRF token Invalid!。算了,还是老老实实把 Cookie 也加上吧,但也不是全部加上,只加 kw_token=CCISYM2HV96 部分,因为 Cookie 里面只有这个字段和 token 有关系且它的值和 csrf 相同。

在源代码面板按下 ctrl shift f,搜索一下 csrf,可以看到 csrf 本来就是来自 Object(h.b)("kw_token"),这个函数用来取出 document.cookie 中的 kw_token 字段值。至于 Cookie 中的 kw_token 怎么计算得到的,那就是服务器的事情了,咱们只管 CV 操作即可。

酷我音乐网络爬虫(如何使用python爬取酷我在线音乐)(6)

准备好参数和请求头,重新发送请求,可以得到想要的数据。如果去掉 reqId 参数,也可以拿到数据,但是会有略微的不同,这里就不贴出来了:

复制{ "code": 200, "curTime": 1649482287185, "data": { "total": "741", "list": [ { "musicrid": "MUSIC_11690555", "barrage": "0", "ad_type": "", "artist": "aiko", "mvpayinfo": {"play": 0,"vid": 8530326,"down": 0 }, "nationid": "0", "pic": "http://img4.kuwo.cn/star/starheads/500/24/88/4146545084.jpg", "isstar": 0, "rid": 11690555, "duration": 362, "score100": "42", "ad_subtype": "0", "content_type": "0", "track": 1, "hasLossless": true, "hasmv": 1, "releaseDate": "1970-01-01", "album": "", "albumid": 0, "pay": "16515324", "artistid": 1907, "albumpic": "http://img4.kuwo.cn/star/starheads/500/24/88/4146545084.jpg", "originalsongtype": 0, "songTimeMinutes": "06:02", "isListenFee": false, "pic120": "http://img4.kuwo.cn/star/starheads/120/24/88/4146545084.jpg", "name": "恋をしたのは", "online": 1, "payInfo": {"play": "1100","nplay": "00111","overseas_nplay": "11111","local_encrypt": "1","limitfree": 0,"refrain_start": 89150,"feeType": { "song": "1", "vip": "1"},"down": "1111","ndown": "11111","download": "1111","cannotDownload": 0,"overseas_ndown": "11111","refrain_end": 126247,"cannotOnlinePlay": 0 }, "tme_musician_adtype": "0" } ] }, "msg": "success", "profileId": "site", "reqId": "4b55cf4b0171253c33ce1d71b999c42f", "tId": ""}

请求代码

响应结果的 data 字段中有很多东西,这里只提取需要的部分。在提取之前先来定义一下歌曲信息实体类,这样在其他函数中要一首歌曲的信息时只要把实体类的实例传入即可。

复制# coding:utf-8from copy import deepcopyfrom dataclasses import dataclassclass Entity: """ Entity abstract class """ def __setitem__(self, key, value): self.__dict__[key] = value def __getitem__(self, key): return self.__dict__[key] def get(self, key, default=None): return self.__dict__.get(key, default) def copy(self): return deepcopy(self)@dataclassclass SongInfo(Entity): """ Song information """ file: str = None title: str = None singer: str = None album: str = None year: int = None genre: str = None duration: int = None track: int = None trackTotal: int = None disc: int = None discTotal: int = None createTime: int = None modifiedTime: int = None

上述代码显示定义了实体类的基类,并且重写了 __getitem__ 和 __setitem__ 魔法方法,这样我们可以像访问字典一样来访问实体类对象的属性。接着让歌曲信息实体类继承了实体类基类,并且使用 @dataclass 装饰器,这是 python 3.7 引入的新特性,使用它装饰之后的实体类无需实现构造函数、__str__等常用函数,python 会帮我们自动生成。

在发送请求的过程中可能会遇到各种异常,如果在代码里面写 try except 语句会显得很乱,这里同样可以用装饰器来解决这个问题。

复制# coding:utf-8from copy import deepcopydef ExceptionHandler(*default): """ decorator for exception handling Parameters ---------- *default: the default value returned when an exception occurs """ def outer(func): def inner(*args, **kwargs): try: return func(*args, **kwargs) except BaseException as e: print(e) value = deepcopy(default) if len(value) == 0:return None elif len(value) == 1:return value[0] else:return value return inner return outer

下面是发送获取歌曲信息请求的代码,使用 exception_handler 装饰了 getSongInfos 方法,这样发生异常时会打印异常信息并返回默认值:

复制# coding:utf-8import jsonfrom urllib import parsefrom typing import List, Tupleimport requestsclass KuWoMusicCrawler: """ Crawler of KuWo Music """ def __init__(self): super().__init__() self.headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36', 'Cookie': 'kw_token=C713RK6IJ8J', 'csrf': 'C713RK6IJ8J', 'Host': 'www.kuwo.cn', 'Referer': '' } @exceptionHandler([], 0) def getSongInfos(self, key_word: str, page_num=1, page_size=10) -> Tuple[List[SongInfo], int]: key_word = parse.quote(key_word) # configure request header headers = self.headers.copy() headers["Referer"] = 'http://www.kuwo.cn/search/list?key=' key_word # send request for song information url = f'http://www.kuwo.cn/api/www/search/searchMusicBykeyWord?key={key_word}&pn={page_num}&rn={page_size}&reqId=c06e0e50-fe7c-11eb-9998-47e7e13a7206' response = requests.get(url, headers=headers) response.raise_for_status() # parse the response data song_infos = [] data = json.loads(response.text)['data'] for info in data['list']: song_info = SongInfo() song_info['rid'] = info['rid'] song_info.title = info['name'] song_info.singer = info['artist'] song_info.album = info['album'] song_info.year = info['releaseDate'].split('-')[0] song_info.track = info['track'] song_info.trackTotal = info['track'] song_info.duration = info["duration"] song_info.genre = 'Pop' song_info['coverPath'] = info.get('albumpic', '') song_infos.append(song_info) return song_infos, int(data['total'])

获取歌曲下载链接免费歌曲

虽然我们实现了搜索歌曲的功能,但是没拿到每一首歌的播放地址,也就没办法把歌曲下载下来。我们先来播放一首不收费的歌曲试试。可以看到浏览器发送了一个获取播放链接的请求,路径为 http://www.kuwo.cn/api/v1/www/music/playUrl,有两个需要关注的参数:

mid:音乐 Id,此处的值为 941583,和页面 url 中的编号一致,由于我们是通过点击搜索结果页面中 二人 跳转过来的,而 二人 这条结果也是动态加载出来的,超链接中的 Id 肯定也来自于上一节中响应结果的某个字段。二人 是第四条记录,通过对比可以发现 data.list[3].rid 就是 mid;type:音乐类型?此处的值为 music,发送请求的时候也设置为 music 即可

酷我音乐网络爬虫(如何使用python爬取酷我在线音乐)(7)

在 Apifox 中新建一个获取歌曲播放地址的请求,如下所示,发现可以成功拿到播放地址:

酷我音乐网络爬虫(如何使用python爬取酷我在线音乐)(8)

付费歌曲

现在换一首歌,比如 aiko - 横颜,点击歌曲页面上的播放按钮时会弹出要求在客户端中付费收听的对话框。直接发送请求,响应结果会是下面这个样子,状态码为 403:

酷我音乐网络爬虫(如何使用python爬取酷我在线音乐)(9)

其实酷我在 2021 年 9 月份的时候换过获取播放地址的接口,那时候的请求接口为 http://www.kuwo.cn/url,支持以下几个参数:

format: 在线音乐的格式,可以是 mp3type: 和现在的接口中的 type 参数一样,但是值为 convert_url3rid: 音乐 Id,和 mid 一样br: 在线音乐的比特率,越大则音质越高,可选的有 128kmp3、 192kmp3 和 320kmp3

这个接口不管是付费音乐还是免费音乐都可以用。如果将现在这个接口的 type 参数的值换成 convert_url3,请求结果如下所示,说明成功了:

酷我音乐网络爬虫(如何使用python爬取酷我在线音乐)(10)

请求代码

下面是获取在线音乐播放链接的代码,只需调用 downloadSong 函数并把爬取到的歌曲传入就能完成歌曲的下载:

复制@exceptionHandler('')def getSongUrl(self, song_info: SongInfo) -> str: # configure request header headers = self.headers.copy() headers.pop('Referer') headers.pop('csrf') # send request for play url url = f"http://www.kuwo.cn/api/v1/www/music/playUrl?mid={song_info['rid']}&type=convert_url3" response = requests.get(url, headers=headers) response.raise_for_status() play_url = json.loads(response.text)['data']['url'] return play_url@exceptionHandler('')def downloadSong(self, song_info: SongInfo, save_dir: str) -> str: # get play url url = self.getSongUrl(song_info) if not url: return '' # send request for binary data of audio headers = self.headers.copy() headers.pop('Referer') headers.pop('csrf') headers.pop('Host') response = requests.get(url, headers=headers) response.raise_for_status() # save audio file song_path = os.path.join( save_dir, f"{song_info.singer} - {song_info.title}.mp3") with open(song_path, 'wb') as f: f.write(data) return song

后记

除了获取歌曲的详细信息和播放地址外,我们还能拿到歌词、歌手信息等,方法是类似的,在我的 Groove 中提供了在线歌曲的功能,一部分接口就是来自酷我,还有一些来自酷狗和网易云,爬虫的代码在 app/common/crawler 目录下,喜欢的话可以给个 star 哦,以上~~

文章作者: 之一Yo

文章链接: https://www.cnblogs.com/zhiyiYo/p/16122664.html

  • 冬天车各种异响(车内异响怎么破)
  • 2024-06-08车内异响怎么破嗨嗨喽,大家好,好久不见!之前的两期文章我们聊过车辆底盘橡胶件的异响问题,这期来说说车内各种滋滋,吱吱的烦人异响问题及简单的解决办法,希望帮助到大家!这期的更新间隔时间有些久,原本是想聊聊冷车打方向异。
  • 卡斯柏个人资料(卡斯柏的简介)
  • 2024-06-08卡斯柏的简介卡斯柏(Casper),原名储晓祥1991年3月22日出生于上海市,中国内地男歌手、演员2008年,获得好乐迪男人KTV比赛双冠军、百事燃情麦克风冠军;同年,使用艺名“中泽祥”成为元素组合“嘻乐门”的。
  • 曲阳县虎山风景(河北曲阳县虎山风景区)
  • 2024-06-08河北曲阳县虎山风景区中国小康网虎山风景区位于保定市西部,曲阳县最北部,因其山顶的一块巨石颇似蓄势待发的猛虎而得名与著名的古北岳恒山相连,主峰“三尖梁”海拔1100多米,由于与唐县、阜平接壤,形成了“一脚踩三县”的山岳旅游。
  • 考古学都学习什么(带你迅速入门考古类型学)
  • 2024-06-08带你迅速入门考古类型学大家好,欢迎来到“小揪说”在之前的节目中我们为大家非常浅显地介绍了考古学的两大基本理论之一的考古地层学,本期节目将为大家简单介绍一下另一大基本理论——类型学考古地层学和考古类型学并称为考古学的“车之两。
  • 小马哥头发怎么长出来的(小马已变成老马)
  • 2024-06-08小马已变成老马浮生一片草,岁月催人老转眼间,小马哥已经45岁了!岁月真如一把杀马刀,曾经意气风发的少年如今已变成略显沧桑的中年大叔再叫他“小马哥”都有些不太合适,叫“马叔”似乎更加贴切生日礼物12月21日是小马哥4。
  • 油头蓬松洗发水测评(20年资深大油头洗发水分享)
  • 2024-06-0820年资深大油头洗发水分享你是否有头痒头油的困扰?你是否头发特别油,发梢特别干?你是否脱发秃头,发际线要变成麦当劳?恭喜你看到了这一期丸子的分享,适合油头使用的洗发水,做为20年的资深油头(这里用资深是不是不太合适,哈哈哈),。
  • 知否电视剧女主是谁(电视剧知否将播)
  • 2024-06-08电视剧知否将播近日,由张开宙执导,曾璐、吴桐编剧,赵丽颖、冯绍峰、朱一龙、施诗、张佳宁等领衔主演,中国内地女演员杨智迪参演的古代社会家庭题材电视剧《知否知否应是绿肥红瘦》终于定档12月25日湖南卫视开播,在该剧中,。
  • 我的团长我的团后续(我的团长我的团之东北迷龙之死)
  • 2024-06-08我的团长我的团之东北迷龙之死看过《我的团长我的团》的团迷都知道,这部电视剧里,迷龙可以说是混的最好的,而且不管什么时候,都不缺吃的,而且也是炮灰团里面唯一的一个在战争年代还可以找到老婆的人迷龙,东北人,姓张,这个是他进入缅甸远征。
  • 腰生蛇是什么原因导致的(女子身上盘了条蛇)
  • 2024-06-08女子身上盘了条蛇来源:新闻夜航众所周知,自然分娩非常疼痛,要问还有比这更疼的吗?有,不仅有,而且那种疼痛,几乎让人痛不欲生克山县居民李女士那种疼法别提了,有地缝都能钻进去,疼起来真是要命啊哈尔滨市双城区居民靳女士像毛。
  • 东风猛士民用越野车评测(东风猛士品牌全新发布)
  • 2024-06-08东风猛士品牌全新发布品玩8月28日讯,8月27日晚,东风旗下全新新能源豪华越野品牌猛士正式发布湖北省委副书记、省长王忠林,湖北省副省长赵海山,武汉市委副书记、市长程用文出席活动,并与东风公司董事长、党委书记竺延风,东风公。
  • mkv怎么转换成mp4最快(如何把mkv转成mp4)
  • 2024-06-08如何把mkv转成mp4如何把mkv转成mp4?大家好,我是专门为大家答疑解惑的办公小助手小优我们经常收到后台来自各位小伙伴的吐槽,大概就是下载视频的时候,遇见了格式不通用的mkv视频,不知道怎么操作才能正常使用它要么有知道。