引言:本人最近稍微弄懂了inspeckage的用法,特在此以步道乐跑APP为例,较详细记录地记录APP抓包与简单的逆向分析过程,用于备忘与共同学习!另外,温馨提醒,本文图片较多,建议连接WiFi阅读!
目录:
正文:
一、准备工作
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()
好了,今天的总结与分享就到这里,感谢你的阅读!