本次实例的目标是某网站的气象数据,相对来说比较复杂,它涉及到js的混淆,反解以及py摸拟执行js。其实上本次爬取的代码并不多,但是分析过程相当复杂,稍有一点思路不清就会拿不到结果。
本次获取结果的url:https://www.aqistudy.cn/html/city_detail.html
下面是分析过程:
• 点击不同气象指标的选项卡,发现没有相关的请求发送,说明当页面加载出来的时候,所有的气象数据已经加载完毕。
• .筛选条件没有变更时,点击搜索没有发送相关的请求。
说明网页打开时,数据已经加载完毕。
• 再判断数据是否为动态加载
我们向https://www.aqistudy.cn/html/city_detail.html发起请求,在数据包中发现第一个请求即是:
通过preview看看返回结果:发现里面并没有数据,意味着数据并不在我们发送请求的url对就的数据包中,说明数据是动态加载的。
一般情况下,动态加载的数据是通过ajax请求的,但我们在抓包XHR中发现并没有ajax请求.那我们尝试修改条件,看看是否发起新的请求。
1.修改时间段,点搜索有请求
2.修改城市,有两个ajax请求,请求的url相同
分析这两个数据包,发现请求url相同,但请求都有参数d,但值不同,且动态变化。
处理动态数据d:
-- 全局搜索d,不可行
-- 但,在我们在点搜索按钮后有请求发起,因此点击事件发起了请求。
-- 找click事件
借助firefox inspect element
click绑定了一个函数
分析getData这个函数的js实现:目的就是为了找到ajax请求对应的js代码
通过firefox开发者工具找到js代码,搜索getData函数。
function getData()
{
state = 0;
city=$('#city').val();
$.cookie('dcity', city, {expires : 30});
//type = $('#type').combobox('getValue');
type = $('input:radio[name=type]:checked').val();
getTimeSel();
if(type=="HOUR")
{
var timediff = converTimeFormat($('#dtbEndTime').datetimebox('getValue')).getTime()-converTimeFormat($('#dtbStartTime').datetimebox('getValue')).getTime();
if(timediff >30*24*3600*1000)
{
showMessage(false,"按小时查询仅支持查询一个月数据,查看长时间变化趋势请选择按日查询!");
return ;
}
}
getAQIData();
getWeatherData();
}
分析函数的实现:因为它发起的请求一定涉及到请求参数。
这个函数内部并没有发现ajax请求,但有两个函数:getAQIData, getWeatherData
分析这两个函数实现:
实现的区别,(如果函数定义则没有分号,如果是调用则有分号)
两个函数的区别:
不同之处:
• method变量赋值字符串不一样,分别是:GETDETAIL, GETCITYWEATHER
相同之处:
• 都没有ajax请求对应的代码,但是有getserverData函数
• method:GETDETAIL或者GETCITYWEATHER
• param:字典,有四组键值对
○ city:查询城市的名称
○ type:HOUR
○ startTime:查询开始的时间
○ endTIme:查询结束的时间
分析getServerData(method,param,匿名函数,0.5)这个函数的实现,还是为了找打ajax请求对应的代码:
在本js中只发现了两次getServerData的调用。没有定义。全局搜索在一个叫:jquery-1.8.0.min.js的文件中发现了
但是代码经过混淆,完全看不懂
对代码进行反混淆,
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)
}
}
可以看到参数d: param, 而param 又是通过geParam得到
getParam(method, object)
method:method
object: param字典,四个键值分别是城市名,type, 起始时间,结束时间
请求到的密文数据的解密方式:
decodeData(data):data参数就是响应的密文数据,返回值就是解密后的原文数据
js逆向
• 使用 PyExecJS 库来实现模拟JavaScript代码执行。
• 环境的安装:
• pip install PyExecJS
• 必须还要安装nodeJs的开发环境
将反混淆的所有代码复制到一个js文件中,
这里命名为encoded.js
同时我们封装一个函数,并把它加到encode.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);
}
最终代码:
import execjs
import requests
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'
}
node = execjs.get()
file = 'encoded.js'
ctx = node.compile(open(file,encoding='utf-8').read()) # 固定用法
#这五个变量会作为getPostParamCode的参数
method = 'GETCITYWEATHER'#GETCITYWEATHER
city = '北京'
type = 'HOUR'
start_time = '2018-01-25 00:00:00'
end_time = '2018-01-25 23:00:00'
#模拟执行getPostParamCode函数, 因为ctx.eval()的参数只能是字符串,所以这里拼接字符串
js = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, type, start_time, end_time)
params = ctx.eval(js)
# print(params)#请求参数d
url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php'
data = {
'd':params
}
#获取了加密的响应数据
response_code = requests.post(url=url,headers=headers,data=data).text
# response_code
#模拟执行decodeData函数对密文数据进行解密
js = 'decodeData("{0}")'.format(response_code)
page_text = ctx.eval(js)
print(page_text.encode('utf-8'))
总结分析步骤:
1.判断数据是如何加载的
动态加载
找到动态加载数据的url,必须找到发起的ajax请求,参数d
找到了:getAQIData(), getWeatherData()函数
没有 ajax请求
发现了getServerData() 函数
定位 getServerData()函数的定义
定位到jquery-1.8.0.min.js文件
定位到getServerData()的函数定义,但经过了混淆,看不懂:
反混淆
分析getServerData()函数
发现参数d来自函数 getParam
js逆向(PyExecJS)
摸拟执行js,发起请求,得到加密的数据,解析数据