中國空氣質量在線監測平台加密數據爬取


- 中國空氣質量在線監測分析平台是一個收錄全國各大城市天氣數據的網站,包括溫度、濕度、PM 2.5、AQI 等數據,鏈接為:https://www.aqistudy.cn/html/city_detail.html,網站顯示為:

該網站所有的空氣質量數據都是基於圖表進行顯示的,並且都是出發鼠標滑動或者點動后才會顯示某點的數據,所以如果基於selenium進行數據爬取也是挺吃力的,因此我們采用requests模塊進行數據爬取。

- 基於抓包工具展開分析:

  - 通過分析發現,只有在頁面中設置了查詢的城市名稱和時間范圍后,然后點擊查詢按鈕,在抓包工具中才會捕獲到一個ajax請求的數據包,我們想要爬取的數據也在該數據包中:

  

  然后點擊捕獲到的數據包后,發現當前ajax請求為post類型的請求,攜帶一個請求參數d,且該請求參數為加密之后的數據,並且響應中的響應數據也是經過加密后的密文數據。

  加密的請求參數:

  

  加密的響應數據:

  

- 問題:那么如果我們想要將空氣質量數據進行爬取,則需要對上述捕獲到的ajax數據包中的post請求對應的url攜帶請求參數進行請求發送,然后獲取對應的響應數據。但是請求參數是加密后的密文,響應數據也是加密后的密文。並且post請求參數對應的密文每次請求都是動態變化的,我們如何設置?就算能夠破解動態且加密的請求參數,那么我們請求到的密文形式的響應數據我們也無法使用啊,我們只能使用可視化的明文數據,也就是解密后的數據。所以,我們換個思路,擼起袖子,把數據加密給干了!

- 解決:

  - 我們以及知道,剛才我們捕獲到的ajax請求是通過點擊了頁面中設定”時間范圍“后的查詢按鈕后觸發的,也就是說該查詢按鈕上一定綁定了某個點擊事件且觸發了對應ajax請求發送的事件。那么接下來我們可以通過火狐瀏覽器去檢測該查詢按鈕上到底綁定了哪些事件且是否發起了ajax請求呢?火狐瀏覽器可以分析頁面某個元素的綁定事件以及定位到具體的代碼在哪一行。

  

  - 進入到點擊事件對應的頁面源碼中,發現果真是對搜索按鈕添加了一個點擊事件。

  

  - 接下來,我們需要分析getData函數的內部實現,在當前源文件中,搜索該方法進行定位,定位到了之后,通過簡短的分析,發現其內部是調用下圖選中的兩個方法進行數據的請求。

  

  - 接着分析這兩個方法內部的實現,該兩個方法就在getData實現的下方。再進一步分析發現這兩個方法都調用了 getServerData() 這個方法,並傳遞了 method、param 等參數,然后還有一個回調函數很明顯是對返回數據進行處理的,這說明 Ajax 請求就是由這個 getServerData()方法發起

  

 

  - 定位getServerData方法,查看其內部的具體實現。在谷歌瀏覽器中開啟抓包工具,然后對該網站的首頁https://www.aqistudy.cn/html/city_detail.html發起請求,捕獲所有的數據包,然后在所有的數據包中實現全局搜索,搜索該方法是存在哪個文件中的。

  

  - JavaScript 混淆:我們會驚訝的發現getServerData后面跟的是什么鬼啊?不符合js函數定義的寫法呀!!!其實這里是經過 JavaScript 混淆加密了,混淆加密之后,代碼將變為不可讀的形式,但是功能是完全一致的,這是一種常見的 JavaScript 加密手段。我們想要查看到該方法的原始實現則必須對其進行反混淆。

  - 反混淆:JavaScript 混淆之后,其實是有反混淆方法的,最簡單的方法便是搜索在線反混淆網站,這里提供一個:http://www.bm8.com.cn/jsConfusion/。我們可以將getServerData存在的這行數據粘貼到反混淆的網站中。

  

  - 分析:在反混淆后,我們很清晰的看到了ajax請求發送的實現。然后還看到了ajax對應post請求的動態加密請求參數的加密方法getParam(),並且將method和object作為了函數的參數。method和object是從getServerData函數的參數中獲取的,那么getServerData函數中的method和object表示的是什么呢?我們需要回過頭去查看getServerData函數的調用:

  

  發現method是固定形式字符串,object就是param是一個字典,里面存儲了三組鍵值對city表示查詢城市名稱,startTime和endTime為查詢起止時間,type表示為HOUR:

  

   至此getParam()函數中的兩個參數的表示含義我們已經清楚了。getParam函數的返回值就是ajax對應post請求的動態加密請求參數了,我們需要定位到其函數內部的實現,看看是如何對請求參數進行加密的。在getServerData中我們發現了ajax請求對應的操作代碼,其中還有一個非常重要的一步,就是ajax請求成功后的回調函數實現內部,接受到了響應數據data,data我們知道是一組密文數據,然后調用了decodeData對data進行了解密操作:

  

  在反混淆網站的代碼中我們可以搜索到getPrame和decodeData這兩個函數的實現:

  

  發現getParam函數中使用了 Base64 和 AES 對param進行加密。加密之后的字符串便作為ajax對應post的請求參數傳送給服務器了。

 

  

  服務器相應回來的密文數據是被decodeData進行解密的。觀察解密函數發現是通過base64+AES+DES進行的解密!

