引言:本人最近稍微弄懂了inspeckage的用法,特在此以步道乐跑APP为例,较详细记录地记录APP抓包与简单的逆向分析过程,用于备忘与共同学习!另外,温馨提醒,本文图片较多,建议连接WiFi阅读!

目录:

一、准备工作

1、需要用到的APP

2、安装与配置

二、开始抓取数据

1、Inspeckage监测

2、HttpCanary抓取

三、数据分析

1、HttpCanary数据部分

2、Inspeckage数据部分

四、代码实现(Python) 

1、AES-CBC-PKCS5加解密

2、md5加密

3、请求数据构建

4、发送请求与解析响应

5、总代码(包括其他接口与其他数据)


正文:

一、准备工作

1、需要用到的APP

  • VMOS Pro
  • HttpCanary
  • JustTrustMe
  • Inspeckage

已经在蓝奏云安排上了,请自行下载!

链接:

https://huanxingke.lanzoui.com/b02069isj

密码:

lptiyu

2、安装与配置

(1)VMOS Pro的配置

Ⅰ、安装在真机上;

Ⅱ、本人为了更好的效果开了会员,用的是下图的虚拟机:

Ⅲ、成功加载虚拟机并打开后,会看到如下页面,点击文件传输:

Ⅳ、点击我要导入:

Ⅴ、点击安装包,选中Inspeckage和JustTrustMe,确认后将自动安装:

Ⅵ、安装步道乐跑:先在真机上安装步道乐跑APP,然后还是点击我要导入 --> 应用 --> 找到步道乐跑 --> 确认安装;

Ⅶ、然后回到主页,点击进入Xpose:

Ⅷ、点击左上角三杠 --> 模块 --> 选中Inspeckage和JustTrustMe模块,然后重启虚拟机以激活模块:

Ⅸ、重启虚拟机后,JustTrustMe模块已默认激活成功,然后配置Inspeckage:进入Inspeckage,当显示Module enable和Server start时,初始化成功,如下图:

(2)HttpCanary的配置

Ⅰ、安装在真机上;

Ⅱ、打开HttpCanary,点击左上角三杠 --> 点击左下角设置 --> 点击SSL证书设置,如图:

Ⅲ、点击安装证书,如图,然后按提示进行:

Ⅳ、返回上一页面,选择目标应用,如图:

Ⅴ、点击右上角+号,选择VOMS Pro,如图,注意不要选择其他应用,以免干扰:

至此,准备工作完成,准备开始抓取数据。

 

二、开始抓取数据

1、Inspeckage监测

(1)首先,请先在步道乐跑APP上登录好你的账号,然后退出;

(2)进入Inspeckage,按下图选择监测步道乐跑APP:

(3)选择好之后,保留虚拟机在后台运行,回到真机浏览器,输入127.0.0.1:8008,若看到如下界面,则配置初步成功,注意此时App is running为false,左上角开关为OFF:

(4)保留真机浏览器在后台,不要关闭,然后进行下一步;

2、HttpCanary抓取

(1)打开HttpCanary,点击右下角小飞机,小飞机变绿代表抓包开始;

(2)然后直接返回,注意不要清理后台,调出虚拟机,此时HttpCanary将会驻在屏幕右下角位置,如图,然后点击LAUNCH APP:

(3)此时Inspekage将会唤醒步道乐跑APP,抓包与监测工作均将开始,如图,可看到HttpCanary已有抓包数据:

(4)然后点击真机的方框导航键(手势导航的是按住屏幕底部上滑),调出真机浏览器后,刷新一下刚才的页面,如果App is running为true则监测成功,此时打开左上角开关,就可以获取监测的数据了,成功界面如图所示:

至此,抓包与监测工作均已开始并已经获取到步道乐跑APP启动后的网络请求数据,可以开始分析数据了。

 

三、数据分析

1、Httpcanary数据部分

(1)全屏打开HttpCanary,可以看到已经抓取到很多请求了,先拉到最底部,从最开始的请求寻找起,看有没有比较特别的、可能符合我们需要的请求;

