Web端虾米音乐爬虫实战分析 处理参数JS加密

鳄鱼君

发表文章数:591

Vieu四代商业主题

高扩展、安全、稳定、响应式布局多功能模板。

¥69 现在购买
首页 » Python » Web端虾米音乐爬虫实战分析 处理参数JS加密

前言

在对虾米音乐PC客户端抓包失败后,尝试虾米APP抓包,然后再次失败,API数据全部进行了加密,所以转换思路进行网页版虾米音乐数据抓取,哈哈成功了!一起来跟鳄鱼君分析一下虾米音乐的数据接口吧!

先进入虾米音乐官网,我们呢这里先对:PC虾米音乐客户端数据抓包做一下补充。在右上角的搜索栏输入张国荣,点击搜索,数据就在眼前:

Web端虾米音乐爬虫实战分析 处理参数JS加密

刚开始我是奔着抓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文件中发现了加密的字符串:

Web端虾米音乐爬虫实战分析 处理参数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音乐的抓取和下载

未经允许不得转载:作者:鳄鱼君, 转载或复制请以 超链接形式 并注明出处 鳄鱼君
原文地址:《Web端虾米音乐爬虫实战分析 处理参数JS加密》 发布于2020-04-02

分享到:
赞(4) 赏杯咖啡

评论 抢沙发

1 + 5 =


文章对你有帮助可赏作者一杯咖啡

支付宝扫一扫打赏

微信扫一扫打赏

Vieu4.6主题
专业打造轻量级个人企业风格博客主题!专注于前端开发,全站响应式布局自适应模板。
切换注册

登录

忘记密码 ?

您也可以使用第三方帐号快捷登录

Q Q 登 录
微 博 登 录
切换登录

注册