目录
声明:本文章案例只用于学习不用于商业用途。
这个案例是爬取某页面的评论信息,评论信息不在页面源代码并且请求链接的请求参数被加密了,这种情况该如何入手进行爬取呢?
由图可以看到,发到服务器上的数据被加密了,我们需要把它解密。
现在要做的是找加密代码所在的js脚本
点击启动器,里面是网页刷新时执行过的js脚本,最底下的是第一个执行的js脚本,我们选着最后一个也就是最上面这一个。
打开后选着美观输出,然后在标黄处设置断点,接着F5刷新页面,点击“继续运行脚本”,直到出现要找的链接。
这个是我们请求的网址,设置断点就是为了拦截这个网址,查看data的数据在哪个脚本开始被加密。
展开“调用堆栈”,选择第二个js脚本,查看d0x这个参数里的data,发现它的data也是被加密的,那么就要以此类推继续找到未加密的data所在的js脚本。
到了第5个js脚本,我们看到了参数Ci9Z里的data的内容不是加密的,那就说明了在第4个js脚本里进行了加密操作,接着我们回到第4个js脚本。
在第4个脚本的断点处data已被加密,说明了加密操作很可能在红色方框2的函数里(已确定了加密代码所在的js脚本)。在方框3中可以看到i0x这个参数里的内容是第5个脚本里data的内容,应该就是要被加密的参数,但是还需要进一步的证明。
这一步开始是找哪行代码开始加密
接着依然在第4个脚本里操作,取消旧的断点,在加密方法的第二行设置新的断点。F5刷新网页,点击“继续运行脚本”,直到出现要找的链接。找到之后就点“跳过下一个函数调用”(4),观察data的数据是否被加密,若没有就继续按(4)。
执行到这行代码时看到了params和encSeckey的加密数据,也就说明了在上一行代码里加密了数据,观察图二中的方框1里面的代码可推断加密的数据应该就是图2中的方框2里的内容(已找到了加密代码和要被加密的数据)。注意此时的data数据还没有被加密。我们继续执行一下看看。
到这行代码发现了它把加密的数据添加到了data的数据里面,所有我们要找的params其实是encText,encSeckey就是encSeckey。
补齐加密参数
我们搜索查找 window.asrsea,发现d赋值给了window.asrsea。我们找到了d方法,发现它的参数有d、e、f、g,我们下一步就是把它的参数拿到手。
因为 window.asrsea = d 所以我们看回这段代码:
var bUM2x = window.asrsea(JSON.stringify(i0x), bsG5L(["流泪", "强"]), bsG5L(WW8O.md), bsG5L(["爱心", "女孩", "惊恐", "大笑"]));
- 参数d:可以看到参数d,就是i0x,i0x就是那段要被加密的数据:
data = {
"csrf_token": "",
"cursor": "-1",
"offset": "0",
"orderType": "1",
"pageNo": "1",
"pageSize": "20",
"rid": "R_SO_4_1381414166",
"threadId": "R_SO_4_1381414166"
}
- 参数e:复制 bsG5L(["流泪", "强"]) 到控制台并发送。控制台只返回010001。所以参数e是个定值 010001。
- 参数f:参数f和参数e一样,复制 bsG5L(WW8O.md) 到控制台并发送。控制台只返回一段很长的数据。
00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7
- 参数g:以此类推,得到的数据是 0CoJUm6Qyw8W8jud。
看看加密代码
function a(a) {
var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
for (d = 0; a > d; d += 1) #循环16次
e = Math.random() * b.length, #生成随机数
e = Math.floor(e), #取整
c += b.charAt(e); #取字符串b中某个位置的字符
return c #返回16个随机字符的字符串
}
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) { #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); #i为一个16个字符的字符串
return h.encText = b(d, g),
h.encText = b(h.encText, i), #返回的就是params
h.encSecKey = c(i, e, f), #返回的就是encSecKey
h
}
拿到encSecKey的数据
方法d在python中可以理解为:
h.encText = b(d, g),
h.encText = b(h.encText, i), #返回的就是params
h.encSecKey = c(i, e, f), #e,f是定死的,如果我们把i也定死,那c也是定死的。
return h
由于方法c加密破解比较难,所以只能把i作为定值来处理,这样c返回的数就是定值。
在1处设置断点,按F5刷新,观察2处的参数是否为参数d的数据,如果是就可以复制3处的参数i的数据了。
在1处设置断点,点击2处继续执行代码,在3处拿到参数encSecKey的数据。
拿到params的数据
function b(a, b) {
var c = CryptoJS.enc.Utf8.parse(b) #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 #CBC模式加密
});
return f.toString()
}
function d(d, e, f, g) {
var h = {}
, i = a(16); #i为一个16个字符的字符串
return h.encText = b(d, g),
h.encText = b(h.encText, i), #返回的就是params
h.encSecKey = c(i, e, f), #返回的就是encSecKey
h
}
可以看到encText进行了两次加密,第一次加密是调用方法b,传入的参数是参数d(定值)和参数g(定值),第二次加密依然调用了方法b,传入了方法b返回的结果和参数i(定值)。方法b其实是进行了CBC加密,在Python中也有实现该功能的模块。进行了两次加密的结果就是params的值了。
在 Python中实现爬取
import requests
from Crypto.Cipher import AES
from base64 import b64encode
import json
url = "https://music.163.com/weapi/comment/resource/comments/get?csrf_token="
data = {
"csrf_token": "",
"cursor": "-1",
"offset": "0",
"orderType": "1",
"pageNo": "1",
"pageSize": "20",
"rid": "R_SO_4_1381414166",
"threadId": "R_SO_4_1381414166"
}
e = '010001'
f = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
g = '0CoJUm6Qyw8W8jud'
i ='0z11pIXbegqpbqmU'
def get_encSecKey():
return "06fafa18bb4ea2047a7d868306967a350133076f895ffe42a577cea9c60290a070dfeb3bfffc2cdd0032f2581f6d31831bf2c4d229a5584ed7c8a92bec7835d5efcabba6318e270535ac5fceb74f46c7984251a4d69886b18a35534f543e607bb60a653706d5bb31826892dbb9c9346837d3a5f726fba7bee3e86ab83a8dfb4c"
#以上都是参数
#进行一个保证data的长度是16的倍数的算法
def toString16(data):
pad = 16 - len(data) %16
data += chr(pad) * pad
return data
#进行两次加密
def get_params(data):#设data默认是字符串
first = enc_params(data, g)
second = enc_params(first, i)
return second
#加密过程
def enc_params(data, key):
iv = "0102030405060708"
data = toString16(data)
aes = AES.new(key=key.encode("utf-8"), IV=iv.encode("utf-8"), mode=AES.MODE_CBC) #创建加密器
bs = aes.encrypt(data.encode("utf-8")) #加密,加密的内容的长度必须是16的倍数
return str(b64encode(bs), "utf-8") #转化成字符串返回
#发送请求
resp = requests.post(url,data={
"params": get_params(json.dumps(data)),#把字典转化成json字符串
"encSecKey":get_encSecKey()
})
print(resp.text)
输出结果:
成功爬取到了评论。