需求:

中国空气质量在线监测分析平台是一个收录全国各大城市天气数据的网站,包括温度、湿度、PM 2.5、AQI 等数据,链接为:https://www.aqistudy.cn/html/city_detail.html,网站显示为:

一连串的分析

该网站所有的空气质量数据都是基于图表进行显示的,并且都是触发鼠标滑动或者点动后才会显示某点的数据,所以如果基于selenium进行数据爬取很吃力,因此考虑采用requests模块进行数据爬取.

首先要找到空气质量数据所在的数据包:

使用抓包工具抓取,经过尝试发现,只有设置的图中设置项发生了变化,然后点击查询按钮,在抓包工具中才会捕获到两个ajax请求的数据包,判断需要爬取的数据存在于该数据包中.

通过这两个 aqistudyapi.php 数据包发现,ajax请求的url为 https://www.aqistudy.cn/apinew/aqistudyapi.php ,请求方式为POST,两个ajax请求均发送了一个参数(d),但是其对应的值让我看不懂,并且两个的值不同,考虑是两次提交的参数不相同,并且进行了加密.

接着去看ajax请求的响应数据,发现也是一串加密后的字符串

那么问题来了,ajax请求提交的数据和返回的响应数据都是经过加密的

  1. 如何生成加密的请求数据?
  2. 在获取响应数据后如何解密?

解决:

现在已经直到,两个aqistudyapi.php的ajax请求都是在修改设置并且点击查询按钮后触发的,也就是说查询按钮上一定绑定了某个点击事件,由这个点击事件发送了对应的ajax请求,首先通过浏览器查看该查询按钮绑定的事件栈

点击到事件对应的js代码,发现事件执行了getData()函数

接下来,搜索getData,找到该函数的js代码

接下来,搜索getAQIData函数,发现getWeatherData函数就在其下方,真是贴心

经过分析后发现,两个函数都调用了getServerData函数,并且传入了method,type,函数,0.5四个参数,此时思考一下,既然没有其余的代码,那么ajax请求是如何发送的呢,显而易见是由getServerData函数发送的,并且传入的函数看起来像是对obj.data进行了一些列的分析展示

那么,让我们去找一下getServerData函数,首先局部搜索发现没有这个函数,不碍事,让我们来全局搜索一下看看

JavaScript 混淆: 我们会惊讶的发现getServerData后面跟的是什么鬼啊?不符合js函数定义的写法呀!看不懂呀!其实这里是经过 JavaScript 混淆加密了,混淆加密之后,代码将变为人不可读的形式,但是功能是完全一致的,这是一种常见的 JavaScript 加密手段.我们想要查看到该方法的原始实现则必须对其进行反混淆.

JavaScript 反混淆: JavaScript 混淆之后,其实是有反混淆方法的,最简单的方法便是搜索在线反混淆网站,例如 http://www.bm8.com.cn/jsConfusion/ ,将getServerData存在的这行数据粘贴到反混淆网站中

来分析一下getServerData函数,发现它判断当前页面数据后,发起了一个ajax请求(终于找到了!),调用getParam函数,传入了两个参数(method和object),将getParam函数的返回值赋值给param,并且作为ajax请求的参数(d)对应的值发送到了../apinew/aqistudyapi.php,我们看到的乱码一样的参数就是这个函数产生的.

接下来,使用decodeData函数对响应数据进行了解密,然后使用json反序列化,将反序列化的结果赋值给obj,然后调用之前传入的callback进行分析

回想一下getServerData有四个参数分别是:

  • method: GETDETAIL或 GETCITYWEATHER

  • object: param对象,是个字典,有四个属性

    • city: 明显是城市
    • type: 通过标签定位发现是主页右上角的radio框的值,分别为HOUR,DAY,MOUTH
    • startTime: 起始时间
    • endTime: 终止时间
  • callback: 回调函数,用于分析解密后的ajax请求响应

  • period: 0.5

接下来分别找一下getParam和decodeData函数

