历史天气数据获取——请求参数与返回结果的模拟加密与解密

# 获取大量的历史天气数据,得到的数据可以做很多反面的研究(数据分析的期中、期末汇报等等,还可以做相关的web开发等,在本次的例子中也可以将重点放在逆向上,考虑安全问题),本次将以一个提供近10年主要城市的天气数据的网页为例,介绍逆向过程和数据处理与分析,提供一丢丢的思路与解法。

目标网址PM2.5历史数据_空气质量指数历史数据_中国空气质量在线监测分析平台历史数据PM2.5历史数据查询,空气质量指数(AQI)历史数据查询,全国空气质量指数排名,中国空气质量在线监测分析平台历史数据查询icon-default.png?t=N7T8https://www.aqistudy.cn/historydata/

一、找到接口并分析

准备工作

这个网站的反扒工作做得比较全面,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()

感谢各位看官老爷,动动你们发财的小手指,点个收藏加关注!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值