python利用charles爬虫爬取下载qq音乐歌单里的歌曲

因为这是我第一个独立实践的爬虫项目,所以这次把思路都放上来了

特别注意,用的是charles,可以拦截查看url

知识点:

·······利用charles分析网页,抓包,得出对应URL

·······json网页不是可以直接用json.load 直接转换json的

·······利用find_path快速找到所需内容的路径

······学会用try,预防程序中断

·······urltrieve下载文件可以添加回调函数

·······多进程使用

·······利用sys.stdout.write 记录进度

============先打开charles,再操作qq音乐

1.打开qq音乐页面,进入我的音乐


2.选择我创建的歌单


3.选择其中一个歌单


4.进入歌单,随便选一首歌,点击播放



5.最后就进入了这个页面,可以播放歌曲了


==========================================================

好了,现在可以看看charles上有哪些url

特喵的,这么多,怎么入手?


思路:

①不一定要跟着步骤去找url,可以反过来的嘛

    先找歌曲文件mp3(原来是m4a格式)

    找code是206的url,206一般是歌曲跟视频下载什么的

    找到了,不过这个url好像有点儿复杂

  

    我把这个url分析了下:

     

       不同歌曲间,只需改变那2个变量即可下载!

②那么,如何得到那个m4a跟 vkey呢?

    这时就是charles大显神威了,直接ctrl+f,搜索那2个变量,直接vkey就行了!

    

    看,结果是2部分,分别是2个网址,下面那个就是歌曲的url了,复制的文本是在Request URL跟Header,那就是刚刚从这里赋值过来的,所以忽略,主要看上面那个

    Response Body:206,,,对了,看来vkey就是出自这个网址了,点击进去

    

        OK,看来找到vkey跟filename的出处了,那么下一步就是分析这个URL的组成了

        PS:注意,如果进入这个网址,构造的时候需要在header加上referer:https://y.qq.com/portal/player.html

     很明显,这个url也很鸡儿复杂,我还得分析:

        

③:看上去这个url很复杂,实际只需2个变量!songmid跟filename,不过仔细一看,这2个变量其实只需songmid就行了,

    因为filename=C400+songmid,这时就继续利用charles,ctrl+F 看看这个songmid出自哪里

    搜索结果如下:


    包含songmid字串符的url有这么多,而我的目的页面是这个:

    

    包含360多首歌的songmid,经过对比就是上面qzone跟splcloud这2个网址,我就选了qzone这个继续分析了

    

    OK!所有歌曲的songmid都在里面了,剩下来就再分析这个URL的组成了             PS:referer:'https://y.qq.com/n/yqq/playlist/2986328419.html'

     

===============================================

所需的网址全部找出来了,现在把思路整理下:

1.进入包含所有songmid的歌单URL:得到songmid

https://c.y.qq.com/qzone/fcg-bin/fcg_ucc_getcdinfo_byids_cp.fcg?type=1&json=1&utf8=1&onlysong=0&disstid=2986328419&format=jsonp&g_tk=1547190586&jsonpCallback=playlistinfoCallback&loginUin=2020197426&hostUin=0&format=jsonp&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq&needNewCode=0

    referer:https://y.qq.com/n/yqq/playlist/2986328419.html

2.进入songmid的URL:得到对应的vkey

https://c.y.qq.com/base/fcgi-bin/fcg_music_express_mobile3.fcg?g_tk=1547190586&jsonpCallback=MusicJsonCallback7615551634069047&loginUin=2020197426&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq&needNewCode=0&cid=205361747&callback=MusicJsonCallback7615551634069047&uin=2020197426&songmid=003EsQzm2RJPmv&filename=C400003EsQzm2RJPmv.m4a&guid=4670078301

    referer:https://y.qq.com/portal/player.html

3.得到歌曲的URL,可以直接下载了

http://dl.stream.qqmusic.qq.com/C400003EsQzm2RJPmv.m4a?vkey=6569916EE22DF54C0EB5DFBFE390B426B6F97153BF510DF12D36C4E1A72EF04E528CE77655E19B1DC6F6533B893EC217BF440DA84363ED3B&guid=4670078301&uin=2020197426&fromtag=66