function decodeData(data) {
data = AES.decrypt(data, aes_server_key, aes_server_iv);
data = DES.decrypt(data, des_key, des_iv);
data = BASE64.decrypt(data);
return data
} // 发现decodeData的内部分别使用了AES,DES,BASE64对响应数据进行解密
var getParam = (function () {
function ObjectSort(obj) {
var newObject = {};
Object.keys(obj).sort().map(function (key) {
newObject[key] = obj[key]
});
return newObject
}
return function (method, obj) {
var appId = '1a45f75b824b2dc628d5955356b5ef18';
var clienttype = 'WEB';
var timestamp = new Date().getTime();
var param = {
appId: appId,
method: method,
timestamp: timestamp,
clienttype: clienttype,
object: obj,
secret: hex_md5(appId + method + timestamp + clienttype + JSON.stringify(ObjectSort(obj)))
};
param = BASE64.encrypt(JSON.stringify(param));
return AES.encrypt(param, aes_client_key, aes_client_iv)
}
})();
// getParam的内部时使用了BASE64和AES对请求数据进行了加密

总结:

点击查询按钮后,触发的事件最终执行了 getServerData 函数,发起了ajax请求,请求的参数是通过getParam(method,object)进行加密的,响应的数据是通过decodeData(data)进行解密的.

PyExecJS 模块

接下来,需要借助于 PyExecJS模块来实现模拟JavaScript代码执行,获取动态加密的请求参数,然后再将加密的响应数据传入decodeData进行解密

PyExecJS介绍: PyExecJS 是一个可以使用 Python 来模拟运行 JavaScript 的库.

环境安装:

  • pip install PyExecJS
  • 安装nodejs的开发环境

接下来,然我们一步一步的来实现

获取ajax请求的动态变化且加密的请求参数

  • 将反混淆网站中的代码粘贴到code.js文件中

  • 在该js文件中添加一个自定义函数getPostParamCode,该函数是为了获取且返回post请求的动态加密参数

function getPostParamCode(method, city, type, startTime, endTime){
// 封装getParam函数所需的参数
var param = {};
param.city = city;
param.type = type;
param.startTime = startTime;
param.endTime = endTime;
return getParam(method, param);
}
import execjs
# 实例化一个对象
node = execjs.get() # 创建参数
method = 'GETCITYWEATHER'
city = '北京'
c_type = 'HOUR'
start_time = '2019-10-09 00:00:00'
end_time = '2019-10-11 23:00:00' # 编译需要的js代码
file_path = './code.js'
js_obj = node.compile(open(file_path, encoding='utf-8').read())
# 获取加密的参数
js_code = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, c_type, start_time, end_time)
params = js_obj.eval(js_code)
print(params)

携带加密后的数据发送请求

import execjs
import requests
# 实例化一个对象
node = execjs.get() # 创建参数
method = 'GETCITYWEATHER'
city = '北京'
c_type = 'HOUR'
start_time = '2019-10-09 00:00:00'
end_time = '2019-10-11 23:00:00' # 编译需要的js代码
file_path = './code.js'
js_obj = node.compile(open(file_path, encoding='utf-8').read())
# 获取加密的参数
js_code = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, c_type, start_time, end_time)
params = js_obj.eval(js_code) # 发送请求
url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php'
response_text = requests.post(url, data={'d': params}).text
print(response_text)

对响应数据进行解密

import execjs
import requests
# 实例化一个对象
node = execjs.get() # 创建参数
method = 'GETCITYWEATHER'
city = '北京'
c_type = 'HOUR'
start_time = '2019-10-09 00:00:00'
end_time = '2019-10-11 23:00:00' # 编译需要的js代码
file_path = './code.js'
js_obj = node.compile(open(file_path, encoding='utf-8').read())
# 获取加密的参数
js_code = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, c_type, start_time, end_time)
params = js_obj.eval(js_code) # 发送请求,获取响应数据
url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php'
response_text = requests.post(url, data={'d': params}).text # 对响应数据进行解密
js_decode_data = 'decodeData("{}")'.format(response_text)
decode_data = js_obj.eval(js_decode_data)
print(decode_data)

至此,完成了对空气质量数据的爬取.

