美文网首页
虾米音乐下载

虾米音乐下载

作者: 小温侯 | 来源:发表于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