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 = {city,type,starttime,endtime}

8.getServerData(method,param)的實現:

  • 可以基於谷歌瀏覽器的抓包工具進行全局搜索。

  • js混淆:服務器端會將一些比較重要的函數實現進行加密。

  • 解決方式:通過工具js反混淆:進行線上平台暴力破解。平台的url:http://www.bm8.com.cn/jsConfusion/

  • 破解后找到了通過getServerData函數的實現ajax請求代碼,

    且發現

    • 動態變化的請求參數:getParam(method, object)函數返回
      • 函數參數
        • method:GETDETAI
        • object:{city,type,starttime,endtime}
    • 加密的響應數據解密的函數: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混淆


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM