- 中国空气质量在线监测分析平台是一个收录全国各大城市天气数据的网站,包括温度、湿度、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).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":"","wse":"","wd":"\u4e1c\u5317\u98ce","tq":"\u6674"},{"time":"2018-01-25 01:00:00","temp":"-9","humi":"","wse":"","wd":"\u897f\u98ce","tq":"\u6674"},{"time":"2018-01-25 02:00:00","temp":"-10","humi":"","wse":"","wd":"\u4e1c\u5317\u98ce","tq":"\u6674"},{"time":"2018-01-25 03:00:00","temp":"-8","humi":"","wse":"","wd":"\u4e1c\u5317\u98ce","tq":"\u6674"},{"time":"2018-01-25 04:00:00","temp":"-8","humi":"","wse":"","wd":"\u4e1c\u98ce","tq":"\u6674"},{"time":"2018-01-25 05:00:00","temp":"-8","humi":"","wse":"","wd":"\u4e1c\u5317\u98ce","tq":"\u6674"},{"time":"2018-01-25 06:00:00","temp":"-9","humi":"","wse":"","wd":"\u4e1c\u5317\u98ce","tq":"\u591a\u4e91"},{"time":"2018-01-25 07:00:00","temp":"-9","humi":"","wse":"","wd":"\u4e1c\u5317\u98ce","tq":"\u591a\u4e91"},{"time":"2018-01-25 08:00:00","temp":"-9","humi":"","wse":"","wd":"\u4e1c\u98ce","tq":"\u6674\u8f6c\u591a\u4e91\u8f6c\u591a\u4e91\u95f4\u6674"},{"time":"2018-01-25 09:00:00","temp":"-8","humi":"","wse":"","wd":"\u4e1c\u5317\u98ce","tq":"\u6674\u8f6c\u591a\u4e91\u8f6c\u591a\u4e91\u95f4\u6674"},{"time":"2018-01-25 10:00:00","temp":"-7","humi":"","wse":"","wd":"\u4e1c\u5317\u98ce","tq":"\u6674\u8f6c\u591a\u4e91\u8f6c\u591a\u4e91\u95f4\u6674"},{"time":"2018-01-25 11:00:00","temp":"-6","humi":"","wse":"","wd":"\u4e1c\u5317\u98ce","tq":"\u591a\u4e91"},{"time":"2018-01-25 12:00:00","temp":"-6","humi":"","wse":"","wd":"\u4e1c\u5317\u98ce","tq":"\u591a\u4e91"},{"time":"2018-01-25 13:00:00","temp":"-5","humi":"","wse":"","wd":"\u4e1c\u5317\u98ce","tq":"\u591a\u4e91"},{"time":"2018-01-25 14:00:00","temp":"-5","humi":"","wse":"","wd":"\u4e1c\u98ce","tq":"\u591a\u4e91"},{"time":"2018-01-25 15:00:00","temp":"-5","humi":"","wse":"","wd":"\u5317\u98ce","tq":"\u591a\u4e91"},{"time":"2018-01-25 16:00:00","temp":"-5","humi":"","wse":"","wd":"\u4e1c\u5317\u98ce","tq":"\u591a\u4e91"},{"time":"2018-01-25 17:00:00","temp":"-5","humi":"","wse":"","wd":"\u4e1c\u98ce","tq":"\u591a\u4e91"},{"time":"2018-01-25 18:00:00","temp":"-6","humi":"","wse":"","wd":"\u4e1c\u98ce","tq":"\u6674\u95f4\u591a\u4e91"},{"time":"2018-01-25 19:00:00","temp":"-7","humi":"","wse":"","wd":"\u4e1c\u98ce","tq":"\u6674\u95f4\u591a\u4e91"},{"time":"2018-01-25 20:00:00","temp":"-7","humi":"","wse":"","wd":"\u4e1c\u98ce","tq":"\u6674\u95f4\u591a\u4e91"},{"time":"2018-01-25 21:00:00","temp":"-7","humi":"","wse":"","wd":"\u5357\u98ce","tq":"\u6674\u95f4\u591a\u4e91"},{"time":"2018-01-25 22:00:00","temp":"-7","humi":"","wse":"","wd":"\u4e1c\u5317\u98ce","tq":"\u6674\u95f4\u591a\u4e91"},{"time":"2018-01-25 23:00:00","temp":"-9","humi":"","wse":"","wd":"\u897f\u5357\u98ce","tq":"\u6674\u95f4\u591a\u4e91"}]}}}

完美结束!!!

