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 =

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)
            }
        }
    

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混淆
posted @ 2020-06-22 21:33  自己有自己的调调、  阅读(1378)  评论(0编辑  收藏  举报