- 總結:點擊查詢按鈕后,最終是觸發了getServerData函數發起了ajax請求,請求參數是通過getParam進行的加密,響應回來的密文數據是通過decodeData函數進行解密處理的。

 

- 接下來,我們需要借助於 PyExecJS 庫來實現模擬JavaScript代碼執行獲取動態加密的請求參數,然后再將加密的響應數據帶入decodeData進行解密即可!

  - PyExecJS介紹:PyExecJS 是一個可以使用 Python 來模擬運行 JavaScript 的庫。我們需要pip install PyExecJS對其進行環境安裝。

  - 開始執行js:

    - 1.將反混淆網站中的代碼粘貼到jsCode.js文件中

    - 2.在該js文件中添加一個自定義函數getPostParamCode,該函數是為了獲取且返回post請求的動態加密參數:

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

    - 3.在py源文件中可以基於PyExecJS模擬執行步驟2中定義好的自定義函數,獲取動態加密參數:    

import execjs
 

node = execjs.get()
 
# Params
method = 'GETCITYWEATHER'
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)

    - 4.接着我們用 requests 庫來模擬 POST 請求:    

import execjs
import requests

node = execjs.get()
 
# Params
method = 'GETCITYWEATHER'
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).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)

    - 5.接下來我們再調用一下 JavaScript 中的 decodeData() 方法即可實現解密:

import execjs
import requests

node = execjs.get()
 
# Params
method = 'GETCITYWEATHER'
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).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)

    運行結果:

