虾米音乐爬虫实战分析 批量下载虾米音乐到本地

刚开始我是奔着抓API的,发现翻页的参数不好找,后来看到搜索出来的歌曲都存在当前url中,翻页只需更换浏览器中的url参数即可:https://www.xiami.com/list?page=1&query={“searchKey”:”张国荣”}&scene=search&type=song,那么为啥要费那么多事找接口呢?

但是后来发现歌曲的下载地址存在于API接口中,呢我们不得不寻找_s和_q的参数,也就是说我们上面的代码只能抓取张国荣的歌曲和信息,不能下载到本地,显然没有什么卵用,我们在观察一下,同时参考一篇文章:https://www.cnblogs.com/zhuchunyu/p/11496882.html,有兴趣的可以去看看。重点就是找到md5加密的内容是什么,最中在: https://g.alicdn.com/xiami-frontend/web-xiami-main-node/3.8.0/main/index.js js文件中发现了加密的字符串:

虾米音乐爬虫实战分析 批量下载虾米音乐到本地

很明显就可以看到_s的值是通过: cookie中的xm_sg_tk通过_切割取第一个值 + xmMain + 接口地址 + _ + _q ,知道了参数的来源那么我们就可以构造这样的参数放到url中去请求数据。鳄鱼君推荐使用MD5转换工具,便于测试参数是否正确。代码参考:

import requests,json,os
from hashlib import md5

class XiMimusic():
    def __init__(self):
        self.URL='http://www.xiami.com'
        #请求头和cookies
        self.headers={
            'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
            'Accept-Encoding':'gzip, deflate, br',
            'Accept-Language':'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
            'Connection':'keep-alive',
            'Host':'www.xiami.com',
            'TE':'Trailers',
            'Upgrade-Insecure-Requests':'1',
            'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0'
            }
        #维持会话,携带cookies
        self.session=requests.Session()
        self.session.get(url=self.URL,headers=self.headers)
        # 每日音乐API接口推荐
        self.APIDailySongs = "/api/recommend/getDailySongs"
        # 排行榜音乐API接口
        self.APIBillboardDetail = "/api/billboard/getBillboardDetail"
        # 歌单API接口
        self.APICollectionL = "/api/collect/getCollectDetail"
        # # 歌曲详情信息 包含歌曲的下载地址
        self.APISongDetails = "/api/song/getPlayInfo"

    #获取对应的接口地址
    def get_api_url(self, api):
        return self.URL + api
    # 获取xm_sg_tk的值,用于对数据加密的参数
    def  get_xm_sg_tk(self):
        xm_sg_tk = self.session.cookies.get('xm_sg_tk')
        return xm_sg_tk.split("_")[0]
    # 获取加密字符串_s
    def get_params_s(self,api,_q: str = ""):
        xm_sg_tk=self.get_xm_sg_tk()
        xxoo = xm_sg_tk + '_xmMain_' + api + '_' + _q
        md_5 = md5(xxoo.encode()).hexdigest()
        return md_5
    def get_daily_songs(self,*args):
        """
        获取每日推荐的30首歌曲  只需_s参数
        :param args:
        :return:
        """
        url=self.get_api_url(self.APIDailySongs)
        params={
            "_s":self.get_params_s(self.APIDailySongs)
        }
        result = self.session.get(url=url, params=params,)
        data = json.loads(result.content)
        try:
            if data['code']=="SUCCESS":
                for i in data['result']['data']['songs']:
                    item={}
                    item['songname']=i['songName']
                    item['songid']=i['songId']
                    yield item
            else:
                return None
        except Exception as e:
            return None
    # 获取虾米音乐的音乐排行榜
    def get_billboard_song(self, billboard_id):
        """根据排行榜的id来下载不同的排行榜歌曲
         :param billboard_id:
        :return:
        """
        url = self.get_api_url(self.APIBillboardDetail)
        _q = '{\"billboardId\":\"%s\"}' % billboard_id
        params = {
            "_q": _q,
            "_s": self.get_params_s(self.APIBillboardDetail, _q)
        }
        result = self.session.get(url=url, params=params)
        data = json.loads(result.content)
        try:
            if data['code'] == "SUCCESS":
                for i in data['result']['data']['billboard']['songs']:
                    item = {}
                    item['songname'] = i['songName']
                    item['songid'] = i['songId']
                    yield item
            else:
                return None
        except Exception as e:
            return None
    def get_song_details(self,func):
        """
        获取音乐的下载地址
        :param func: 参数为一个函数
        :return: 返回音乐的下载地址和音乐名字
        """
        try:
            for i in func:
                url=self.get_api_url(self.APISongDetails)
                _q = "{\"songIds\":[%s]}" % i['songid']
                params = {
                    "_q": _q,
                    "_s": self.get_params_s(self.APISongDetails, _q)
                }
                result = self.session.get(url=url, params=params,)
                data = json.loads(result.content)
                yield data['result']['data']['songPlayInfos'][0]['playInfos'][1]['listenFile'],i['songname']
        except Exception as e:
            return None
    def download_music(self,songurl,songname:str =""):
        """
        下载音乐到本地
        :param songurl:
        :param songname:
        :return:
        """
        try :
            file_path='{0}/{1}.{2}'.format('G:/XM音乐',songname,'m4a')
            if not os.path.exists(file_path):
                with open(file_path,'wb') as f:
                    f.write(requests.get(songurl).content)
        except Exception as e:
            return None
    def user_choice(self):
        music_list = ['美国Billboard单曲榜id:204', '韩国MNET音乐排行榜id:206', '英国UK单曲榜id:203', 'Beatport电音榜id:329', '新歌榜id:102','抖音热歌榜id:332', 'K歌榜id:306', '热点讨论榜id:331', '歌单收录榜id:305', '趴间热歌榜id:327', '影视原声榜id:324']
        for k in music_list:
            print(k)
        data = input('请输入需要下载歌曲榜单的id(请不要乱输入否则程序会崩溃....):')
        return data
    def start(self):
        id=self.user_choice()
        try:
            for songurl, songname in self.get_song_details(self.get_billboard_song(id)):
                if songurl and songname:
                    print("正在下载歌曲:{0}".format(songname))
                    self.download_music(songurl,songname)
        except Exception as e:
            return None
        # #下载每日30首歌曲到本地
        # try:
        #
        #     for songurl,songname in self.get_song_details(self.get_daily_songs()):
        #         if songurl and songname:
        #             print("正在下载歌曲:{0}".format(songname))
        #             self.download_music(songurl,songname)
        # except Exception as e:
        #     return  None
    if __name__=='__main__':
    xm=XiMimusic()
    xm.start()

代码测试于2020-4-4日,在这里深切哀悼在抗击新冠肺炎疫情牺牲的烈士和逝世同胞们!!!!

虾米音乐使用了js对请求的参数进行了加密,请求需携带headers且cookies存在过期时间,如果处理不好动不动就会出现:www.xiami.com:443/api/search/searchSongs/tmd/punish?x5secdata。其中tmd格外显眼,这是一个滑动验证的url,你在尝试的时候可能会经常出现在你的控制台中。

代码中有很多内容没有完善,但是下载几百首歌曲是没有问题的,我们已经知道了参数如何构建,那么还有什么难度呢?多花费点时间罢了。

相比于虾米音乐,QQ音乐的参数非常好找,在响应的html源码中是可以找到的,我们也可以轻松构建url获取数据,详情参考:QQ音乐的抓取和下载

你可能感兴趣的:(虾米音乐爬虫实战分析 批量下载虾米音乐到本地)