爬虫实战-批量爬取QQ音乐歌曲

前言

本文仅供学习交流使用,请勿不当使用。

寻找歌单

首先下载音乐肯定得先找要下载的目标音乐啊,兄弟我自认为自己还算是个乐观的人,所以平常比较少听“网抑云”啦,于是就自然的打开了QQ音乐的官网,如下:
爬虫实战-批量爬取QQ音乐歌曲_第1张图片

大致看了看,然后我选择了分类歌单,因为里面的歌单稍微多一点:

爬虫实战-批量爬取QQ音乐歌曲_第2张图片

来到如下页面,首先找一个歌单进去,这里就选择第二个吧,看起来播放量高一点:
爬虫实战-批量爬取QQ音乐歌曲_第3张图片

随便点开一首歌,我点开了你,好不好?,可恶,竟然强迫我登录,好吧好吧,登录就登录,登录完毕后点击歌曲如下:

爬虫实战-批量爬取QQ音乐歌曲_第4张图片

首先大眼一看,浏览器里面的网址肯定不是歌曲的下载url啊,我们想下载歌曲肯定得找到歌曲的下载url,怎么找?按F12,如下(没有数据记得刷新下页面):
爬虫实战-批量爬取QQ音乐歌曲_第5张图片

选择菜单栏中的Media
爬虫实战-批量爬取QQ音乐歌曲_第6张图片

OK,一看最后一个竟然是3.3MB,不用想,数据肯定在这个里面,点开:

爬虫实战-批量爬取QQ音乐歌曲_第7张图片

看如图蓝色横条中明明白白的写着:Content-Type: audio/mp4,那这个就是下载url了,复制Request URL粘贴到浏览器中,如下:

爬虫实战-批量爬取QQ音乐歌曲_第8张图片

得,就是他,那么requests.get()一下这首歌就下载下来了,OK完事收工。

这时候应该可能有读者不满意了:“你TM在逗我?这么下载一首歌谁不会,老哥要一键下载几百首才行!”

不要慌不要慌嘛,这就来告诉大家怎么把首页那些歌单里面的歌全部下载下来!(VIP的除外,你懂的。。。)

既然想实现批量下载,首先肯定是来观察歌曲的下载url有什么特征啊,来看看这首歌的Request URL

https://isure.stream.qqmusic.qq.com/C400003Frk3c0M7j5N.m4a?guid=5091581860&vkey=E306AEA443CA993C6007CC8E6E8E46AE83C4A665EF3843FB90480C0203B98A08448DCD6DDA534F2E25C821E86C955456597666DE344E0C90&uin=4166&fromtag=66

再来一首用来对比:

https://ws.stream.qqmusic.qq.com/C400001BxqJH2rf6uG.m4a?guid=5091581860&vkey=746525FC77CAB20350FC334CABA1BD3C0C2C50FC0B821A848403D0FFED2DF7E06390CE0CA93AFAAB97ECB18B46694624E2AEE671375A515C&uin=4166&fromtag=66

对比一看我们发现,在C之前的域名是相同的,guid是相同的,uin是相同的,fromtag是相同的,不同的就只有C到.m4a之间vkey不一样了,那只要搞到这俩参数,下载歌就没啥问题了。那我们找找它们在哪,找啊找啊找朋友,找啊找啊找朋友,哎呀,突然找到一个巨好朋友
爬虫实战-批量爬取QQ音乐歌曲_第9张图片

如图,蓝色条框是purl参数,啊啊啊啊,这个参数直接就是域名之后的所有东西啊,包含了那两个不一样的vkey和C到.m4a之间的字符串了。得,那我们要是能找到这个请求的特征,发送这个请求就能得到purl了。找到purl就能下载音乐了。

来看看这个请求:

https://u.y.qq.com/cgi-bin/musics.fcg?-=getplaysongvkey11865704078518058&g_tk=652661168&sign=zzanqm3iok901lfce97ee26f87ec8b50b4ffa60462bdef&loginUin=2428022854&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq.json&needNewCode=0&data=%7B%22req%22%3A%7B%22module%22%3A%22CDN.SrfCdnDispatchServer%22%2C%22method%22%3A%22GetCdnDispatch%22%2C%22param%22%3A%7B%22guid%22%3A%225091581860%22%2C%22calltype%22%3A0%2C%22userip%22%3A%22%22%7D%7D%2C%22req_0%22%3A%7B%22module%22%3A%22vkey.GetVkeyServer%22%2C%22method%22%3A%22CgiGetVkey%22%2C%22param%22%3A%7B%22guid%22%3A%225091581860%22%2C%22songmid%22%3A%5B%22002xdDqK3GkHVO%22%5D%2C%22songtype%22%3A%5B0%5D%2C%22uin%22%3A%222428022854%22%2C%22loginflag%22%3A1%2C%22platform%22%3A%2220%22%7D%7D%2C%22comm%22%3A%7B%22uin%22%3A2428022854%2C%22format%22%3A%22json%22%2C%22ct%22%3A24%2C%22cv%22%3A0%7D%7D

