Python 爬虫之网易云音乐下载
目标
用 Python 根据网易云音乐的 ID,下载音乐,保存到本地 MP3 格式
可以下载歌曲的范围:所有能够听的歌曲
配置基础
Python 3.5
模块
sys(可选)
pycrypto
base64
requests
json
progressbar(可选)
pycrypto
这是哈希函数(如 SHA256 和 RIPEMD160)和各种加密算法(AES,DES,RSA,ElGamal 等)的集合.主要是用来加密解密,为何要用这个呢,稍后分析
安装
python 自带的模块,主要是配合 pycrypto 模块使用
pip install pycrypto
base64
requests,json
requests 主要用来发送网络请求, json 主要用于解析网络请求的 response
分析
为了避免麻烦,我们选择网页版的网易云音乐而不用客户端的,省去抓包的麻烦
我们随便选择一首歌(这里选择: 一千零一夜 )然后打开网页,得到如下界面:
然后打开浏览器网络面板,点击播放按钮,然后查看网络请求,如下:
一共发送了四个网络请求,仔细一点,我们发现了一个有趣的请求,就是上图最后一个,带有. mp3 后缀的那个,很明显,这里是将一千零一夜这首歌缓存了下来,复制该网络请求到浏览器地址栏打开,然后浏览器就开始下载一千零一夜这首歌.到此,好像前面提的所谓的目标完成了,但是我不开心,身为一个开发人员,这么没有技术含量的东西,是不是可以考虑用技术去实现呢.能不能输入一个歌曲的 ID,然后就把歌曲下回来呢.
我们播放多几首歌曲,很容易发现,每一首歌曲都会有一个独立的链接,仔细看看这个链接( http://m10.music.126.net/2018... ),显然是经过处理的,这个处理有可能是前端直接处理的,也有可能是后端处理的(是不是说了等于白说...),后端处理会有多种情况,其中一种就是另一个网络请求返回来对应的东西,反正网络请求不多,我们先看看网咯请求,一看吓一跳,还真蒙着了,上图中第一个网络请求返回来的数据
再看一下请求的组成
只要模拟这个请求,就可以得到歌曲的链接,只要得到链接就能下载歌曲.在该请求的参数中,params 以及 encSecKey 都是一个经过加密的数据,在反复分析点击播放按钮的事件后,得到 JavaScript 进行了如下操作
其中,window.asrsea 函数代码如下
var bPc2x = window.asrsea(JSON.stringify(j4n), buv7o(["流泪", "强"]), buv7o(Tg9X.md), buv7o(["爱心", "女孩", "惊恐", "大笑"]));
e4i.data = k4o.cE5J({
params: bPc2x.encText,
encSecKey: bPc2x.encSecKey
})
由上得知,window.asrsea 一共传递了四个参数(假设为 window.asrsea(a, b, c, d)),而这四个参数中,只有 a 是一个跟歌曲 id 相关的参数,其他三个都是一个常量
!
function() {
function a(a) {
var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
c = "";
for (d = 0; a > d; d += 1) e = Math.random() * b.length,
e = Math.floor(e),
c += b.charAt(e);
return c
}
function b(a, b) {
var c = CryptoJS.enc.Utf8.parse(b),
d = CryptoJS.enc.Utf8.parse("0102030405060708"),
e = CryptoJS.enc.Utf8.parse(a),
f = CryptoJS.AES.encrypt(e, c, {
iv: d,
mode: CryptoJS.mode.CBC
});
return f.toString()
}
function c(a, b, c) {
var d, e;
return setMaxDigits(131),
d = new RSAKeyPair(b, "", c),
e = encryptedString(d, a)
}
function d(d, e, f, g) {
var h = {},
i = a(16);
return h.encText = b(d, g),
h.encText = b(h.encText, i),
h.encSecKey = c(i, e, f),
h
}
function e(a, b, d, e) {
var f = {};
return f.encText = c(a + e, b, d),
f
}
window.asrsea = d,
window.ecnonasr = e
} ();
再研究 window.asrsea 的代码,发现请求的两个参数 params 以及 encSecKey 都在这里加密了,其中 params 经过了两次 AES 加密,第一次加密的时候,传入了两个参数,一个是 a,一个是 d, 第二个加密的两个参数,第一个是第一次加密的结果, 第二个是一个 16 位的随机字符串,因为是一个随机的字符串,所以我们可以随便用一个 16 位的字符串就行了,由于这里这个随机的字符串固定了,那第二个参数 encSecKey 就是一个固定的值
b = "010001";
c = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
d = "0CoJUm6Qyw8W8jud"
至此,我们的分析完成,也得到了需要的信息
Python 实现
Python 想要模拟请求,那就需要进行 AES 加密,因此我们就用到了开始所说的 pycrypto 模块
from Crypto.Cipher import AES
import base64
def aes_encrypt(text, key):
iv = "0102030405060708"
pad = 16 - len(text) % 16
text = text + pad * chr(pad)
encryptor = AES.new(key, AES.MODE_CBC, iv)
result = encryptor.encrypt(text)
result_str = base64.b64encode(encrypt_text)
return result_str
来源: https://segmentfault.com/a/1190000012818254