12、Python 高级反爬机制-破解js加密
1、案例需求:爬取空气质量数据
URL:https://www.aqistudy.cn/html/city_detail.html
2、分析思路:
1.页面中是有相关的查询条件,指定查询条件后点击查询按钮,就会加载出相关的数据。
- 查询的条件:
- 城市名称
- 查询的时间范围
- 当点击了查询按钮后,整张页面没有刷新,而是局部页面发生了刷新
- 说明:点击了查询按钮后,发起了一个ajax请求,该请求可以帮我们进行页面的局部刷新,且请求到符合查询条件的相关指标数据。
2.目的:想要获取点击查询按钮后加载出来的数据。将ajax请求到的数据获取即可。
-
可以通过抓包工具捕获到ajax请求的数据包,
从数据包中想要提取出:
- 请求url:https://www.aqistudy.cn/apinew/aqistudyapi.php
- 请求方式:post
- 请求参数:d,参数值是一组看不懂的字符串
- 是动态变化的请求参数(每次请求对应的请求参数的值都是不一样的)
- 响应数据:一组看不懂的字符串
- 响应一定是需要加载到前台页面进行显示,但是捕获到响应数据并不是前台页面加载出来的原文数据。说明请求到的响应数据一定是一组密文数据。
3.处理动态变化的请求参数
- 该请求是一个ajax请求,查看ajax请求对应的js代码或者Jquery代码中有没有请求参数的设置呢?
- 当点击了页面中的查询按钮后,就会发起一个ajax请求。说明该查询按钮一定被绑定了一个点击事件,当点击事件发生后就会执行一组ajax请求的代码。
- 通过查看ajax请求代码,就可以发现请求参数的参数值。
示例代码:
$.ajax({
url:"发送请求(提交或读取数据)的地址",
dataType:"预期服务器返回数据的类型",
type:"请求方式",
async:"true/false",
data:{id:func},
success:function(data){请求成功时执行},
error:function(){请求失败时执行}
});
4.寻找ajax请求的代码。
- 找到查询按钮绑定的点击事件。(火狐浏览器的开发者工具)
5.发现getData函数就是点击了查询按钮后触发的函数,该函数实现内容去查询ajax请求的代码
6.查看getData函数的实现:
跳转到指定的位置
局部搜索getData
-
type=="HOUR":查询条件是以小时为单位
-
没有发现ajax请求代码,但有另外的两个函数的调用,
-
说明ajax请求代码一定是存在于这两个函数实现中。
getAQIData();
getWeatherData();
7.查看getAQIData的实现:
- 未发现ajax请求代码,发现了另一个函数调用getServerData,
- getServerData参数:
- method = GETDETAIL
- param =
- getServerData参数:
8.getServerData(method,param)的实现:
-
可以基于谷歌浏览器的抓包工具进行全局搜索。
-
js混淆:服务器端会将一些比较重要的函数实现进行加密。
-
解决方式:通过工具js反混淆:进行线上平台暴力破解。平台的url:http://www.bm8.com.cn/jsConfusion/
-
破解后找到了通过getServerData函数的实现ajax请求代码,
且发现
- 动态变化的请求参数:getParam(method, object)函数返回
- 函数参数
- method:GETDETAI
- object:
- 函数参数
- 加密的响应数据解密的函数:decodeData(data)
- 参数data为加密的响应数据,返回值为解密后的原文数据
function getServerData(method, object, callback, period) { const key = hex_md5(method + JSON.stringify(object)); const data = getDataFromLocalStorage(key, period); if (!data) { var param = getParam(method, object); $.ajax({ url: '../apinew/aqistudyapi.php', data: { d: param }, type: "post", success: function (data) { data = decodeData(data); obj = JSON.parse(data); if (obj.success) { if (period > 0) { obj.result.time = new Date().getTime(); localStorageUtil.save(key, obj.result) } callback(obj.result) } else { console.log(obj.errcode, obj.errmsg) } } }) } else { callback(data) } }
- 动态变化的请求参数:getParam(method, object)函数返回
3、js逆向获取数据:
-
js逆向
- 可以将js代码改写成python代码进行相关的调用。
- 方式:
- 1.手动将js函数改写成python函数
- 2.可以使用工具自动进行python函数的改写
- pyexclJS
-
PyExecJS介绍:PyExecJS 是一个可以使用 Python 来模拟运行 JavaScript 的库。
-
需要环境安装
pip install PyExecJS
-
注意:如果想使用PyExecJS的话,在你的本机中必须安装好nodejs的环境。
将解密成功的js方法存放到本地jsCode.js并对其方法进行封装
function getPostParamCode(method, city, type, startTime, endTime){
var param = {};
param.city = city;
param.type = type;
param.startTime = startTime;
param.endTime = endTime;
return getParam(method, param);
}
#动态实时获取ajax请求的参数
import execjs
node = execjs.get()
# Params
method = 'GETDETAIL'
city = '北京'
type = 'HOUR'
start_time = '2018-01-25 00:00:00'
end_time = '2018-01-25 23:00:00'
# Compile javascript
file = 'jsCode.js'
ctx = node.compile(open(file,encoding='utf-8').read())
# Get params
js = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, type, start_time, end_time)
params = ctx.eval(js) #调用执行封装好的方法
print(params)
>>>
tdgHOYxwKdDSgYXe+RLPzYCgLvrddahasI5XXklB4gVLYqab+XRPpMD/oSqnJ/aEmFwzVEUhLnPzRy03+X1BIzLvxQKwu4A3YsqR3OemYgNnHqPdBwvJlbxia99YeK+xNLwdqFad2OO8kQ/eMmdXDnGMvVAdhy3hOdXSgMgwVdUjXSyKzDV31TAxmYlJqwB6U3oElEpwW7AG1sOS1EpGER7Q1a1xkekm9tvDAeHRXrPB1jXX4hsdnZoYBSE23ei+sBC/30MZXDD1ons7hnF4fNS7j0aSqyscRk5ueQAvN1FRHCg9aM9tClVrDd4dC9q5Tk8vlH8aiTmGBZjYRkdIina1REOBdr3z73I+8GTRintq9RjSTycygKb3IpNejPAtU+P4FwPOmhiTCf1pDl0GXOw23BHL/8yR0yWCSHwOH+EDUmV+oQKOwh7T84w7LGjaHB0hGrvW94R6bI5iC+Qsaw==
#携带上动态变化的请求参数发起ajax请求获取加密的响应数据
import execjs
node = execjs.get()
# Params
method = 'GETDETAIL'
city = '北京'
type = 'HOUR'
start_time = '2018-01-25 00:00:00'
end_time = '2018-01-25 23:00:00'
# Compile javascript
file = 'jsCode.js'
ctx = node.compile(open(file,encoding='utf-8').read())
# Get params
js = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, type, start_time, end_time)
params = ctx.eval(js) #调用执行封装好的方法
#发起post请求
url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php'
response_text = requests.post(url, data={'d': params}).text
print(response_text)
>>>
qZMXM1uw6YvIv1UWRplJEP8adQ/jrupTMOgOGHddu9sLczXIVdbUQNC6FKKO1n/E+u+ROZbS20IkqL9BxAlZGzas1Cr/5Xra0/8RJ4dgOSerFidYiI6gQTe2hR83SC2FtPOuHYS/0KmslfuTqyH21g==
import execjs
import requests
node = execjs.get()
# Params
method = 'GETDETAIL'
city = '北京'
type = 'HOUR'
start_time = '2018-01-25 00:00:00'
end_time = '2018-01-25 23:00:00'
# Compile javascript
file = 'jsCode.js'
ctx = node.compile(open(file,encoding='utf-8').read())
# Get params
js = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, type, start_time, end_time)
params = ctx.eval(js)
#发起post请求
url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php'
response_text = requests.post(url, data={'d': params}).text
#对加密的响应数据进行解密
js = 'decodeData("{0}")'.format(response_text)
decrypted_data = ctx.eval(js)
print(decrypted_data)
这篇博文涉及到的反爬机制有:
- js加密
- js逆向可以破解js加密
- 动态变化的请求参数
- js混淆