这…相信一些同学看到这些直接会骂街吧哈哈哈。后面那一串带百分号的参数是什么鬼!

不要紧,来看这里:
爬虫实战-批量爬取QQ音乐歌曲_第10张图片

注意看右下角Query String Parameters,这里其实就是请求后面跟的参数了,第一个带:(冒号),我们不用管,然后你再打开另一首歌的这个参数界面,对比对比,我们发现只有sign标志和最后的data里面的songmid是不一样的,OK,再来找signsongmid,找到之后就能知道purl就能下载歌曲了!

简单分析一下,这个songmid顾名思义就是歌曲的标识符,我们肯定不能在这个播放页找了,首先我们得找到一群songmid才能下载一批歌曲啊,那哪里可能有一群songmid?动动脑子想想,当然是歌单页!歌单页里面才有不止一首歌,它很有可能包含所有歌曲的songmid!,OK,来到这儿:

爬虫实战-批量爬取QQ音乐歌曲_第11张图片

…,又是一个惊喜,简单对比下就可以发现,图中的mid就是之前要找的songmid,关键还是**这里面有119首歌的信息!!**而实际上的网页里面只有10首歌,它是这么提示的:查看更多内容,请下载客户端。不得不说,“它们”真聪明啊!

然后就是找那个sign了,找啊找啊找啊找,找啊找啊找啊找,最后,我找到脑子爆炸了,然而并没有找到。气急败坏的我就没附带那个参数给浏览器发了个请求,…没想到仍然返回了我们需要的数据。。。又是个惊喜。果然今天1024好运不断吗?(同志们你们看到的这一段话实际中我花了好长好长时间。。。)

OK,到这里我们有了歌单的url,这个直接复制就可,就能下载这119首歌了,才119首,说不定还有要VIP的,也没几首嘛,这怎么能满足!,走,想办法能把首页所有歌单的url特征找到就好了,我们好像开启了套娃之旅。

来看看这个歌单url的请求参数:

爬虫实战-批量爬取QQ音乐歌曲_第12张图片

然后再换一个歌单对比对比:

爬虫实战-批量爬取QQ音乐歌曲_第13张图片

于是我们又发现了!

只有一个dissid参数不一样,看来这个标签就是各个歌单的标识符了!,那哪里包含很多歌单的dissid呢?当然是首页!只有首页才显示了各个歌单的信息!该去首页找好朋友了,有了前面的竟然,继续套娃套下去我们也不怕了,这次很容易就找到了包含各个歌单dissid的地方了:
爬虫实战-批量爬取QQ音乐歌曲_第14张图片

看!总共19个歌单,每一个歌单的dissid都工工整整的在那里,好像一早就知道我们要来在等我们似的。

OK,19个歌单,假如每个几十首歌曲,也起码几百首了吧,够听一阵子了,就不再套娃了,当然同志们有想法的可以去排行榜电台之类的都看看。。。

到这里就完工了,捋一捋,首先我们可以通过首页的url,来得到各个歌单的dissid,再通过各个歌单的dissid得到每个歌单下的songmid,再通过songmid来得到各个歌曲的purl,然后再简单经过url拼接就能下载歌曲了!

话不多说,直接上代码:

# @Author :   goldsunC
# @Date   :   2020/10/24/
# @Email  :   [email protected]
# @Blog   :   https://blog.csdn.net/weixin_45634606
import requests
import json
import os.path
def parse_dissid():
    """
    得到歌单的dissid
    """
    url = 'https://c.y.qq.com/splcloud/fcgi-bin/fcg_get_diss_by_tag.fcg?'
    data = {
     
        'picmid': '1',
        'rnd': '',
        'g_tk_new_20200303':'398486200',
        'g_tk': '398486200',
        'loginUin': '你的QQ账号',
        'hostUin': '0',
        'format': 'json',
        'inCharset': 'utf8',
        'outCharset': 'utf-8',
        'notice': '0',
        'platform': 'yqq.json',
        'needNewCode': '0',
        'categoryId': '10000000',
        'sortId': '5',
        'sin': '0',
        'ein': '19'
    }
    headers = {
     
        'accept':'application/json,text/javascript,*/*;q=0.01',
        'accept-encoding':'gzip,deflate,br',
        'accept-language': "zh-CN,zh;q=0.9",
        'referer': 'https://y.qq.com/portal/playlist.html',
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36',
        'cookie':'你的cookie'
    }
    try:
        response = requests.get(url=url , params=data , headers = headers)
        dissid_list = []
        diss_json = json.loads(response.text).get('data').get('list')
        for diss_id in diss_json:
            id = diss_id.get('dissid')
            dissid_list.append(id)
        return dissid_list
    except requests.RequestException:
        print('请求得到dissid有问题')