(2)如下图,可以发现有一个请求含有Login关键词,我们可以下意识地想到:这可能是跟用户登录有关的请求,这在APP抓包上还是挺重要的一部分:

(3)那我们点击打开这一请求,点击请求 --> 点击右下角预览,如图:

(4)我们可以看到有几个值得我们留意的字段:token、access_token、refresh_token、timestamp、nonce、jpush_id、sign:

Ⅰ、前三者均为token类字段,按照经验,此类字段一般是由服务器生成的,所以我们暂时先不考虑;

Ⅱ、timestamp为时间戳,nonce为随机字符串,均是起防止重放攻击作用的,可以不予考虑(见博主@koastal的博文:https://blog.csdn.net/koastal/article/details/53456696);

综上,最值得我们考虑的就是sign值了——其实我们一开始就应该特别留意到这个sign值了,因为这是很常用的加密算法的字段,并且我们还可以发现它与md5加密后的格式十分相似,先默默记下来;

(5)然后我们再看一下响应吧,如图:

(6)很明显,响应中的data值为base64编码,但当我们拿去解密的时候,得到的是乱码:

¶"Z¥ÆÍǠЩ<í/ÅtlÎÝοaó±8Êã½BV-0ÃúCÃæOÒ;Çÿ(^ò±°Î?t(:t1ÂTº	ådä0ãùºÆ^Ü~.KÝÜ[õõ»9+ÕI5ùÄs©îÁ^Çw[Ï8
Ïϼ#>,ÌeÔPÅ¿Vú2Êç7ZzÇF£ÙÈÊn©

所以很显然,这个字段在base64之前就已经被加密过一次了,这便应当引起我们的兴趣了;

得到了以上数据之后,我们便可以去Inspeckage网页上寻找对应加密方法了。

2、Inspeckage数据部分

(1)回到Inspeckage网页,我们需要知道,网页上的Crypto(一般是签名算法,如AES)和Hash(哈希算法,如md5)是我们寻找加密方法的来源;

(2)我们先看一下Crypto里的数据吧,点击Crypto,如图(温馨提示:请先把左上角的开关关闭为OFF再来分析数据哦,否则网页的动态变化会影响我们的数据寻找过程哦):

(3)我们可以看到已经监测到很多数据了,在上面已经说到了sign可能是md5加密的,也就是Hash,所以我们先不在Crypto里寻找sign,而是先寻找data值,那么要怎么寻找呢:

Ⅰ、根据请求顺序寻找:我们知道data值是包含login字段请求的响应,而此请求在HttpCanary中属于最早发送的一批请求,那我们便应该在Inspeckage网页上也拉到最底部来寻找;

Ⅱ、使用浏览器自带的查找功能,如图:

然后输入data值来匹配寻找即可,注意:只需要复制data值开头的几位字符来寻找即可,因为页面是显示不完全的,如果使用整个data值来匹配,反而找不到结果;

(4)由(3)中的方法,我们成功找到了data的加密算法,如图:

放大后:

将它复制下来就更明显了:

(为了保护隐私, 部分数据使用*号代替)
SecretKeySpec(Wet2C8d34f62ndi3,AES) , Cipher[AES/CBC/PKCS5Padding] IV: K6iv85jBD8jgf32D (tiJapcbNx6AL0JmpPO0VL8V0bM7dEc4dvwth87E4HMrjE
L1CVi0ww5qO+kPD5k8L0jvH/xooXvKxsADOP5x0KAsSOpV0McJUugnlZOQwm+P5usZe3H4uS93cW/X1uzkQK4jVSY0eNfnEG3Op7hDBAl6PjccMd1uVzzgNz88VvCM+LMxl1FDFv1b6MsoC5zdaesdGoxiP******** , {"uid":"*******","access_token":"333C877C9429E26D4FCDC404******","refresh_token":"CC304EDBA40FF2F2AEA2D28C******","refresh_expire":1623509738})

也就是说data进行base64前的加密算法为AES-CBC对称算法,字符串使用的是PKCS5Padding格式,其中SecretKey值为:Wet2C8d34f62ndi3,偏移量iv为K6iv85jBD8jgf32D,加密前的字符串为:

{"uid":"*******","access_token":"333C877C9429E26D4FCDC404******","refresh_token":"CC304EDBA40FF2F2AEA2D28C******","refresh_expire":1623509738}

data值成功破译!

(5)按照同样的方法,我们点击Hash部分来尝试破译sign值:

(6)这里有个小技巧,因为网页默认是不完整显示的,所以会导致某些值看不到,这是我们可以点击网页里的一些>>符号可以将其展开完全显示,避免匹配失败:

(7)不出意外,sign值算法与加密前的字符串也被我们找到了:

复制下来:

Algorithm(MD5) [access_token006C31A39AED1ECAAA28C32554******jpush_id1507bfd3f7684******mobileDeviceId185818969******mobileModelBRQ-AN00
mobileOsVersion7.1.2nonce320975ostype1refresh_token9EF86DB536CEB764B430EC2BA4******school_id***student_num******timestamp1620917736token006C31A39AED1ECAAA28C325****uid******version86version_name3.3.6rDJiNB9j7vD2 : 7aeb494fc0063ec5c60cfd7ef8373929]

所以果然是md5加密!加密前的字符串为:

access_token006C31A39AED1ECAAA28C32554******jpush_id1507bfd3f7684******mobileDeviceId185818969******mobileModelBRQ-AN00
mobileOsVersion7.1.2nonce320975ostype1refresh_token9EF86DB536CEB764B430EC2BA4******school_id***student_num******timestamp1620917736token006C31A39AED1ECAAA28C325****uid******version86version_name3.3.6rDJiNB9j7vD2

至此,简单的逆向分析已经大功告成了!其他接口、其他数据加密算法都可以使用类似的方法来寻找和破译!可以开始码代码了!

 

四、代码实现(Python)

1、AES-CBC-PKCS5加解密(crypto.py)

(1)先看一下相关库的安装与使用:博客园:https://www.cnblogs.com/niuu/p/10107212.html

(2)代码实现:

from Crypto.Cipher import AES
import base64
import re


# 加解密
class Crypto(object):
    def __init__(self):
        # pubkey值
        self.key = 'Wet2C8d34f62ndi3'.encode('utf-8')
        # 偏移量
        self.iv = b'K6iv85jBD8jgf32D'
        # AES-CBC对称加密
        self.mode = AES.MODE_CBC
        # AES-CBC-PKCS5格式化字符串
        self.bs = 16
        self.PADDING = lambda s: s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs)

    # AES-CBC加密
    def AESEncrypt(self, text):
        generator = AES.new(self.key, self.mode, self.iv)
        crypt = generator.encrypt(self.PADDING(text).encode("utf-8"))
        # 加密后转base64
        crypted_str = base64.b64encode(crypt)
        result = crypted_str.decode()
        return result

    # 解密
    def AESDecrypt(self, text):
        text = base64.b64decode(text)
        cryptos = AES.new(self.key, self.mode, self.iv)
        plain_text = cryptos.decrypt(text)
        data = bytes.decode(plain_text)
        # 转中文
        data = data.replace(r'\/', '/').encode().decode('unicode_escape')
        # print(json.dumps(data))
        # 格式化
        pat = re.compile(r'<html>(.*?)</html>')
        html = pat.findall(data)
        html = html[0] if html else ""
        html_ = html.replace('"', "'")
        # 转为字典
        data = json.loads(data.replace(html, html_).strip('"').strip('\u000e').strip('\u0007').strip('\u0004'))
        return data

2、md5加密(Md5.py)

import hashlib


def Md5(text):
    text = text.encode()
    m = hashlib.md5()
    m.update(text)
    return m.hexdigest()

3、请求数据构建(data.py)

from crypto import *
import time


# 打*号的数据涉及隐私, 请自行获取
data = {'version': '86', 'version_name': '3.3.6', 'mobileModel': 'BRQ-AN00', 'mobileDeviceId': '******', 'mobileOsVersion': '7.1.2', 'ostype': '1', 'student_num': '******', 'school_id': '***', 'uid': '******', 'token': '***************', 'timestamp': '', 'nonce': '******', 'access_token': '***************', 'refresh_token': '*****************', 'jpush_id': '*****************', 'sign': '*****************'}
timestamp = int(time.time())
token = '*************'
refresh_token = '************'
sign_data = 'access_token{}jpush_id*********mobileDeviceId**************mobileModelBRQ-AN00mobileOsVersion7.1.2nonce********ostype1refresh_token{}school_id***student_num********timestamp{}token{}uid******version86version_name3.3.6rDJiNB9j7vD2'.format(token, refresh_token, timestamp, token)
sign = Md5(sign_data)
data['token'] = token
data['access_token'] = token
data['refresh_token'] = refresh_token
data['timestamp'] = str(timestamp)
data['sign'] = sign
print(sign)
print(data)