{"success":true,"errcode":0,"errmsg":"success","result":{"success":true,"data":{"total":24,"rows":[{"time":"2018-01-25 00:00:00","temp":"-7","humi":"35","wse":"1","wd":"\u4e1c\u5317\u98ce","tq":"\u6674"},{"time":"2018-01-25 01:00:00","temp":"-9","humi":"38","wse":"1","wd":"\u897f\u98ce","tq":"\u6674"},{"time":"2018-01-25 02:00:00","temp":"-10","humi":"40","wse":"1","wd":"\u4e1c\u5317\u98ce","tq":"\u6674"},{"time":"2018-01-25 03:00:00","temp":"-8","humi":"27","wse":"2","wd":"\u4e1c\u5317\u98ce","tq":"\u6674"},{"time":"2018-01-25 04:00:00","temp":"-8","humi":"26","wse":"2","wd":"\u4e1c\u98ce","tq":"\u6674"},{"time":"2018-01-25 05:00:00","temp":"-8","humi":"23","wse":"2","wd":"\u4e1c\u5317\u98ce","tq":"\u6674"},{"time":"2018-01-25 06:00:00","temp":"-9","humi":"27","wse":"2","wd":"\u4e1c\u5317\u98ce","tq":"\u591a\u4e91"},{"time":"2018-01-25 07:00:00","temp":"-9","humi":"24","wse":"2","wd":"\u4e1c\u5317\u98ce","tq":"\u591a\u4e91"},{"time":"2018-01-25 08:00:00","temp":"-9","humi":"25","wse":"2","wd":"\u4e1c\u98ce","tq":"\u6674\u8f6c\u591a\u4e91\u8f6c\u591a\u4e91\u95f4\u6674"},{"time":"2018-01-25 09:00:00","temp":"-8","humi":"21","wse":"3","wd":"\u4e1c\u5317\u98ce","tq":"\u6674\u8f6c\u591a\u4e91\u8f6c\u591a\u4e91\u95f4\u6674"},{"time":"2018-01-25 10:00:00","temp":"-7","humi":"19","wse":"3","wd":"\u4e1c\u5317\u98ce","tq":"\u6674\u8f6c\u591a\u4e91\u8f6c\u591a\u4e91\u95f4\u6674"},{"time":"2018-01-25 11:00:00","temp":"-6","humi":"18","wse":"3","wd":"\u4e1c\u5317\u98ce","tq":"\u591a\u4e91"},{"time":"2018-01-25 12:00:00","temp":"-6","humi":"17","wse":"3","wd":"\u4e1c\u5317\u98ce","tq":"\u591a\u4e91"},{"time":"2018-01-25 13:00:00","temp":"-5","humi":"17","wse":"2","wd":"\u4e1c\u5317\u98ce","tq":"\u591a\u4e91"},{"time":"2018-01-25 14:00:00","temp":"-5","humi":"16","wse":"2","wd":"\u4e1c\u98ce","tq":"\u591a\u4e91"},{"time":"2018-01-25 15:00:00","temp":"-5","humi":"15","wse":"2","wd":"\u5317\u98ce","tq":"\u591a\u4e91"},{"time":"2018-01-25 16:00:00","temp":"-5","humi":"16","wse":"2","wd":"\u4e1c\u5317\u98ce","tq":"\u591a\u4e91"},{"time":"2018-01-25 17:00:00","temp":"-5","humi":"16","wse":"2","wd":"\u4e1c\u98ce","tq":"\u591a\u4e91"},{"time":"2018-01-25 18:00:00","temp":"-6","humi":"18","wse":"2","wd":"\u4e1c\u98ce","tq":"\u6674\u95f4\u591a\u4e91"},{"time":"2018-01-25 19:00:00","temp":"-7","humi":"19","wse":"2","wd":"\u4e1c\u98ce","tq":"\u6674\u95f4\u591a\u4e91"},{"time":"2018-01-25 20:00:00","temp":"-7","humi":"19","wse":"1","wd":"\u4e1c\u98ce","tq":"\u6674\u95f4\u591a\u4e91"},{"time":"2018-01-25 21:00:00","temp":"-7","humi":"19","wse":"0","wd":"\u5357\u98ce","tq":"\u6674\u95f4\u591a\u4e91"},{"time":"2018-01-25 22:00:00","temp":"-7","humi":"22","wse":"1","wd":"\u4e1c\u5317\u98ce","tq":"\u6674\u95f4\u591a\u4e91"},{"time":"2018-01-25 23:00:00","temp":"-9","humi":"27","wse":"1","wd":"\u897f\u5357\u98ce","tq":"\u6674\u95f4\u591a\u4e91"}]}}}

 

完美結束!!!


免責聲明!

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



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