JS逆向分析+Python爬虫结合
特别声明📢:本教程只用于教学,大家在使用爬虫过程中需要遵守相关法律法规,否则后果自负!!!
- 完整代码地址Github:
https://github.com/ziyifast/ziyifast-code_instruction/tree/main/python-demo/spider-demo/spider-js
。 - 大家如果觉得还不错的话,欢迎star⭐️哦
1 概念
有时我们通过Python爬虫爬取数据会发现服务器返回的是密文数据,我们拿到密文之后,需要对它进行解密才能转换为可用数据。
- 前端对服务器返回的密文数据是通过JS来进行解密的,这时我们就需要分析并找到前端解密的代码,应用到我们的爬虫,达到爬虫抓取并解密数据的效果。
- 总结:JS逆向分析,大致就是我们 ①分析前端JS代码 + ②通过JS代码解密服务器返回的密文数据。
2 JS逆向分析
以烯牛平台数据为例,我们登录平台,访问目前国内最新赛道,服务发现服务器返回的都是加密后数据,这时我们就需要使用XHR断点,一步步进行逆向分析。
- 平台链接:https://www.xiniudata.com/industry/newest?from=data
思路分析 & 定位JS文件
- 可以看到服务器返回回来的数据在
d
字段中,这种命名太过常见,因此我们不能通过关键字定位搜索对应的js代码。 - 因为返回的是json数据,前端js肯定会进行解码,所以我们可以搜索
与JSON.parse()
有关的方法,找到对应解码位置。 - f12打开开发者工具,抓请求包,找到请求发起的js文件,点击进入
格式化JS代码 & 打XHR断点
- 点击进入js代码后,点击代码片段左下角的花括号,格式化js代码,同时根据上面的分析,搜索JSON.parse方法
- 可以看到搜索结果有11个,但此处我们应该找入参由他自定义解析出的数据,不要选成了由内置的函数解析的数据,如:l = JSON.stringfy(n),这种就该放过,不打断点。
- y由前端自定义的方法而来,我们应该在此处打断点,因为这里很可能是解码的js部分。
继续寻找,直到整个js文件搜索完成。最后我们在第30行、第38行为其打上断点。
刷新页面 & 移除无用断点
断点我们已经打好了,下面就需要我们刷新页面或者下滑,继续请求数据,以触发XHR断点。
- 刷新页面,触发断点
- 我们发现断点走在了第30行的位置,这样我们就可以把其他断点去掉
控制台 & 逆向分析JS
- 点击下一步,让程序往下执行一步,然后观察返回的数据是否是我们所需要的明文数据:
- 来到控制台打印m的值,观察是否是我们需要的数据
- 上面m发现是我们的个人信息,并非是我们所需要的数据,因此可直接跳过当前断点
- 点击resume跳过断点后,发现程序再次走到JS代码的第30行。此时我们单步往下走一行,让程序计算出m的值
- 控制台输入m,回车,观察控制台返回的是否是我们所需的数据
可以看到,成功解析出明文数据,表明断点出的这个方法就是前端的JS解密逻辑处。
定位前端代码 & 完善JS
- 新建逆向JS代码,并拷贝这段JS代码
var d = Object(u.a)(s)
, y = Object(u.b)(d)
, m = JSON.parse(y);
- 控制台输入对应函数名,获取对应函数详细信息
可以看到上面的JS代码,我们并不知道Object(u.a)是什么意思,那么我们就放行,单步执行断点,待断点执行到Object(u.a)之后,就可以从控制台获取其具体值
- 控制台获取函数具体值:Object(u.a),点击控制台返回结果,查看函数详情
- 拷贝函数到我们逆向的JS代码
我们逆向的JS代码就成了:
// 将u.a替换为了d1
var d = Object(d1)(s)
, y = Object(u.b)(d)
, m = JSON.parse(y);
function d1(e) {
var t, n, r, o, i, a, u = "", c = 0;
for (e = e.replace(/[^A-Za-z0-9\+\/\=]/g, ""); c < e.length;)
t = _keyStr.indexOf(e.charAt(c++)) << 2 | (o = _keyStr.indexOf(e.charAt(c++))) >> 4,
n = (15 & o) << 4 | (i = _keyStr.indexOf(e.charAt(c++))) >> 2,
r = (3 & i) << 6 | (a = _keyStr.indexOf(e.charAt(c++))),
u += String.fromCharCode(t),
64 != i && (u += String.fromCharCode(n)),
64 != a && (u += String.fromCharCode(r));
return u
}
重复此步骤,不断完善我们自己新建的JS代码,直到能正确解析出服务器返回的密文数据。
- 服务器返回的密文数据获取:
- 拷贝到自己的逆向JS代码,然后执行,观察是否能解析成功
- 执行JS代码,解析成功
3 JS+Python爬虫结合实战
curl自动生成Python爬虫
- 复制请求为curl
- 将curl转换为Python代码
网站链接:https://spidertools.cn/#/curl2Request
调用execjs执行我们编写的逆向JS
Python代码和js逆向代码都就绪了,剩下的就是我们通过execjs在Python代码中使用了。
全部python代码
demo01-JS逆向入门.py:
import requests
import json
# 执行命令安装:pip install pyExecJs
import execjs
headers = {
"accept": "application/json",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"content-type": "application/json",
"origin": "https://www.xiniudata.com",
"priority": "u=1, i",
"referer": "https://www.xiniudata.com/industry/newest?from=data",
"sec-ch-ua": "\"Chromium\";v=\"128\", \"Not;A=Brand\";v=\"24\", \"Microsoft Edge\";v=\"128\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0"
}
cookies = {
"btoken": "ZOH2JO8CQ3CUE1YH9PC3K6TG1Z3HCEAE",
"hy_data_2020_id": "192383e583c10e1-06189fe412f809-7e433c49-1484784-192383e583d26f6",
"hy_data_2020_js_sdk": "%7B%22distinct_id%22%3A%22192383e583c10e1-06189fe412f809-7e433c49-1484784-192383e583d26f6%22%2C%22site_id%22%3A211%2C%22user_company%22%3A105%2C%22props%22%3A%7B%7D%2C%22device_id%22%3A%22192383e583c10e1-06189fe412f809-7e433c49-1484784-192383e583d26f6%22%7D",
"Hm_lvt_42317524c1662a500d12d3784dbea0f8": "1727520463,1727571559",
"HMACCOUNT": "CA33EC3F1CCD3E5F",
"utoken": "Z1MNZUNI27IG3HYGMABLAIRRP67D1DF4",
"username": "Clay",
"Hm_lpvt_42317524c1662a500d12d3784dbea0f8": "1728100324"
}
url = "https://www.xiniudata.com/api2/service/x_service/person_industry_list/list_industries_by_sort"
data = {
"payload": "LBc3V0I6ZGB5bXsxTCQnPRBuDgQVcDhbICcmb2x3AjI=",
"sig": "45B0ECB73CAE7AEA531F5A39B29023A0",
"v": 1
}
data = json.dumps(data, separators=(',', ':'))
response = requests.post(url, headers=headers, cookies=cookies, data=data)
# 解析json响应,获取加密后数据
# print(response.json()["d"])
# 调用我们编写好的逆向JS函数,解密数据
decode_data = execjs.compile(open('./demo01.js', 'r', encoding='utf-8').read()).call('decode_data', response.json()["d"])
print(decode_data)
如果出现报错:execjs not found,执行下面命令安装:
pip install pyExecJs
全部逆向js代码
js解密代码,我们无需理解,直接拷贝即可
// s = '服务器返回的加密数据'
function decode_data(encode_data) {
var d = Object(d1)(encode_data)
, y = Object(d2)(d)
, m = JSON.parse(y);
rte = m
return rte
}
function d1(e) {
var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var t, n, r, o, i, a, u = "", c = 0;
for (e = e.replace(/[^A-Za-z0-9\+\/\=]/g, ""); c < e.length;)
t = _keyStr.indexOf(e.charAt(c++)) << 2 | (o = _keyStr.indexOf(e.charAt(c++))) >> 4,
n = (15 & o) << 4 | (i = _keyStr.indexOf(e.charAt(c++))) >> 2,
r = (3 & i) << 6 | (a = _keyStr.indexOf(e.charAt(c++))),
u += String.fromCharCode(t),
64 != i && (u += String.fromCharCode(n)),
64 != a && (u += String.fromCharCode(r));
return u
}
function d2(e) {
_p = "W5D80NFZHAYB8EUI2T649RT2MNRMVE2O";
for (var t = "", n = 0; n < e.length; n++) {
var r = _p.charCodeAt(n % _p.length);
t += String.fromCharCode(e.charCodeAt(n) ^ r)
}
return t = _u_d(t)
}
function _u_d(e) {
for (var t = "", n = 0, r = 0, o = 0, i = 0; n < e.length;)
(r = e.charCodeAt(n)) < 128 ? (t += String.fromCharCode(r),
n++) : r > 191 && r < 224 ? (o = e.charCodeAt(n + 1),
t += String.fromCharCode((31 & r) << 6 | 63 & o),
n += 2) : (o = e.charCodeAt(n + 1),
i = e.charCodeAt(n + 2),
t += String.fromCharCode((15 & r) << 12 | (63 & o) << 6 | 63 & i),
n += 3);
return t
}
验证
执行Python爬虫代码,完整能否正确抓取并解析数据
完整代码
Github:
https://github.com/ziyifast/ziyifast-code_instruction/tree/main/python-demo/spider-demo/spider-js