4、发送请求与解析响应(req.py)

import urllib.parse
import requests


url = "https://api2.lptiyu.com/v3/api.php/Login/RefreshToken"
headers = {'Cookie': 'PHPSESSID=*******************', 'Connection': 'close', 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', 'Content-Length': '299', 'User-Agent': 'Dalvik/2.1.0(Linux;U;Android7.1.2;BRQ-AN00Build/NZH54D)', 'Host': 'api2.lptiyu.com', 'Accept-Encoding': 'gzip'}
data = {'version': '86', 'version_name': '3.3.6', 'mobileModel': 'BRQ-AN00', 'mobileDeviceId': '***************', 'mobileOsVersion': '7.1.2', 'ostype': '1', 'student_num': '************', 'school_id': '***', 'uid': '*******', 'token': '*************', 'timestamp': '******', 'nonce': '*********', 'access_token': '**********', 'refresh_token': '***********', 'jpush_id': '***************', 'sign': '*************'}
data = urllib.parse.urlencode(data)
response = requests.post(url=url, headers=headers, data=data).json()
print(response)

5、总代码(包括其他接口与其他数据)

from Crypto.Cipher import AES
import urllib.parse
import requests
import hashlib
import random
import string
import base64
import time
import json
import re
import os


# 加解密
class Crypto(object):
    def __init__(self):
        # pubkey值
        self.key = 'Wet2C8d34f62ndi3'.encode('utf-8')
        # 偏移量
        self.iv = b'K6iv85jBD8jgf32D'
        # AES-CBC对称加密
        self.mode = AES.MODE_CBC
        # AES-CBC-PKCS5格式化字符串
        self.bs = 16
        self.PADDING = lambda s: s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs)

    # AES-CBC加密
    def AESEncrypt(self, text):
        generator = AES.new(self.key, self.mode, self.iv)
        crypt = generator.encrypt(self.PADDING(text).encode("utf-8"))
        # 加密后转base64
        crypted_str = base64.b64encode(crypt)
        result = crypted_str.decode()
        return result

    # 解密
    def AESDecrypt(self, text):
        text = base64.b64decode(text)
        cryptos = AES.new(self.key, self.mode, self.iv)
        plain_text = cryptos.decrypt(text)
        data = bytes.decode(plain_text)
        # 转中文
        data = data.replace(r'\/', '/').encode().decode('unicode_escape')
        # print(json.dumps(data))
        # 格式化
        pat = re.compile(r'<html>(.*?)</html>')
        html = pat.findall(data)
        html = html[0] if html else ""
        html_ = html.replace('"', "'")
        # 转为字典
        data = json.loads(data.replace(html, html_).strip('"').strip('\u000e').strip('\u0007').strip('\u0004'))
        return data

    # md5加密
    @staticmethod
    def Md5(text):
        text = text.encode()
        m = hashlib.md5()
        m.update(text)
        return m.hexdigest()


