教你用Python爬取QQ音乐上的付费专辑

Hello,there!好久没写爬虫的博客啦,今天来写一下怎么爬取QQ音乐上的付费专辑(理论上所有专辑都可以)。想爬QQ音乐是因为实在没钱买那些专辑,太多想听的了!!

附上运行结果截图:

教你用Python爬取QQ音乐上的付费专辑_第1张图片

教你用Python爬取QQ音乐上的付费专辑_第2张图片

先说一下需要的环境

系统是:基于Linux的DeepinOS桌面版15.7。为什么用Linux?因为Terminal啊。

IDE是:PyCharm Community2018

语言&模块:Python3.5+requests+json+pymysql,为什么用数据库?因为数据库好用啊!最近数据库课设,写了好多SQL语句。爱上数据库了。你爬虫不用re模块或者是BeautifuiSoup?垃圾!我就不用,你打我啊。这又要说到课设了,以前爬虫总喜欢re.compile(),然后findall()。课设写服务器端用到json数据,然后发现Python的json模块也挺好用的。直接和字典互相转换,不要太爽。其实主要是这次爬虫都是处理的QQ音乐的json数据。

好了废话就说这么多,我们开始吧。

首先呢我们要爬的是QQ音乐(https://y.qq.com/)的专辑,顺便也把歌手的一些信息爬下来,然后再把专辑里的歌曲保存到自己的电脑上。


一.分析专辑页面

1.在浏览器中打开专辑页面,我用的是Chrome然后打开开发者模式

教你用Python爬取QQ音乐上的付费专辑_第3张图片

第二个箭头所指的文件里有这个专辑的信息直接双击点开:

教你用Python爬取QQ音乐上的付费专辑_第4张图片

这里面有这个专辑的很多信息:名称、流派、发行时间、公司.....等等,最重要的是歌曲的信息(songmid,songname)我们待会儿爬歌曲的时候要用到。先爬这个javascript文件,进行数据“清洗”,然后保存到数据库。数据库建表的代码如下

//歌手表
CREATE TABLE artists(
name VARCHAR(50),
id VARCHAR(20) NOT NULL PRIMARY KEY,
genre VARCHAR(10),
sex VARCHAR(10),
intro VARCHAR(10000),
area VARCHAR(10)
)engine innodb default charset =utf8;
//专辑表
CREATE TABLE albums(
name VARCHAR(50) NOT NULL,
id VARCHAR(20)NOT NULL PRIMARY KEY,
genre VARCHAR(10),
type VARCHAR(10),
company VARCHAR(20),
intro VARCHAR(1000),
area VARCHAR(10),
time VARCHAR(10)
)engine innodb default charset =utf8;
//歌曲表
CREATE TABLE songs(
name VARCHAR(200) NOT NULL,
id VARCHAR(20)NOT NULL PRIMARY KEY,
genre VARCHAR(10),
language VARCHAR(10),
time VARCHAR(10)
)engine innodb default charset =utf8;
//歌曲专辑关系表
CREATE TABLE so_to_al(
song_id VARCHAR(20),
album_id VARCHAR(20),
FOREIGN KEY (song_id) REFERENCES songs(id),
FOREIGN KEY (album_id) REFERENCES albums(id)
)engine innodb default charset =utf8;
//专辑歌手关系表
CREATE TABLE al_to_ar(
artists_id VARCHAR(20),
album_id VARCHAR(20),
FOREIGN KEY (artists_id) REFERENCES artists(id),
FOREIGN KEY (album_id) REFERENCES albums(id)
)engine innodb default charset =utf8;

爬专辑信息:

教你用Python爬取QQ音乐上的付费专辑_第5张图片

在Headers可以看到专辑信息的URL,也可以在双击打开的页面看到。

下面是处理专辑信息代码handle_album_callback.py

import requests
import json
import pymysql
import delete_from_db#在保存在数据库之前,先将数据库里与这个专辑相关的数据删除

class Song(object):
    def __init__(self,name,mid):
        self.name = name
        self.mid = mid


def get_album_callback(album_id):
    text = ''
    try:#未登录时的专辑信息URL
        url = 'https://c.y.qq.com/v8/fcg-bin/fcg_v8_album_info_cp.fcg?albummid='+album_id+'&g_tk=5381&jsonpCallback=albuminfoCallback&loginUin=0&hostUin=0&format=jsonp&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq&needNewCode=0'
        res = requests.get(url)
        text = res.text
    except Exception as e :
        print(e)
    return text


def handle_album_callback(album_id):
    songs =[]
    #去掉多余的文本
    text = get_album_callback(album_id)
    text = text.replace("albuminfoCallback(","")
    text = text[:-1]
    #用json模块将json转为python的字典类型(我猜的)
    album_js = json.loads(text)
    singername = album_js['data']['singername']
    singerid = album_js['data']['singermid']
    company = album_js['data']['company']
    aDate = album_js['data']['aDate']
    intro = album_js['data']['desc']
    genre = album_js['data']['genre']
    area = album_js['data']['lan']
    albumname = album_js['data']['name']
    albumid = album_js['data']['mid']
    delete_from_db.delete_from_db(album_id, singerid)
    conn = pymysql.connect(host='localhost',user='root',password='109071',db='musicDB')
    try:
        with conn.cursor() as cursor:
            sql = 'INSERT INTO `artists` VALUES(%s,%s,%s,%s,%s,%s)'
            cursor.execute(sql,(singername,singerid,'test','test','test','test'))
            sql1 = 'INSERT INTO `albums` VALUES(%s,%s,%s,%s,%s,%s,%s,%s)'
            cursor.execute(sql1,(albumname,album_id,genre,"null",company,intro[:100],area,aDate))
            sql2 = 'INSERT INTO `al_to_ar` VALUES(%s,%s)'
            cursor.execute(sql2, (singerid, albumid))
            for k in album_js['data']['list']:
                song = Song("", "")
                song.name = k['songname']
                song.mid = k['songmid']
                songs.append(song)
                sql3 = 'INSERT INTO `songs` VALUES(%s,%s,%s,%s,%s)'
                cursor.execute(sql3,(song.name,song.mid,genre,area,aDate))
                sql4 = 'INSERT INTO `so_to_al` VALUES(%s,%s)'
                cursor.execute(sql4,(song.mid,albumid))
                print(song.name, song.mid)
            conn.commit()
    except Exception as e:
        print(str(e)+'  HAC')
   #将歌曲的mid,和名字返回
    return songs

这时候我们已经拿到了很多信息了,但是最主要的歌曲没有弄到。

随便播放一首能播放的歌:

教你用Python爬取QQ音乐上的付费专辑_第6张图片

在Network里点最长的那个,这个就是我们想要的音乐数据。为了获取这个数据,我们得先制造这个歌曲资源的URL。而这个歌曲资源的URL里vkey(秘钥)是由QQ音乐专门的秘钥服务器生成的。这时候我就就要去找哪个文件里保存了服务器返回的秘钥。

教你用Python爬取QQ音乐上的付费专辑_第7张图片

我们找到了这个文件,然后在右边Header里看到Request URL 里面有点乱。为了让这个URL通俗易懂,我们需要用python2里的urllib模块(我不知道为什么我的python3里的urllib模块为啥没有这个功能)具体用法如下:

教你用Python爬取QQ音乐上的付费专辑_第8张图片

其实开发者模式也可以看到这个URL的参数,但是我懒得凑出来。这个URL最主要的参数是guid、songmid和loginUin这三个。这里需要注意一下,这个是需要登录才能获得的URL。由于这个我怕腾讯爸爸封我号,所以不敢多爬(手动狗头)。不登录就不能获取这个guid,这个东西是用来验证的,你登录时在服务端生成了这个东西。然后这里的songmid就是用来区分每一首歌的,这个东西就是我们在处理专辑信息时返回的Song类的mid。

接下来就是访问这个URL拿到里面的vkey:

教你用Python爬取QQ音乐上的付费专辑_第9张图片

拿到了这个purl我们就可以凑成歌曲资源的URL了,然后保存到电脑上。

下面是爬虫的主代码grab_songs,py:

import handle_album_callback
import requests
import generate_song_url
import pymysql
import os

headers={'Accept': '*/*',
'Accept-Encoding': 'identity;q=1, *;q=0',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Connection': 'keep-alive',
'Host': '122.190.14.159',
'Range': 'bytes=0-',
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.84 Safari/537.36'}

headers1={'authority': 'u.y.qq.com',
'method': 'GET',
'path': '/cgi-bin/musicu.fcg?callback=getplaysongvkey8040123747165813&g_tk=1617931267&jsonpCallback=getplaysongvkey8040123747165813&loginUin=1090710046&hostUin=0&format=jsonp&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq&needNewCode=0&data={%22req%22:{%22module%22:%22CDN.SrfCdnDispatchServer%22,%22method%22:%22GetCdnDispatch%22,%22param%22:{%22guid%22:%227978049712%22,%22calltype%22:0,%22userip%22:%22%22}},%22req_0%22:{%22module%22:%22vkey.GetVkeyServer%22,%22method%22:%22CgiGetVkey%22,%22param%22:{%22guid%22:%227978049712%22,%22songmid%22:[%22001KKIbk3vD39M%22],%22songtype%22:[0],%22uin%22:%221090710046%22,%22loginflag%22:1,%22platform%22:%2220%22}},%22comm%22:{%22uin%22:1090710046,%22format%22:%22json%22,%22ct%22:20,%22cv%22:0}}',
'scheme': 'https',
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'zh-CN,zh;q=0.9',
'cache-control': 'max-age=0',
'cookie': 'pgv_pvid=7978049712; eas_sid=31h5x3C1H0Q5C7t3C561l6i5D1; pt2gguin=o1090710046; RK=Fhp8FqShTu; ptcz=a5dfc959cadde7f185167913f1ca3dfd0a107e07cb63d2c1cc3d7d20d25e9470; pgv_pvi=7308248064; ptui_loginuin=1090710046; ts_uid=1811149517; wf_tid=1148256b2a52; wf_rid=0fd757881c20; pgv_info=ssid=s1763371196; pgv_si=s8480007168; qqmusic_fromtag=66; ptisp=cnc; ts_refer=xui.ptlogin2.qq.com/cgi-bin/xlogin; uin=o1090710046; skey=@CVMCk5RED; luin=o1090710046; lskey=00010000d3eab41a990cfc37b089f29764ec6057a610ff2a499b7bd67dba47affb73c1bf21547356109c9947; p_uin=o1090710046; pt4_token=4qW6bkRx-4GOHiOp-7C2R537dGLrlYlF7aGND6BAfv0_; p_skey=RjDyrGp-*NaDu4nleLdOpBGT7XrY69jy1aU9nAASZSc_; p_luin=o1090710046; p_lskey=00040000bb83c4265423609a0ae3abe12d4b8c3c89535839d24cd70f525a25630f28a623f752260a99036107; yq_index=0; yq_playschange=0; yq_playdata=; player_exist=1; yplayer_open=0; yqq_stat=0; ts_last=y.qq.com/n/yqq/album/001v3NMj3Pi45u.html',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.84 Safari/537.36'}

def grab_song(album_id):
    album_name = ''
    singer_name = ''
    songs = handle_album_callback.handle_album_callback(album_id)
    conn = pymysql.connect(host='localhost', user='root', password='109071', db='musicDB')
    try:
        with conn.cursor() as cursor:
             sql = 'select name from `albums` where id =%s'
             cursor.execute(sql,album_id)
             album_name = cursor.fetchone()[0]
             sql1 = 'SELECT name FROM artists where id in (SELECT artists_id FROM al_to_ar WHERE album_id = %s)'
             cursor.execute(sql1,album_id)
             singer_name = cursor.fetchone()[0]
    except Exception as e :
        print(str(e)+' GS')
    #path是歌曲的保存在本机的路径,请根据自己的电脑路径修改
    path = '/home/alexhowe/grabed_music/' + album_name+'/'
    if not os.path.exists(path):
        os.mkdir(path)
        print('The song will be stored at '+path)
    for s in songs:
        #请按照上面的方法得到这个获取vkey的URL(guid处为数字)
        url = 'https://u.y.qq.com/cgi-bin/musicu.fcg?callback=getplaysongvkey8040123747165813&g_tk=1617931267&jsonpCallback=getplaysongvkey8040123747165813&loginUin=1090710046&hostUin=0&format=jsonp&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq&needNewCode=0&data=' \
              '{"req":{"module":"CDN.SrfCdnDispatchServer","method":"GetCdnDispatch","param":' \
              '{"guid":"7978049712","calltype":0,"userip":""}},"req_0":{"module":"vkey.GetVkeyServer",' \
              '"method":"CgiGetVkey","param":{"guid":"7978049712","songmid":["'+s.mid+'"],' \
              '"songtype":[0],"uin":"1090710046","loginflag":1,"platform":"20"}},' \
              '"comm":{"uin":1090710046,"format":"json","ct":20,"cv":0}}'
        res = requests.get(url,headers=headers1)
     #song_url是我们获取vkey后凑成的歌曲资源URL
        song_url = generate_song_url.gen_song_url(res.text)
        res_song = requests.get(song_url,headers=headers)
        #保存到本地就不多说了
        with open(path+s.name+'-'+singer_name+'.m4a','wb') as f:
            f.write(res_song.content)
            print(s.name+' by '+singer_name+' has been saved!')
      
if __name__=='__main__':
    grab_song('000h66z521TUCT')#这里输入专辑ID

注意主爬虫代码(grab_songs.py)里的url要自己去登录(https://y.qq.com/)然后按照上面的方法获取的,其中的guid是阿拉伯数字。然后再像代码里那样将url里的songmid参数拼接成"songmid":["’+s.mid+‘"],因为我们要获取专辑里每一首歌的vkey。

下面是凑成歌曲资源URL的代码generate_song_url.py:

import json

#这个比较简单就不解释了,和专辑信息“清洗”差不多
def gen_song_url(text):
    text = text.replace('getplaysongvkey8040123747165813(','')
    text = text[:-1]
    song_js = json.loads(text)
    url_js = song_js['req_0']['data']['midurlinfo'][0]
    return 'http://122.190.14.159/amobile.music.tc.qq.com/'+url_js['purl']

结语:

好了,到这里我们爬取QQ音乐的爬虫代码就全部写好了。总结一下思路:

打开专辑页面->在地址栏里可以拿到专辑ID(这是主代码grab_songs.py里grab_song()函数的参数)->获取到专辑信息的javascript文件,然后再用handle_album_callback.py处理并返回Song类->这是已经拿到了songmid和歌曲名字,然后再用songmid去获取vkey->到generate_song_url.py就把拿到的vkey凑成歌曲资源URL返回。

整个过程是不是很简单?相信聪明的你一定能理解的~

最后还有一个delete_from_db.py(handle_album_callback.py里面调用了)等着你去写,没错这是一片关于SQL数据库的博客!没想到吧!

注意,代码运行前请在第一行添加:# -- coding: utf-8 --(因为添加了注释)

你要是实在不想写可以把handle_album_callback.py里用到的地方删掉,或者你要是想支持我也行:

点击下载

博客有点长感谢耐心看完!祝你在Python爬虫领域所向披靡!

你可能感兴趣的:(Python日记)