爬虫之Js混淆&加密案例的更多相关文章

  1. js混淆加密,通过混淆Js代码让别人(很难)无法还原

    js混淆加密,通过混淆Js代码让别人(很难)无法还原   使用js的混淆加密,其目的是为了保护我们的前端代码逻辑,对应一些搞技术吃饭的公司来说,为了防止被竞争对手抓取或使用自己的代码,就会考虑如何加密 ...

  2. 爬虫应对js混淆的方法

    大家做爬虫可能经常要跟js打交道.如果积累一定的经验肯定会遇到eval(....);这种js,很多新人可能慌了,woc这怎么办??????? 下面楼主给大家介绍一种方法简单,有效. F12 在Cons ...

  3. JS 混淆加密

    http://www.javascriptobfuscator.com/Javascript-Obfuscator.aspx http://www.javascriptobfuscator.com/d ...

  4. 爬虫入门到放弃系列07:js混淆、eval加密、字体加密三大反爬技术

    前言 如果再说IP请求次数检测.验证码这种最常见的反爬虫技术,可能大家听得耳朵都出茧子了.当然,也有的同学写了了几天的爬虫,觉得爬虫太简单.没有啥挑战性.所以特地找了三个有一定难度的网站,希望可以有兴 ...

  5. 1秒破解 js packer 加密

    原文:1秒破解 js packer 加密 其实有点标题党了,不过大概就是这个意思. 进入正题, eval(function(p,a,c,k,e,d){e=function(c){return(c< ...

  6. 爬虫05 /js加密/js逆向、常用抓包工具、移动端数据爬取

    爬虫05 /js加密/js逆向.常用抓包工具.移动端数据爬取 目录 爬虫05 /js加密/js逆向.常用抓包工具.移动端数据爬取 1. js加密.js逆向:案例1 2. js加密.js逆向:案例2 3 ...

  7. Python爬虫—破解JS加密的Cookie

    前言 在GitHub上维护了一个代理池的项目,代理来源是抓取一些免费的代理发布网站.上午有个小哥告诉我说有个代理抓取接口不能用了,返回状态521.抱着帮人解决问题的心态去跑了一遍代码.发现果真是这样. ...

  8. 中国空气质量在线监测分析平台之JS加密、JS混淆处理

    中国空气质量在线监测分析平台数据爬取分析 页面分析:确定url.请求方式.请求参数.响应数据 1.访问网站首页:https://www.aqistudy.cn/html/city_detail.htm ...

  9. 当爬虫遇到js加密

    当爬虫遇到js加密 我们在做python爬虫的时候经常会遇到许多的反爬措施,js加密就是其中一种. 破解js加密的方法也有很多种: 1.直接驱动浏览器抓取数据,无视js加密. 2.找到本地加密的js代 ...

随机推荐

  1. opencv windows源码编译

    WITH_QT//H:\software\programming\qt\5.12.3\mingw73_32\lib\cmake  5.6的路径要改这样 WITH_OPENGL 编译器mingw32-m ...

  2. 【leetcode】1031. Maximum Sum of Two Non-Overlapping Subarrays

    题目如下: Given an array A of non-negative integers, return the maximum sum of elements in two non-overl ...

  3. Telegraf根据配置文件启动(Influxdb的数据收集)

    1.创建一个telegraf.config文件 telegraf -sample-config -input-filter cpu:disk:diskio:net:system:mem -output ...

  4. 带有lazy标记的线段树

    #include<bits/stdc++.h> using namespace std; ]; struct st{ int l,r,val,add; }tr[]; void build( ...

  5. 【HDOJ6665】Calabash and Landlord(dfs)

    题意:二维平面上有两个框,问平面被分成了几个部分 x,y<=1e9 思路:分类讨论可以 但数据范围实在太小了,离散化以后随便dfs一下 #include<bits/stdc++.h> ...

  6. 破解Revealapp的试用时间限制

    转载自:http://jingwei6.me/2014/02/28/reveal_crack.html Revealapp作为分析iOS app UI结构的利器,还是非常称手的,89刀的价格也是物有所 ...

  7. oracle SQL in plsql

    刚安装好的oracle和plsql,以oracle11g为例 1.刚安装好后有两个默认的系统账号和初始密码:sys/change_on_install,system/manager 2.如果忘记了或不 ...

  8. execute、executeQuery和executeUpdate之间的区别 转

    转:http://blog.csdn.net/colin_fantasy/article/details/3898070 execute.executeQuery和executeUpdate之间的区别 ...

  9. “美登杯”上海市高校大学生程序设计 E. 小花梨的数组 (线段树)

    https://acm.ecnu.edu.cn/contest/173/problem/E/ 分析: 考虑这样一种情况,如果对一个点连续地做几次乘操作,那么之后紧跟着的除操作只需要将乘操作的次数减少即 ...

  10. CodeChef FNCS (分块+树状数组)

    题目:https://www.codechef.com/problems/FNCS 题解: 我们知道要求区间和的时候,我们用前缀和去优化.这里也是一样,我们要求第 l 个函数到第 r 个函数 [l, ...