# 生成sign/data值
class Md5Encrypt(object):
    def __init__(self, **kwargs):
        # 原始json数据
        self.data = {
            # 可自定义的值
            "jpush_id": "",
            "mobileDeviceId": "",
            "mobileModel": "",
            "mobileOsVersion": "7.1.2",
            # 客户端版本号, 不建议修改
            "version": "86",
            "version_name": "3.3.6",
            "ostype": "1",
            # 标志值
            # 学校id, 默认-1为未知
            "school_id": "-1",
            # 时间戳
            "timestamp": "",
            # 六位随机数字字符串
            "nonce": ""
        }
        # sign末端的一个固定值
        self.sign_str = 'rDJiNB9j7vD2'
        # 用户uid
        self.uid = kwargs['uid'] if 'uid' in kwargs else None
        # 学号
        self.student_num = kwargs['student_num'] if 'student_num' in kwargs else None
        # 设置其他值
        for key, value in kwargs.items():
            self.data[key] = value

    # 登录data值
    def loginData(self, code, phone):
        # 继承data属性
        data = self.data
        # 更新标志值
        # 固定为1
        data['type'] = '1'
        # 手机号
        data['phone'] = str(phone)
        # 验证码, 30天内只能获取一次
        data['code'] = str(code)
        # 时间戳
        timestamp = str(int(time.time()))
        # 六位随机数字字符串
        nonce = str(random.randint(100000, 999999))
        # 放入data
        data['timestamp'] = timestamp
        data['nonce'] = nonce
        # 生成原始sign值
        # 一定要先对字典排序
        sign_data = ''.join([(i + data[i]) for i in sorted(data)]) + self.sign_str
        # 加密sign值
        sign = Crypto().Md5(sign_data)
        # sign值放入data
        data['sign'] = sign
        # 加密data
        data = Crypto().AESEncrypt(json.dumps(data))
        # 传输的data原始json值
        data = {
            'key': data
        }
        # 转换成form-data类型
        data = urllib.parse.urlencode(data)
        # 返回data值
        return data

    # 获取用户信息
    def userData(self, token):
        # 继承data属性
        data = self.data
        # 删除jpush_id值
        del data['jpush_id']
        # 更新标志值
        # 用户id
        data['uid'] = self.uid
        # token值
        data['token'] = token
        # 时间戳
        timestamp = str(int(time.time()))
        # 六位随机数字字符串
        nonce = str(random.randint(100000, 999999))
        # 放入data
        data['timestamp'] = timestamp
        data['nonce'] = nonce
        # 生成原始sign值
        # 一定要先对字典排序
        sign_data = ''.join([(i + data[i]) for i in sorted(data)]) + self.sign_str
        # 加密sign值
        sign = Crypto().Md5(sign_data)
        # sign值放入data
        data['sign'] = sign
        # 转换成form-data类型
        data = urllib.parse.urlencode(data)
        # 返回data值
        return data

    # 获取cookie
    def ipData(self, token):
        # 继承data属性
        data = self.data
        # 更新标志值
        # 固定为2
        data['type'] = '2'
        # 学号
        data['student_num'] = self.student_num
        # 用户id
        data['uid'] = self.uid
        # token值
        data['token'] = token
        # 时间戳
        timestamp = str(int(time.time()))
        # 六位随机数字字符串
        nonce = str(random.randint(100000, 999999))
        # 放入data
        data['timestamp'] = timestamp
        data['nonce'] = nonce
        # 生成原始sign值
        # 一定要先对字典排序
        sign_data = ''.join([(i + data[i]) for i in sorted(data)]) + self.sign_str
        # 加密sign值
        sign = Crypto().Md5(sign_data)
        # sign值放入data
        data['sign'] = sign
        # 转换成form-data类型
        data = urllib.parse.urlencode(data)
        # 返回data值
        return data

    # 刷新token值
    def refreshData(self, token, refresh_token):
        # 继承data属性
        data = self.data
        # 更新标志值
        # 学号
        data['student_num'] = self.student_num
        # 用户id
        data['uid'] = self.uid
        # token值
        data['token'] = token
        # access_token值(与token相同)
        data['access_token'] = token
        # refresh_token值
        data['refresh_token'] = refresh_token
        # 时间戳
        timestamp = str(int(time.time()))
        # 六位随机数字字符串
        nonce = str(random.randint(100000, 999999))
        # 放入data
        data['timestamp'] = timestamp
        data['nonce'] = nonce
        # 生成原始sign值
        # 一定要先对字典排序
        sign_data = ''.join([(i + data[i]) for i in sorted(data)]) + self.sign_str
        # 加密sign值
        sign = Crypto().Md5(sign_data)
        # sign值放入data
        data['sign'] = sign
        # 转换成form-data类型
        data = urllib.parse.urlencode(data)
        # 返回data值
        return data

    # 获取排行榜
    def rankData(self, token, page):
        # 继承data属性
        data = self.data
        # 删除jpush_id值
        if 'jpush_id' in data:
            del data['jpush_id']
        if 'sign' in data:
            del data['sign']
        # 更新标志值
        # 固定为1
        data['type'] = '1'
        # 固定为1
        data['category'] = '1'
        # 学号
        data['student_num'] = self.student_num
        # 用户id
        data['uid'] = self.uid
        # token值
        data['token'] = token
        # 页码
        data['page'] = str(page)
        # 时间戳
        timestamp = str(int(time.time()))
        # 六位随机数字字符串
        nonce = str(random.randint(100000, 999999))
        # 放入data
        data['timestamp'] = timestamp
        data['nonce'] = nonce
        # 生成原始sign值
        # 一定要先对字典排序
        sign_data = ''.join([(i + data[i]) for i in sorted(data)]) + self.sign_str
        # 加密sign值
        sign = Crypto().Md5(sign_data)
        # sign值放入data
        data['sign'] = sign
        # 转换成form-data类型
        data = urllib.parse.urlencode(data)
        # 返回data值
        return data


