js加密的数据爬取一直都是非常困难的,本来水平就不咋地,决定爬取网易云音乐的评论进行练习。花费了半天功夫才弄好,真的太难了,幸亏有大佬的参考https://www.zhihu.com/question/36081767/answer/386606315,不然根本不可能完成。这篇文章主要是将个解密的思想,当然每个步骤我都会详细说明,从分析api到爬取代码。学习到思路才是最重要的。
在这里我先讲一下js解密的思想,首先我们会找到加密的参数,然后通过全局搜寻找到相应的js文件,我们可以将js文件拿到本地进行更改,这样的js文件一般代码都是几万行起步,看的头皮发麻,我们通过IDE将代码整理,然后全局搜寻加密参数找到产生他的函数,如果这个函数可以独立运行,我们可以将它拿出来放到本地进行运行,分析结果。另一种是不可以独立运行,需要其他的函数依赖,参数依赖等,很难找到,我们可以通过fiddler进行js文件调换,本文主要用到第二种,废话不多说,开始整。
1 查看请求
通过chrome的f12,调取控制台进行查看是哪个请求得到评论,很简单的找到那个请求,如下图1
图1
我们看一下它的请求头,如图2,api为https://music.163.com/weapi/v1/resource/comments/R_SO_4_1294910785?csrf_token=
其中那个R_SO_....代表的是歌曲的ID表示.
图2
api为一个post请求,看一下它需要提交的参数,如图3,看到这个就知道是js加密了,直接搞起
图3
1.寻找js加密文件
直接crtl+shift+l,进行全局搜索找到了得出两个参数的js文件,如图4,这是一个在core.js文件。
图4
2.分析core.js
通过IDE进行字词搜寻,找到了产生两个参数的函数,这就是产生两个参数的位置。只要知道变量bSm5r就可以揭开谜底。
var bSm5r = window.asrsea(JSON.stringify(i5n), bry7r(["流泪", "强"]), bry7r(PI8A.md), bry7r(["爱心", "女孩", "惊恐", "大笑"]));
e5j.data = k5p.cB6v({params: bSm5r.encText, encSecKey: bSm5r.encSecKey})
分析这四个参数是个令人脑壳疼的事情,可能对于精通前端大佬的人来说是小菜一碟,这可咋办。。。。。。。。。。。根据开头我所说的两种方式,显然是第二种比较可靠,开始搞起。
打开Fiddler,进行js文件调换,将http请求的js文件转到请求本地的js文件,这样我们就可以知道这四个固定的值。
底部两行的内容,第一行写http请求的网址,第二个写你保存到本地的js文件,然后点击save,进行保存,将浏览器清除数据,重新请求,那么你会发现请求的js文件就是你本机js文件的内容,这样你想得到什么样的结果就可以得到啥结果了,嘻嘻嘻。
将四个参数分别输出,如下图,第一个参数就是讲变量类型转化为字符串,重新刷新浏览器,在console里查看结果
结果如图所示,结果里面会出现很多这种类型的数据,是因为多个网页会请求这个js文件,显然这个是比较靠谱的一个。
得到了四个参数
first={rid: "R_SO_4_1294910785", offset: "0", total: "true", limit: "20", csrf_token: ""}
second=010001
third=00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7
forth=0CoJUm6Qyw8W8jud
后面的三个值是固定的,,嘻嘻嘻,第一个很显然,一看就知道各个参数的意思,rid是歌曲id,然后limit是评论数,其他没啥意思。知道参数,然后开始分析函数。
想知道bSm5r还需要知道函数 window.asrsea ,再次进行查找,找到了asrsea是哪个函数,如图
,他就是函数function d(),根据wendow.asrsea函数含有四个参数进行判断,找到了
3 分析加密js
我们先一个参数一个参数的开始分析,函数的四个参数是调用函数时的四个变量,根据第二步骤里的代码可以发现这个参数很是奇葩,先看一下里面的内容。i = a(16) 通过查找function a() ,找到他就只是获得一个十六位的随机值,这个我们随意指定一个就行了。比如16个‘F’,如果没有猜错这个enctext就是param,encSeckey就是encSeckey,求出这个就行了。
encText需要函数b,查看函数b,得到下图,这是一个CBC模式的AES加密(AEC有许多模式,要注意别写错),偏移值为“0102030405060708”,我们要用pyhton进行加密模拟,之后会进行详细解释。
encText是一个加密了两次的值,我们再来分析encSeckey,他是函数c,三个参数都是固定的,所以这个值我们请求的时候找个就行了,没啥好分析。如图:
现在已经分析出了所以然了,开始python代码
首先需要安装pycrypto这个库,因为这个库已经三四年没有维护,而且如果你不用vs编程的话,安装还需要设置很多东西,我就因为这个浪费很多时间,现在官方使用pycryptodome这个库。最开始那个知乎上大佬应该是pycrypto,我用的是pycryptodome库,如果遇到安装问题自行百度google。
加密的代码,我也是从知乎大佬的代码进行更改了一下,因为安装的库文件的不同,嘻嘻嘻
# coding = utf-8
from Crypto.Cipher import AES
import base64
first_param = '{rid:"", offset:"0", total:"true", limit:"80", csrf_token:""}'
second_param = "010001"
third_param = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
forth_param = "0CoJUm6Qyw8W8jud"
def get_params():
iv = '0102030405060708'
first_key = forth_param
second_key = 16 * 'F'
h_encText = AES_encrypt(first_param, first_key, iv)
h_encText = AES_encrypt(h_encText, second_key, iv)
return h_encText
def get_encSecKey():
encSecKey = "257348aecb5e556c066de214e531faadd1c55d814f9be95fd06d6bff9f4c7a41f831f6394d5a3fd2e3881736d94a02ca919d952872e7d0a50ebfa1769a7a62d512f5f1ca21aec60bc3819a9c3ffca5eca9a0dba6d6f7249b06f5965ecfff3695b54e1c28f3f624750ed39e7de08fc8493242e26dbc4484a01c76f739e135637c"
return encSecKey
def AES_encrypt(text, key, iv):
if type(text)==type(b'123'): #这是判断当前变量的类型是bytes还是字符串,因为pycryptodome要
#求参数要是字节类型
text=text.decode('utf-8')
pad = 16 - len(text) % 16 #了解这个加密算法的人应该知道,加密的位数是16的位数,不够的话
#进行不上,我也不了解这个算法,有兴趣的可以上网查查
text = text + pad * chr(pad)
iv=iv.encode('utf-8')
key=key.encode('utf-8')
encryptor = AES.new(key, AES.MODE_CBC, iv)
text=text.encode('utf-8')
encrypt_text = encryptor.encrypt(text)
encrypt_text = base64.b64encode(encrypt_text)
return encrypt_text
if __name__ == "__main__":
params = get_params();
encSecKey = get_encSecKey();
print((params).decode('utf-8'))
print(encSecKey)
爬取网易云评论的代码,这个代码很是简陋,毕竟这主要讲的是破解js加密,至于内容你们可以自己进行扩展。
# coding = utf-8
from Crypto.Cipher import AES
import base64
import requests
import json
first_param = '{rid:"", offset:"0", total:"true", limit:"80", csrf_token:""}'
second_param = "010001"
third_param = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
forth_param = "0CoJUm6Qyw8W8jud"
headers = {
'Cookie': 'appver=1.5.0.75771;',
'Accept-Language':"zh-CN,zh;q=0.9,en;q=0.8",
'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36',
'Referer': 'http://music.163.com/'
}
def get_params():
iv = '0102030405060708'
first_key = forth_param
second_key = 16 * 'F'
h_encText = AES_encrypt(first_param, first_key, iv)
h_encText = AES_encrypt(h_encText, second_key, iv)
return h_encText
def get_encSecKey():
encSecKey = "257348aecb5e556c066de214e531faadd1c55d814f9be95fd06d6bff9f4c7a41f831f6394d5a3fd2e3881736d94a02ca919d952872e7d0a50ebfa1769a7a62d512f5f1ca21aec60bc3819a9c3ffca5eca9a0dba6d6f7249b06f5965ecfff3695b54e1c28f3f624750ed39e7de08fc8493242e26dbc4484a01c76f739e135637c"
return encSecKey
def AES_encrypt(text, key, iv):
if type(text)==type(b'123'):
text=text.decode('utf-8')
# text=text.decode('utf-8')
pad = 16 - len(text) % 16
text = text + pad * chr(pad)
iv=iv.encode('utf-8')
key=key.encode('utf-8')
encryptor = AES.new((key), AES.MODE_CBC, (iv))
text=text.encode('utf-8')
encrypt_text = encryptor.encrypt(text)
encrypt_text = base64.b64encode(encrypt_text)
return encrypt_text
def get_json(url, params, encSecKey):
data = {
"params": params,
"encSecKey": encSecKey
}
response = requests.post(url, headers=headers, data=data)
return response.content
if __name__ == "__main__":
url = "http://music.163.com/weapi/v1/resource/comments/R_SO_4_1294910785/?csrf_token="
params = get_params();
encSecKey = get_encSecKey();
print((params).decode('utf-8'))
print(encSecKey)
json_text = get_json(url, params, encSecKey)
json_dict = json.loads(json_text)
print (json_dict['total'])
i=0
for item in json_dict['comments']:
print (item['content'])
print(i)
i+=1
非常感谢知乎大佬写的https://www.zhihu.com/question/36081767/answer/386606315,毕竟没有这篇文章,我还是不知道该如何解密。希望各位读者也能分享自己学习心得的文章,让我们共同勉励自己。不当之处,欢迎各位指正,通过结合两个文章一起阅读更加,我写的要详细一些,可能表达的不是很好。