美文网首页
虾米音乐下载

虾米音乐下载

作者: 小温侯 | 来源:发表于2018-07-21 00:17 被阅读159次

    思路和想法

    虾米音乐有个网页播放器,这回我抓的就是在登陆的时候,这个播放器上所有的歌单里的所有歌曲。如图:

    xiami-界面.jpg

    我之前没用过虾米,所以注册了个新账号,新建了三个歌单,每个歌单(左侧红框)大概3-5首歌,我的目标就是将这些歌曲都下载到电脑里来。

    当我们点击这个播放器页面里的链接时,页面的地址不会发生变化,显然这又是个异步加载,但是这次找XHR要注意一点细节:

    xiami-xhr分析.jpg

    经过一定的分析,左侧第一个红框里的XHR包含了所有的歌单以及每个歌单的list_id,之后我们可以用这个id来构建一条类似于蓝框里的XHR,用来获取某个歌单里所有歌曲的location,location就是该歌曲加密后的下载URL。这里有几个细节:

    • 如果你点击播放某个歌曲,会收到如第二个红框那样的XHR,它里面也包含了该歌曲的location,这种方法也是可行的,只是每次只能获取一首歌曲的location,用蓝框里那种XHR可以一次获得整个歌单里所有歌曲的location。
    • 在构建蓝框里那条XHR时,要注意请求头的字段,不仅要包含cookies,也要包含referer,后者很重要否则服务器会返回404错误,至于referer的内容,里面涉及到一个ID,但据我测试,任何一首歌的songID都可以。
    • 请求的两个参数_ksTScallback,前者是由当前的时间(time.time())加一个数字组成,后者是jsonp加一个数字,后一个数字比前一个数字大1。这里我没具体测出数字的范围,取一个1000以内的应该没问题。callback的值会在之后返回的json文件里体现出来,说实话,个人觉得有点画蛇添足地味道。

    现在我们有了每个歌单的id,并且根据这个id,获取了歌单里所有歌曲的信息,这里我只取了歌曲名、歌手和位置。接着你会发现这个位置是加密过的,所以这时应该去js里找这个加密函数。

    找js文件其实也是有技巧的,一般来说,像这种功能肯定是自定义的,所以要去自定义的js文件里找,然后搜索一些如location啊,url之类的关键字,这次运气好,很快就找了。加密函数在https://g.alicdn.com/music/music-player/1.1.6/??common/global-min.js,pages/index/page/init-min.js里,格式化后第1087-1104行,如图。

    xiami-getlocation.jpg

    这里我暂且不谈这个函数是怎么运作的。先说点别的,我在网上看到了一些关于下载虾米音乐的资料,几乎全部在获取了加密后的location之后都是开始自己琢磨如何解密,个人以为这其实是不可取的,像js这种端脚本语言,如果要解密肯定是有迹可循的,而且肯定在某个js文件里,区别只是写代码的人把它隐藏到哪种程度。所以,如果遇到类似的情况,应该去找js,而不是先自己琢磨。

    这里还有一个技巧就是:其实我们根本不需要理解这个函数是怎么运作的,也不需要将它改变成python版本,python有一个名为PyExecJS的库可以直接运行js代码。很好很强大。

    代码

    代码很简单,首先获取所有歌单的list_id,然后根据每个list_id获取其中的所有歌曲的信息,然后解密location的值,最后在将歌曲下载下来。最后下载下来的效果如图:

    xiami-下载.jpg

    js.py

    jsstr = '''
    function getLocation(a) {
        if (-1 !== a.indexOf("http://"))
            return a;
    
        for (var b = Number(a.charAt(0)), c = a.substring(1), d = Math.floor(c.length / b), e = c.length % b, f = new Array, g = 0; e > g; g++)
            void 0 == f[g] && (f[g] = ""),
            f[g] = c.substr((d + 1) * g, d + 1);
    
        for (g = e; b > g; g++)
            f[g] = c.substr(d * (g - e) + (d + 1) * e, d);
    
        var h = "";
        for (g = 0; g < f[0].length; g++)
            for (var i = 0; i < f.length; i++)
                h += f[i].charAt(g);
                
        h = unescape(h);
        var j = "";
        for (g = 0; g < h.length; g++)
            j += "^" == h.charAt(g) ? "0" : h.charAt(g);
        
        return j = j.replace("+", " ")
        }
    '''
    
    
    

    xiami.py

    configure.py请参考拙作:爬取糗事百科的内容和图片并展示

    import requests
    import re
    import json
    import os
    import time
    from random import choice
    import random
    import execjs
    
    from js import jsstr
    import Configure
    
    url = "https://www.xiami.com/playercollect/list"
    header = {'user-agent': choice(Configure.FakeUserAgents)}
    
    cookies = {}
    
    cookiestr = '''gid=152425845220935; 
                _unsign_token=1ce1fe55087ea1f21db47780701aa248; 
                cna=BOBeExJaZ2MCAUWMrkuEudTQ; 
                UM_distinctid=162e4e236948cf-0773ed2704ac51-3b60490d-1fa400-162e4e236957a6; 
                join_from=0zqfTI9Kv2Ew3f7BEdw; 
                _xiamitoken=9934bc4572854b88e3b35017a976a3c7; 
                user_from=2; 
                PHPSESSID=abdb66f05f100adc171436cdc0db744a; 
                s_uid=2523264732; 
                access_token=2.00Q73lkC81ERME01ffcdd5d907MPnY; 
                __XIAMI_SESSID=9dd80406b67dc4af1df541f343f5ecdf; 
                xmgid=6539bf5e-7321-44d3-ab3d-0d8ff66d0f50; 
                connect_sina=76633; 
                expires_in=2652725; 
                CNZZDATA2629111=cnzz_eid%3D1724500643-1524255732-https%253A%252F%252Fwww.google.com%252F%26ntime%3D1525313285; 
                _umdata=535523100CBE37C33075D564A95D152BC5B96D713321CA2CED3D987F95DDB172352CD465FA09B190CD43AD3E795C914C6D829DD7915A193EB257667C30CCA2E2; 
                member_auth=hWrNTo4duj9lgqPAT4FlIiIW4OLdHDLSwo0C3rIl5AMhJ9wBa4TxlauSRAlB3SiVqVEmwDAiBbv9xkT9%2FlYdtts; 
                user=362958619%22%E4%BD%8E%E8%B0%83%E9%9A%90%E5%BF%8DOB%22%220%220%22do%220%220%220%22975ef99c85%221525314365; 
                t_sign_auth=0; 
                CNZZDATA921634=cnzz_eid%3D1878118879-1524256440-https%253A%252F%252Fwww.google.com%252F%26ntime%3D1525314514; 
                form_timestamp=1525318171; 
                XMPLAYER_url=/song/playlist-default; 
                XMPLAYER_addSongsToggler=0; 
                __guestplay=MTc3NDMyMTIxNSw2OzE3OTYwMzI0MTMsMjA7MTgwMzAwMjM4MSw0OzE3OTYwMzI0MTIsMg%3D%3D; 
                XMPLAYER_isOpen=0; 
                isg=BEREMy-7wt1ZiXahWEFx4c_eFcI8XWhO-TQFnl7k1I_SieVThG1fVPBozTdRyqAf
                '''
    
    for cookie in cookiestr.split(';'):
        name,value=cookie.strip().split('=',1)  
        cookies[name]=value
    
    def getRandomParams():
        a = int(time.time()*1000)
        b = random.randint(200,1000)
    
        return {'_ksTS':'{0:d}_{1:d}'.format(a,b), 'callback':'jsonp{0:d}'.format(b+1)}
    
    def getPlaylist():
        playlist = []
        try:    
            response = requests.get(url, headers=header, params=getRandomParams(), cookies=cookies)
            content = None
    
            if response.status_code == requests.codes.ok:
                content = response.text
                
        except Exception as e:
            print (e)
    
        pattern = re.compile('"list_id":(\d+),', re.S)
        data = pattern.findall(content)
        return data
    
    def getSongLocation(list_id):
    
        url2 = "https://www.xiami.com/song/playlist/id/{0:s}/type/3/cat/json".format(list_id)
    
        header = {}
        header['user-agent'] =  choice(Configure.FakeUserAgents)
        header['referer'] = 'https://www.xiami.com/play?ids=/song/playlist/id/1796032423/object_name/default/object_id/0'
    
        payload = {
            '_ksTS':'{0:d}_428'.format(int(time.time()*1000)), 
            'callback':'jsonp_429'
        }
    
        try:    
            response = requests.get(url2, headers=header, params=payload, cookies=cookies)
            content = None
            
            if response.status_code == requests.codes.ok:
                content = response.text
                
        except Exception as e:
            print (e)
        
        data = json.loads(content[11:][:-1])
        tracklists = data.get('data').get('trackList')
        ctx = execjs.compile(jsstr)
    
        res = []
        for track in tracklists:
            tmp = {}
            tmp['songName'] = track.get('songName')
            tmp['singers'] = track.get('singers')
            tmp['location'] = "http:"+ ctx.call('getLocation',  track.get('location'))
            res.append(tmp)
    
        return res
    
    def DownloadSong(SongURLs):
        if not os.path.exists("Download"):
            os.makedirs("Download")
    
        for songurl in SongURLs:
            req = requests.get(songurl.get('location'))
            filename = "{0:s}-{1:s}.mp3".format(songurl.get('songName'), songurl.get('singers'))
            with open("Download/"+filename, 'wb') as file:    
                file.write(req.content)
            print ("Download {0:s} Successfully.".format(filename)) 
    
    
    if __name__ == '__main__':
        ids = getPlaylist()
        for list_id in ids:
            res = getSongLocation(list_id)
            DownloadSong(res)
            
    

    相关文章

      网友评论

          本文标题:虾米音乐下载

          本文链接:https://www.haomeiwen.com/subject/apycmftx.html