=========================================================

现在终于可以写代码了!

一、先搞歌单这部分

import requests,json,re,urllib,os

url='https://c.y.qq.com/qzone/fcg-bin/fcg_ucc_getcdinfo_byids_cp.fcg?type=1&json=1&utf8=1&onlysong=0&disstid=2984189527&format=jsonp&g_tk=1547190586&jsonpCallback=playlistinfoCallback&loginUin=2020197426&hostUin=0&format=jsonp&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq&needNewCode=0'
header={}
header['user-agent']='Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36'
header['referer']='https://y.qq.com/n/yqq/playlist/2984189527.html'

req=requests.get(url,headers=header)
req1=str(req.text).replace('playlistinfoCallback','').strip('()')#text不能直接json,要把头跟末尾去掉才行!
info=json.loads(req1)
print(info)

text的内容:

我去,好恐怖的字典,怎么找出我要的songmid位置?

当然可以自己手动找,不过我之前不是写了个find_path模块吗?正好派上用场

from find_path.find_path import find_path
d=find_path()
d.work(info,'004WJqnr0OlghO')

搜索结果:


OK!知道什么路径了,现在把songmid、歌手、歌曲名、都提取出来!

import requests,json,re,urllib,os
from find_path.find_path import find_path
playlist='2984189527'   ##指定歌单
url='https://c.y.qq.com/qzone/fcg-bin/fcg_ucc_getcdinfo_byids_cp.fcg?type=1&json=1&utf8=1&onlysong=0&disstid={}\
&format=jsonp&g_tk=1547190586&jsonpCallback=playlistinfoCallback&loginUin=2020197426\
&hostUin=0&format=jsonp&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq&needNewCode=0'.format(playlist)
header={}
header['user-agent']='Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36'
header['referer']='https://y.qq.com/n/yqq/playlist/{}.html'.format(playlist)

req=requests.get(url,headers=header)
req1=str(req.text).replace('playlistinfoCallback','').strip('()')#text不能直接json,要把头跟末尾去掉才行!
info=json.loads(req1)
songinfo=info['cdlist'][0]['songlist']
songlist={}
for name in songinfo:
    songlist[name['songmid']]=name['singer'][0]['name']+'-'+name['songname']
print(songlist)    ##得到的字典就是'003gltCe0JUvCU': '雷安娜-停不了的爱'  各个歌曲组成

二、现在随便拿一首歌进行下载:

#求vkey
header1={}
header1['user-agent']='Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36'
header1['referer']='https://y.qq.com/portal/player.html'
id='002pRyIY2kSvK1'   #要找的歌曲的songmid
url='https://c.y.qq.com/base/fcgi-bin/fcg_music_express_mobile3.fcg?g_tk=1547190586&jsonpCallback=MusicJsonCallback8769319220381753&loginUin=2020197427&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq&needNewCode=0&cid=205361747&callback=MusicJsonCallback8769319220381753&uin=2020197427\
&songmid={}&filename=C400{}.m4a&guid=4670078301'.format(id,id)
keyreq=requests.get(url,headers=header1)
vkey=re.findall(r'vkey":"(.+)"}]}}',keyreq.text)[0]

##下载歌曲
songurl='http://dl.stream.qqmusic.qq.com/C400{}.m4a?vkey={}\
&guid=4670078301&uin=2020197427&fromtag=66'.format(id,vkey)
urllib.request.urlretrieve(songurl,os.getcwd()+'\\'+songlist[id]+'.m4a')

  

 终于搞定了!! 剩下就是自己把代码简化了!然后把歌单里的所有歌曲都下载,代码整合如下

import requests,json,re,urllib,os,time,sys
from find_path.find_path import find_path
from multiprocessing import Pool
import logging

