- 中国空气质量在线监测分析平台是一个收录全国各大城市天气数据的网站,包括温度、湿度、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. svn 一、 安装及汉化

    svn 是日常开发过程中常用的版本控制工具 第一步 安装 进入官网 https://tortoisesvn.net/ 点击downloads 进入之后选中 需要的版本,及位数 这里推荐安装最新版的 因 ...

  2. loj2000[SDOI2017]数字表格

    题意:f为Fibnacci数列.求$\prod_{1<=i<=n,1<=j<=m} f[gcd(i,j)]$. n,m<=1e6. 标程: #include<bit ...

  3. No identifier specified for entity: com.XXX.XXX...

    这种情况一般是没有在属性上加@Id注解导致的. @Entity @Data @Table(name = "hl_role_module") public class RoleMod ...

  4. thinkphp 入口绑定

    入口绑定是指在应用的入口文件中绑定某个模块,甚至还可以绑定某个控制器和操作,用来简化URL地址的访问. 绑定模块 例如,我们定义了一个入口文件admin.php,希望可以直接访问Admin模块,那么我 ...

  5. Python3 From Zero——{最初的意识:002~字符串和文本}

    一.使用多个界定符分割字符串 字符串.split(',')形式只适用于单一分割符的情况:多分割符同时应用的时候,可使用re.split() >>> line = 'asdf fjdk ...

  6. storm0.91集群部署

    事先配置2台服务器配置好zookeeper,在配置文件中用zookeeper管理集群,配置文件如下 配置文件/conf/storm.yaml supervisor.slots.ports: 对于每个S ...

  7. POJ 3667 线段树区间合并裸题

    题意:给一个n和m,表示n个房间,m次操作,操作类型有2种,一种把求连续未租出的房间数有d个的最小的最左边的房间号,另一个操作时把从x到x+d-1的房间号收回. 建立线段树,值为1表示未租出,0为租出 ...

  8. 【POJ】3660 Cow Contest

    题目链接:http://poj.org/problem?id=3660 题意:n头牛比赛,有m场比赛,两两比赛,前面的就是赢家.问你能确认几头牛的名次. 题解:首先介绍个东西,传递闭包,它可以确定尽可 ...

  9. iOS开发系列-iOS布局相关

    LayoutSubViews 需要在某个View调整子视图的位置时,可以重写. 以下情况会出发LayoutSubViews方法的调用 init初始化不会触发layoutSubviews,但是是用ini ...

  10. 笔记:Python实现二分查找

    def search(sequence, number, lower=0, upper=None): if upper is None: upper = len(sequence) - 1 if lo ...