# 发送请求
class GetResponse(object):
    def __init__(self, **kwargs):
        # 请求头
        self.headers = {
            'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
            'User-Agent': 'Dalvik/2.1.0(Linux;U;Android7.1.2;BRQ-AN00Build/NZH54D)',
            'Host': 'api2.lptiyu.com',
            'Accept-Encoding': 'gzip'
        }
        # 读取用户信息
        userData = User().getUser()
        # 虚拟客户端信息
        jpush_id = userData['jpush_id']
        mobileDeviceId = userData['mobileDeviceId']
        mobileModel = userData['mobileModel']
        # 用户标识信息
        # 用户uid
        if 'uid' not in userData:
            self.encrypter = Md5Encrypt(jpush_id=jpush_id, mobileDeviceId=mobileDeviceId, mobileModel=mobileModel)
            self.login(autoLogin=False)
        else:
            self.uid = userData['uid']
            self.token = userData['access_token']
            self.refresh_token = userData['refresh_token']
        # 学号
        if 'student_num' not in userData:
            print('正在获取学号...')
            self.encrypter = Md5Encrypt(jpush_id=jpush_id, mobileDeviceId=mobileDeviceId, mobileModel=mobileModel, uid=self.uid)
            self.user()
        else:
            self.student_num = userData['student_num']
        # 重载加密器
        self.encrypter = Md5Encrypt(jpush_id=jpush_id, mobileDeviceId=mobileDeviceId, mobileModel=mobileModel, uid=self.uid, student_num=self.student_num)
        print('初始化完成!')

    # 重新登录
    def login(self, code=None, phone=None, autoLogin=True):
        if not autoLogin:
            print('请先重新登录!')
            phone = input('请输入手机号码: ')
            code = input('请输入验证码: ')
            print('正在登录......')
        url = 'https://api2.lptiyu.com/v3/api.php/Login/quickLoginV300'
        data = self.encrypter.loginData(code=code, phone=phone)
        response = requests.post(url=url, headers=self.headers, data=data).json()
        if 'data' not in response:
            raise Exception("登录失败!")
        tokenData = Crypto().AESDecrypt(response['data'])
        self.uid = tokenData['uid']
        self.token = tokenData['access_token']
        self.refresh_token = tokenData['refresh_token']
        # 保存token数据
        User().saveUser(userData=tokenData)
        return tokenData

    # 获取用户信息
    def user(self):
        url = 'https://api2.lptiyu.com/v3/api.php/User/User'
        data = self.encrypter.userData(token=self.token)
        response = requests.post(url=url, headers=self.headers, data=data).json()
        if 'data' not in response:
            raise Exception("登录失效!")
        user = Crypto().AESDecrypt(response['data'])
        self.student_num = user['student_num']
        User().saveUser(userData=user)
        return user

    '''
    # 获取cookie
    def getIp(self):
        url = 'https://api2.lptiyu.com/v3/api.php/System/getIp'
        data = self.encrypter.ipData(self.token)
        response = requests.post(url=url, headers=self.headers, data=data)
        try:
            cookies = response.cookies
            cookies = requests.utils.dict_from_cookiejar(cookies)
            self.headers["Cookie"] = list(cookies.keys())[0] + '=' + list(cookies.values())[0]
        except:
            raise Exception("登录失效!")
        return cookies
    '''

    # 刷新token值
    def refreshToken(self):
        url = 'https://api2.lptiyu.com/v3/api.php/Login/RefreshToken'
        data = self.encrypter.refreshData(token=self.token, refresh_token=self.refresh_token)
        response = requests.post(url=url, headers=self.headers, data=data).json()
        if 'data' not in response:
            raise Exception("登录失效!")
        tokenData = Crypto().AESDecrypt(response['data'])
        self.token = tokenData['access_token']
        self.refresh_token = tokenData['refresh_token']
        # 保存token数据
        User().saveUser(userData=tokenData)
        return tokenData

    # 获取排行榜
    def getTotalRank(self, page):
        url = 'https://api2.lptiyu.com/v3/api.php/Run/getTotalRank'
        data = self.encrypter.rankData(token=self.token, page=page)
        response = requests.post(url=url, headers=self.headers, data=data).json()
        if 'data' not in response:
            raise Exception("登录失效!")
        rankData = Crypto().AESDecrypt(response['data'])['rank_list']
        return rankData