js加密数据爬取的更多相关文章

  1. Node.js 爬虫爬取电影信息

    Node.js 爬虫爬取电影信息 我的CSDN地址:https://blog.csdn.net/weixin_45580251/article/details/107669713 爬取的是1905电影 ...

  2. python爬虫:了解JS加密爬取网易云音乐

    python爬虫:了解JS加密爬取网易云音乐 前言 大家好,我是"持之以恒_liu",之所以起这个名字,就是希望我自己无论做什么事,只要一开始选择了,那么就要坚持到底,不管结果如何 ...

  3. 养只爬虫当宠物(Node.js爬虫爬取58同城租房信息)

    先上一个源代码吧. https://github.com/answershuto/Rental 欢迎指导交流. 效果图 搭建Node.js环境及启动服务 安装node以及npm,用express模块启 ...

  4. node.js爬虫爬取拉勾网职位信息

    简介 用node.js写了一个简单的小爬虫,用来爬取拉勾网上的招聘信息,共爬取了北京.上海.广州.深圳.杭州.西安.成都7个城市的数据,分别以前端.PHP.java.c++.python.Androi ...

  5. Node.js爬虫-爬取慕课网课程信息

    第一次学习Node.js爬虫,所以这时一个简单的爬虫,Node.js的好处就是可以并发的执行 这个爬虫主要就是获取慕课网的课程信息,并把获得的信息存储到一个文件中,其中要用到cheerio库,它可以让 ...

  6. Python selenium+phantomjs的js动态爬取

    Selenium是一个用于Web应用程序测试的工具.Selenium测试直接运行在浏览器中,就像真正的用户在操作一样.支持的浏览器包括IE.Mozilla Firefox.Chrome等.Phanto ...

  7. 手把手教你用Node.js爬虫爬取网站数据

    个人网站 https://iiter.cn 程序员导航站 开业啦,欢迎各位观众姥爷赏脸参观,如有意见或建议希望能够不吝赐教! 开始之前请先确保自己安装了Node.js环境,还没有安装的的童鞋请自行百度 ...

  8. Python之手把手教你用JS逆向爬取网易云40万+评论并用stylecloud炫酷词云进行情感分析

    本文借鉴了@平胸小仙女的知乎回复 https://www.zhihu.com/question/36081767 写在前面: 文章有点长,操作有点复杂,需要代码的直接去文末即可.想要学习的需要有点耐心 ...

  9. node js 爬虫爬取静态页面,

    先打一个简单的通用框子 //根据爬取网页的协议 引入对应的协议, http||https var http = require('https'); //引入cheerio 简单点讲就是node中的jq ...

随机推荐

  1. js正则判断实现18位数字

    js实现18位数字 var stuCardReg = /^[0-9]{18}/; var stuCard = $('.inp).val();//获取input框中的数值 if (!stuCardReg ...

  2. H5调用百度地图导航

    template <div class="map"> <div class="content_flex"><img src=&qu ...

  3. CF 398 E(动态规划)

    传送门: http://codeforces.com/problemset/problem/398/E 题解: 首先答案不超过2. 最长环=1时,ans=0 最长环=2时,ans=1 否则,ans=2 ...

  4. java 接受带有中文的get请求文件下载时的问题

    参数是接受到了 , debug的时候也能看的到 , 但是奇怪的是就是找不到文件 @ApiOperation(value = "文件下载/图片预览") @GetMapping(val ...

  5. 帝国cms简介显示转义字符问题

    在模板中设置简介截取字数为0,前端显示用css控制即可 white-space: nowrap; overflow: hidden; text-overflow: ellipsis; 也可以 1,在后 ...

  6. bzoj1037题解

    [解题思路] DP.f[i][j][x][y]表示已选了i个♂和j个♀,其中♂比♀多x,♀比♂多y(x,y≥0). 递推式转移方程: (f[i+1][j][x+1][max(y-1,0)]+=f[i] ...

  7. 字符串匹配dp+bitset,滚动数组优化——hdu5745(经典)

    bitset的经典优化,即把可行性01数组的转移代价降低 bitset的适用情况,当内层状态只和外层状态的上一个状态相关,并且内层状态的相关距离是一个固定的数,可用bitset,换言之,能用滚动数组是 ...

  8. JQuery简单实用的模板引擎

    1.在html界面声明模板(注意type类型) <script id="tmplInvokeProvider" type="text/x-jquery-tmpl&q ...

  9. vue-组件之间的通信:

    组件之间的通信:一个组件被调用,那么里面的数据就需要从前者调用,因为在开发中组件时重复调用的,在页面中会反复使用,但是里面的数据是不一样的,谁调用这个组件谁就传递数据给这个组件,所以就要暴露一些接口, ...

  10. NX二次开发-UFUN创建球UF_MODL_create_sphere1

    NX11+VS2013 #include <uf.h> #include <uf_modl.h> UF_initialize(); //创建球 UF_FEATURE_SIGN ...