def parse_mid(dissid):
    url = 'https://c.y.qq.com/qzone/fcg-bin/fcg_ucc_getcdinfo_byids_cp.fcg?'
    data = {
     
        'type':'1',
        'json':'1',
        'utf8':'1',
        'onlysong':'0',
        'new_format':'1',
        'disstid':dissid,
        'g_tk_new_20200303':'398486200',
        'g_tk':'398486200',
        'loginUin':'你的QQ账号',
        'hostUin':'0',
        'format':'json',
        'inCharset':'utf8',
        'outCharset':'utf-8',
        'notice':'0',
        'platform':'yqq.json',
        'needNewCode':'0'
    }
    headers = {
     
        'accept': 'application/json,text/javascript,*/*;q=0.01',
        'accept-encoding': 'gzip,deflate,br',
        'accept-language': "zh-CN,zh;q=0.9",
        'cookie':'你的cookie',
        'origin':'https://y.qq.com',
        'referer': 'https://y.qq.com/',
        'sec-fetch-dest':'empty',
        'sec-fetch-mode':'cors',
        'sec-fetch-site':'same-site',
        'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36'
    }
    try:
        response = requests.get(url=url , params=data , headers = headers)
        mid_list = []
        name_list = []
        mid_json = json.loads(response.text).get('cdlist')[0].get('songlist')
        for media_mid in mid_json:
            mid = media_mid.get('mid')
            mid_list.append(mid)
            name = media_mid.get('name')
            name_list.append(name)
        return zip(mid_list,name_list)
    except requests.RequestException:
        print('请求得到media_mid出问题')

def parse_purl(mid):

    url = 'https://u.y.qq.com/cgi-bin/musicu.fcg?'
    data = {
     
        'g_tk': '5381',
        'loginUin': '0',
        'hostUin': '0',
        'format': 'json',
        'inCharset': 'utf8',
        'outCharset': 'utf - 8',
        'notice': '0',
        'platform': 'yqq.json',
        'needNewCode': '0',
        'data': '{"req_0":{"module":"vkey.GetVkeyServer","method":"CgiGetVkey","param":{"guid":"8874756349","songmid":["' +
                mid + '"],"songtype":[0],"uin":"0","loginflag":1,"platform":"20"}},"comm":{"uin":0,"format":"json","ct":24,"cv":0}}',
    }
    headers= {
     

        'Referer': 'https://y.qq.com/portal/player.html',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
    }
    try:
        response = requests.get(url,params=data,headers=headers)
        purl_json = json.loads(response.text).get('req_0').get('data').get('midurlinfo')[0].get('purl')
        return purl_json
    except requests.RequestException:
        print('请求得到purl出错')
def download(purl,name):
    base = 'https://ws.stream.qqmusic.qq.com/'
    url = base+purl
    headers= {
     
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
    }
    response = requests.get(url,headers = headers)
    with open('./QQQQ/{}.mp3'.format(name),'wb') as f:
        f.write(response.content)
if __name__ == '__main__':
    dissid_list = parse_dissid()
    mid_name_list = []
    # 将所有歌曲全部下载,因为数量多且程序未优化,会很慢,不过也可以尝试
    # for dissid in dissid_list:
    #     mid_name = parse_mid(dissid)
    #     mid_name_list.append(mid_name)

    #如下是只下载第一个歌单里面的歌曲
    mid_name = parse_mid(dissid_list[0])
    mid_name_list.append(mid_name)

    purl_names_list = []
    for mid in mid_name_list:
        for i in mid:
            purl = parse_purl(i[0])
            purl_names = [purl,i[1]]
            purl_names_list.append(purl_names)
            print(list(purl_names))
    if not os.path.exists('QQQQ'):
        os.mkdir('QQQQ')
    for purl_name in purl_names_list:
        purl_and_name = list(purl_name)
        download(purl_and_name[0],purl_and_name[1])

需要注意的是,程序中的cookie和账号需要改成你自己的,另外因为程序比较简单,没有使用多线程、Scrapy等,下载会比较慢,之后找时间把程序重构优化一下。
如果文章对你有帮助,还请点个赞哦~
不说了,我听歌曲了

你可能感兴趣的:(爬虫,python,爬虫,爬虫实战)