logger = logging.getLogger(__name__)
class qqmusic_songs(object):
    def __init__(self,path):
        self.headers={'user-agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36'}
        self.songdict={}                              #get_songdict会给它赋值
        self.path=path

    def get_songdict(self,playlist):
        '''得出所输入歌单每首歌songmid:歌名 组成的字典'''
        headers=self.headers.copy()
        headers['referer'] = 'https://y.qq.com/n/yqq/playlist/{}.html'.format(playlist)
        url='https://c.y.qq.com/qzone/fcg-bin/fcg_ucc_getcdinfo_byids_cp.fcg?type=1&json=1&utf8=1&onlysong=0&disstid={}\
        &format=jsonp&g_tk=1547190586&jsonpCallback=playlistinfoCallback&loginUin=2020197426\
        &hostUin=0&format=jsonp&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq&needNewCode=0'.format(playlist)

        req=requests.get(url,headers=headers)
        req1=str(req.text).replace('playlistinfoCallback','').strip('()')#text不能直接json,要把头跟末尾去掉才行!
        info=json.loads(req1)

        songinfo=info['cdlist'][0]['songlist']            #对应的歌单里每首歌的歌曲信息
        songdict={}
        for name in songinfo:
            songdict[name['songmid']]=name['singer'][0]['name']+'-'+name['songname']+'__['+name['albumname']+']'
        self.songdict=songdict
        return songdict    ##得到的字典就是'003gltCe0JUvCU': '雷安娜-停不了的爱'  各个歌曲组成


    def download_song(self,songmid):
        '''输入songmid,得出对应的vkey, 用urlretrieve下载歌曲'''
        headers=self.headers.copy()
        headers['referer']='https://y.qq.com/portal/player.html'
        url='https://c.y.qq.com/base/fcgi-bin/fcg_music_express_mobile3.fcg?g_tk=1547190586&jsonpCallback=MusicJsonCallback8769319220381753&loginUin=2020197427&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq&needNewCode=0&cid=205361747&callback=MusicJsonCallback8769319220381753&uin=2020197427' \
            '&songmid={}&filename=C400{}.m4a&guid=4670078301'.format(songmid,songmid)
        try:                                                     #不知哪首歌有问题,用try安全
            keyreq=requests.get(url,headers=headers)
            vkey=re.findall(r'vkey":"(.+)"}]}}',keyreq.text)[0]   #得出vkey,就可以得出歌曲的url            ##下载歌曲
            songurl='http://dl.stream.qqmusic.qq.com/C400{}.m4a?vkey={}&guid=4670078301&uin=2020197427&' \
                    'fromtag=66'.format(songmid,vkey)
            path=self.path+'\\'+self.songdict[songmid]+'.m4a'
            if (not os.path.exists(path)):                       #如果中断,已经有的文件就不用再下载了
                urllib.request.urlretrieve(songurl,path,reporthook=self.Schedule)
            time.sleep(0.8)
        except Exception as e:
            logger.warning(e)
            logger.warning("有问题的网址是%s,歌曲是%s"% (url,self.songdict[songmid]))

    def Schedule(self, a, b, c):
        '''urlretrieve的回调函数'''
        songscount=len(os.listdir(self.path))
        per=100.0 * songscount/len(self.songdict)
        if per > 100:
            per = 1
        sys.stdout.write("  " + "%.2f%% 已经下载%s首歌  共%s首歌" % (per, songscount,len(self.songdict)) + '\r')
        sys.stdout.flush()


    def work(self,playlist):
        '''多进程下载'''
        time1=time.time()
        songdict = self.get_songdict(playlist)
        print('正在下载歌单  %s 的歌曲'% playlist)
        pool=Pool(8)
        pool.map(self.download_song, songdict.keys())
        pool.close()
        pool.join()
        time2=time.time()
        usetime=time2-time1
        songscount = len(os.listdir(self.path))
        print('下载完成!  理论上共%s首歌,实际下载了%s首歌,耗时%.1fs'%(len(self.songdict),songscount,usetime))


if __name__ == '__main__':
    path=r'C:\Users\Administrator\Desktop\歌曲'
    d=qqmusic_songs(path)
    d.work('2984189527')




    



不过话说sys.write这个记录进度的方法,只能在cmd利用,pycharm显示不了的,不过可以用它的terminal控制台运行


猜你喜欢

转载自blog.csdn.net/qq_38282706/article/details/80140236