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}
- getServerData參數:
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) } }
- 動態變化的請求參數: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混淆