# 操作user文件
class User(object):
    def __init__(self):
        # token文件路径
        self.file = 'user.json'

    # 构建客户端信息
    def createInfo(self):
        s = string.ascii_letters + string.digits
        jpush_id = "".join(random.choice(s) for _ in range(0, 19))
        mobileDeviceId = "".join(random.choice(string.digits) for _ in range(0, 15))
        mobileModel = "".join(random.sample(string.digits, 3)) + '-' + "".join(random.sample(s, 4))
        user = {'jpush_id': jpush_id, 'mobileDeviceId': mobileDeviceId, 'mobileModel': mobileModel}
        with open(self.file, 'w') as fp:
            json.dump(user, fp)

    # 从本地获取User信息
    def getUser(self):
        if not os.path.exists(self.file):
            self.createInfo()
        with open(self.file, 'r') as fp:
            userData = json.load(fp)
        return userData

    # 保存User数据
    def saveUser(self, userData):
        with open(self.file, 'r') as fp:
            userData_ = json.load(fp)
        for key, value in userData.items():
            userData_[key] = value
        with open(self.file, 'w') as fp:
            fp.write(json.dumps(userData_))


if __name__ == '__main__':
    handler = GetResponse()
    fp = open('lptiyu.csv', 'w', encoding='utf-8')
    fp.write('排名,姓名,已跑次数\n')
    for j in range(1, 4):
        rankData_ = handler.getTotalRank(page=j)
        for i in rankData_:
            score = i['score_num']
            rank = i['rank']
            name = i['name']
            fp.write('%s,%s,%s\n' % (rank, name, score))
    fp.close()

好了,今天的总结与分享就到这里,感谢你的阅读!

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