回顾和概览
在 爬取网易云音乐个人动态中的视频(Ⅰ) 中简单的分析了一下需要做什么, 现在要做的就是获取网易云的api, 很遗憾, 网易云并没有开放api出来, 但是我们可以对网页进行调试, 尝试从中获得我们需要的信息.
参考
为什么已有两篇分析我还要自己再写一篇呢?
原因有二:
- 两篇分析对我来说还不足够详细
- 看完两篇分析之后, 是我第一次使用断点调试js, 所以想详细记录下来, 当作笔记
开始
从 爬取网易云音乐个人动态中的视频(Ⅰ) 中, 我们发现api(http://music.163.com/weapi/cloudvideo/playurl)中参数有两个 params 和 encSecKey, 加密肯定是通过js驱动的, 所以, 在网易云网页的js上搜索可能会有所发现.
还是在 爬取网易云音乐个人动态中的视频(Ⅰ) 中提到的 视频网页 上进行分析, 可能第一次打开这个网页会出现如下图的情况
刷新一遍, 就有我们需要的core.js文件了:
分析core.js
点击打开这个core.js, 你会发现js代码都没有缩进, 很难看, 点击一下页面的"{}", Chrome浏览器会对其进行格式化并在新窗口打开这个格式化后的core.js, 如下图
搜索字符串"encSecKey", 可以看到有3个匹配, 逐一查看, 在第二个查找结果中发现这和 爬取网易云音乐个人动态中的视频(Ⅰ) 中提到的参数很像
仔细看看encSecKey所在的这个function
(function() {
var c7f = NEJ.P
, eq9h = c7f("nej.g")
, v7o = c7f("nej.j")
, k7d = c7f("nej.u")
, Ua2x = c7f("nm.x.ek")
, l7e = c7f("nm.x");
if (v7o.bk8c.redefine)
return;
window.GEnc = true;
var brX9O = function(crL1x) {
var m7f = [];
k7d.bd7W(crL1x, function(crK1x) {
m7f.push(Ua2x.emj[crK1x])
});
return m7f.join("")
};
var crH1x = v7o.bk8c;
v7o.bk8c = function(Y7R, e7d) {
var i7b = {}
, e7d = NEJ.X({}, e7d)
, lL1x = Y7R.indexOf("?");
if (window.GEnc && /(^|\.com)\/api/.test(Y7R) && !(e7d.headers && e7d.headers[eq9h.yH5M] == eq9h.Ht7m) && !e7d.noEnc) {
if (lL1x != -1) {
i7b = k7d.hf0x(Y7R.substring(lL1x + 1));
Y7R = Y7R.substring(0, lL1x)
}
if (e7d.query) {
i7b = NEJ.X(i7b, k7d.fJ9A(e7d.query) ? k7d.hf0x(e7d.query) : e7d.query)
}
if (e7d.data) {
i7b = NEJ.X(i7b, k7d.fJ9A(e7d.data) ? k7d.hf0x(e7d.data) : e7d.data)
}
i7b["csrf_token"] = v7o.gI0x("__csrf");
Y7R = Y7R.replace("api", "weapi");
e7d.method = "post";
delete e7d.query;
var bRB5G = window.asrsea(JSON.stringify(i7b), brX9O(["流泪", "强"]), brX9O(Ua2x.md), brX9O(["爱心", "女孩", "惊恐", "大笑"]));
e7d.data = k7d.cz8r({
params: bRB5G.encText,
encSecKey: bRB5G.encSecKey
})
}
crH1x(Y7R, e7d)
}
;
v7o.bk8c.redefine = true
}
)();
一个关键的语句是
window.asrsea(JSON.stringify(i7b), brX9O(["流泪", "强"]), brX9O(Ua2x.md), brX9O(["爱心", "女孩", "惊恐", "大笑"]));
这个brX90在这可能是一个拼接字符串的作用, 因为这个函数里面有push和join这两个关键字
window.asrsea的定义不在这里面, 搜索"window.asrsea", 如下图
到现在我们可以确定, window.asrsea就是我们需要的东西, 是大boss!
仔细看看window.asrsea所在的函数
!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
}();
函数d实际是去调用两个加密函数(AES加密函数b和RSA加密函数c)对传入的参数进行加密, 进行分析, 大致逻辑如下:
- 调用函数a生成一个16位随机数i;
- 调用函数b, 加密d(后文可知这是四个参数值唯一可变的), 得到encText
- 调用函数b, 加密步骤2得到的encText, 得到新的encText
- 调用函数c, 得到encSecKey
这样就得到api(http://music.163.com/weapi/cloudvideo/playurl)中所需要的params(encText)和encSecKey(encSecKey)了
断点分析
在上图中的红框处的 var h = {} 也即12828行, 点击行号设置一个断点, 在右侧的Watch窗口新建一个观察, 填入"window.asrsea", 如下图
然后, 刷新页面, 如下图, 我们就获得了window.asrsea中所需的3个固定参数(图中的参数1, 2, 3), 但是图中参数0到底怎样的还需要商榷
看回Network, 如下图, 点击XHR进行一下过滤, 没发现我们需要的东西
现在我们继续执行调试, 可以按F8, 也可以点击下图红框处
看回Source和Network
虽然Source中的Watch没怎么变, 但是在这里我们可以确定图中的参数1, 2, 3都是定值. 从Network可以看出第一次的window.asrsea值是给 http://music.163.com/weapi/cdns 所用的.
重复上述过程, 每次都要去Network看看网络请求, 不要点击太快, 不然又要重来一次调试, 可以把每次Source都截图下来, 方便再次查看.
下图是最后一次调试的Network, 到这就要小心了, 不要继续调试了
下图是Source界面, 把鼠标放在图中参数0的位置, 以防看漏了参数
继续调试, 页面正常运行, 看回Network, 如下图
据此, 可以肯定api(http://music.163.com/weapi/cloudvideo/playurl)需要4个参数, 其中后三个参数是定值, 第一个参数由ids, resolution, csrf_token组成
0: {"ids":"[\"5B0AF067CBB42F7789F7B97E13827565\"]","resolution":"720","csrf_token":""}
1: 010001
2: 00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7
3: 0CoJUm6Qyw8W8jud
实现加密
在 网易云音乐新登录API分析 一文中, 作者对加密进行了实现, 作者的最新代码在 encrypt.py 中可查看