# 获取大量的历史天气数据,得到的数据可以做很多反面的研究(数据分析的期中、期末汇报等等,还可以做相关的web开发等,在本次的例子中也可以将重点放在逆向上,考虑安全问题),本次将以一个提供近10年主要城市的天气数据的网页为例,介绍逆向过程和数据处理与分析,提供一丢丢的思路与解法。
一、找到接口并分析
准备工作
这个网站的反扒工作做得比较全面,F12和右键都被禁用了。
用浏览器的功能打开开发者工具,并将开发者工具的悬浮方式改为停靠到单独的窗口,否则会被检测到调试。
点击右侧的具体月份信息,捕获请求,找到请求网址为https://www.aqistudy.cn/historydata/api/historyapi.php该post请求的载荷和响应都是密文——所以逆向就要分两部分了——请求参数的生成和返回结果的解密。
二、请求参数生成逆向
请求参数的关键字是hA4Nse2cT,直接全局搜索,发现只在一个vm文件中有(可能下次刷新就要重新打断点),直接在这个地方打上断点(hA4Nse2cT: pKmSFk8,也就是将pKmSFk8的值赋给hA4Nse2cT,在该页面中搜索pKmSFk8,发现 var pKmSFk8 = poPBVxzNuafY8Yu(m0fhOhhGL, oBDNNVgaDf);非常可以,所以断点打在这里),再请求其他月份,你猜怎么着,断住了!
分别打印m0fhOhhGL和oBDNNVgaDf的值,不难发现m0fhOhhGL是一个字符串,GETDAYDATA,而oBDNNVgaDf是一个对象,包含城市和月份信息。控制台输出poPBVxzNuafY8Yu(m0fhOhhGL, oBDNNVgaDf)的结果,不难发现就是请求参数。接下来就是慢慢的扣poPBVxzNuafY8Yu这个函数了。定位到
到这就很明显了,这个算法涉及了MD5、BASE64和AES三种加密算法。在调用这些算法的地方打上断点,去内部看看有没有算法魔改(有些魔改算法就是故意的给你迷惑一下,让你以为是标准的算法,其实不是)
检查发现调用的都是标准的加密算法,但是有没有发现它不仅有加密算法还有解密算法,那会不会解密的时候同样涉及这些算法,所以先给它在解密的算法上打上断点。
接下来就是愉快而解压的扣代码过程了,标准库导一下(后续python调用的话记得装一个库)
我封装之后的完整算法仅仅为例。
三、返回结果的解密
当正常发送请求之后,一两秒后,又断住了。
打印输出dGHdO的值,再去看请求的响应,很明显dGHdO就是响应的密文,再打印dxvERkeEvHbS(dGHdO)的结果,就是正常的json格式的响应数据了,那么解密算法算是被我们找到了,接下来又是愉快的扣代码过程了,与请求参数的生成一样,都是标准库,补一些东西就可以用了。
四、python爬虫
这里我就直接给框架了。
import json
import time
import requests
import execjs
from datetime import datetime
from dateutil.relativedelta import relativedelta
def getRequestCiphertext(oNLhNQ):
"""
调用js文件获得请求的密文
"""
with open("历史天气请求参数.js", "r", encoding="utf-8") as f:
js = f.read()
ctx = execjs.compile(js)
requestCiphertext = ctx.call("getDATA", oNLhNQ)
return requestCiphertext
def getPlaintext(requestCiphertext):
"""
调用js文件获得请求的明文
:return:
"""
with open("历史天气解密.js", "r", encoding="utf-8") as f:
js = f.read()
ctx = execjs.compile(js)
plaintext = ctx.call("decrypt", requestCiphertext)
return plaintext
def request_data(requestCiphertext):
headers = {
"Accept": "*/*",
"Accept-Language": "zh-CN,zh;q=0.9",
"Connection": "keep-alive",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"Origin": "https://www.aqistudy.cn",
"Referer": "https://www.aqistudy.cn/historydata/daydata.php?city=%E6%9D%AD%E5%B7%9E&month=201411",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
"X-Requested-With": "XMLHttpRequest",
"sec-ch-ua": "\\Chromium;v=\\124, \\Google",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\\Windows"
}
cookies = {
"Hm_lvt_6088e7f72f5a363447d4bafe03026db8": "1716869252,1716871793,1716882827",
"Hm_lpvt_6088e7f72f5a363447d4bafe03026db8": "1716884070"
}
url = "https://www.aqistudy.cn/historydata/api/historyapi.php"
data = {
"hA4Nse2cT": requestCiphertext
}
response = requests.post(url, headers=headers, cookies=cookies, data=data)
return response.text
def generate_date_strings(start, end):
# 转换起始和结束日期字符串为datetime对象
start_date = datetime.strptime(start, "%Y%m")
end_date = datetime.strptime(end, "%Y%m")
date_string = []
current_date = start_date
# 使用一个循环,按月递增,直到达到结束日期
while current_date <= end_date:
date_string.append(current_date.strftime("%Y%m"))
# 增加一个月
current_date += relativedelta(months=1)
return date_string
def save_data(plaintext, path):
items = json.loads(plaintext)['result']['data']["items"]
with open(f"CSV/{path}.csv", "a", encoding="gbk") as f:
for item in items:
f.write(
f"{str(item['aqi'])},{str(item['pm2_5'])},{str(item['pm10'])},{str(item['so2'])},{str(item['no2'])},{str(item['co'])},{str(item['o3'])},{str(item['rank'])},{str(item['quality'])}\n")
def build_csv(path):
with open(f"CSV/{path}.csv", "w", encoding="gbk") as f:
f.write("aqi,pm2_5,pm10,so2,no2,co,o3,rank,quality\n")
def main():
date_strings = generate_date_strings("201405", "202404")
cities = ["北京"]
for city in cities:
build_csv(city)
count = 0
for date_string in date_strings:
while True:
try:
t = {
"city": city,
"month": date_string
}
ciphertext = (getRequestCiphertext(t)) # 得到密文
res = request_data(ciphertext) # 得到响应的密文
plaintext = getPlaintext(res) # 解密得到明文
save_data(plaintext, city)
print(f"{city} {date_string} 成功")
break
except:
count += 1
if count > 15:
pass
time.sleep(2)
if __name__ == '__main__':
main()
感谢各位看官老爷,动动你们发财的小手指,点个收藏加关注!