提示:第一篇技术博客
文章目录
前言
例如:记录一次对于某教育网站的接口分析,实现平时作业等功能的自动实现…不是完整教程,只展示核心部分。
一、获取账户登陆的cookies值
这里因为是个人学习用,直接用selenium库,实现账户密码的自动输入,获取成功登陆后的cookies。
核心代码如下:
def login(self):
self.browserWebPage.get(self.urlWebPage)
bb = self.browserWebPage.find_elements_by_tag_name('input')
bb[0].send_keys(self.username)
time.sleep(0.5)
bb[1].send_keys(self.username)
time.sleep(0.2)
def setCookies(self):
windows = self.browserWebPage.window_handles
self.browserWebPage.switch_to.window(windows[-1])
dictCookies = self.browserWebPage.get_cookies()
b = {}
for i in dictCookies:
b[i['name']] = i['value']
self.Cookies.update(b)
二、分析学期课程,平时作业等接口
1. 调用bs4解析模拟请求后返回的数据,进行数据清洗(该类代码众多,就不逐个展示)
核心代码如下:
# 获取题目结构,题目以及题目id
def get_question_numbers(self, url): # 获取题目结构,url根据网页地址参数获取
res = self.res_work.get(url, headers=self.headers, cookies=self.cookies)
soup = BeautifulSoup(res.text, 'html.parser')
for k in soup.find_all('div', attrs={'class': 'ui-question-1'}):
try:
if k['id'][0:2] == 'q_':
self.q1_list.append(k['id'])
except:
pass
try:
if k['code'][0:4] == 'psq_':
self.psq1_list.append(k['code'])
except:
pass
self.questions.update(dict(zip(self.q1_list, self.psq1_list)))
for k in soup.find_all('div', attrs={'class': 'ui-question-2'}):
try:
if k['id'][0:2] == 'q_':
self.q2_list.append(k['id'])
except:
pass
try:
if k['code'][0:4] == 'psq_':
self.psq2_list.append(k['code'])
except:
pass
self.questions.update(dict(zip(self.q2_list, self.psq2_list)))
self.title = soup.find_all('title')[0].string
print("当前页面存在{}道单选题,{}道多选题,{}".format(len(self.q1_list), len(self.q2_list), len(self.questions)))
2. js分析解密并提交参数m值
(1) js 分析参数m生成的过程
该部分应该是最麻烦且最困难的部分,需要去分析js代码跳转过程,以及推测加密参数的构造(因为该项目已经失效,很遗憾没能及时保留截图,只有改后的代码)
通过在F12控制台打断点对代码进行调试,发现参数m是经过一个js混淆后的md5方法,md5方法包含以下几个参数(课程id,作业id以及一个包含选项id,答案id和时间戳的列表)
js加密代码如下:
function encryption(_0x218bf7, _0x35647a, _0x3a007c) {
var _0x485c78 = _0x12dc,
_0xec1f31 = {
kDpQF: function (_0x386410, _0x1fbe0f) {
return _0x386410 + _0x1fbe0f;
},
mwVLY: function (_0x7ba511, _0x4f8258, _0x3e4044) {
return _0x7ba511(_0x4f8258, _0x3e4044);
},
inEgb: _0x485c78(0x1a8),
enlev: function (_0x1bbefb, _0x5ea5f3) {
return _0x1bbefb + _0x5ea5f3;
},
iQnit: function (_0x367a66, _0x5a60c0) {
return _0x367a66 + _0x5a60c0;
},
DJdam: _0x485c78(0x1b7),
FEqlZ: function (_0x3eb2c3, _0x3733ca) {
return _0x3eb2c3 + _0x3733ca;
},
Qssiv: function (_0x28a7ab, _0x51c417) {
return _0x28a7ab(_0x51c417);
}
};
_0x3a007c[_0xec1f31[_0x485c78(0x19e)]] = new Date()[_0x485c78(0x1b5)]();
const _0x1f75ae = [];
for (let _0x24cce9 in _0x3a007c) {
_0x1f75ae[_0x485c78(0x1b2)](_0x24cce9);
}
_0x1f75ae[_0x485c78(0x223)]();
// 通过多次猜测尝试,成功模拟拿到加密参数m
let _0x550d17 = "";
const res =[]
res[0] = (
_0x1f75ae[_0x485c78(0x219)]((_0x137da3) => {
var _0x574934 = _0x485c78;
_0x550d17 = _0xec1f31[_0x574934(0x1c0)](
_0x550d17,
_0xec1f31[_0x574934(0x224)](notEach, _0x137da3, _0x3a007c)
);
}),
(_0x550d17 = _0xec1f31[_0x485c78(0x203)](
_0xec1f31[_0x485c78(0x213)](_0x550d17, _0xec1f31[_0x485c78(0x235)]),
_0xec1f31[_0x485c78(0x231)](_0x35647a, _0x218bf7)
)),
_0xec1f31[_0x485c78(0x1f0)](md5, _0x550d17)
);
res[1] = _0x3a007c['stime']
return res
}
(2) 在程序中运行js代码
之前一直没想到能够调用js程序,甚至想到了用命令行带参运行…后面查询相关资料,python有着能够在程序装载js代码的第三方库。这里运用execjs库运行。注意:需要配置node.js的环境才能正常运行
核心代码如下:
def decryption(self,psqId, answer, userExamId, qId):
with open("md5.js", "r") as f:
functions = f.read()
# 编译成js对象
ctx = execjs.compile(functions)
# 调用里面的变量
params = ctx.eval("params")
params = {
'psqId': psqId,
'answer': answer,
'attach': '',
}
# 调用方法,第一个方法名,后面都是参数
params = ctx.call("encryption", userExamId, qId, params)
return params
(3)最后在提交请求上,调用该方法得到解密后参数,并完成一次答题记录
通过遍历页面中所有题目,并且反复发送不同答案的请求,验证每一道题的正确答案。这里只粘贴部分代码。
核心代码如下:
n = 5
for answer in 'abcde':
if len(self.right_answers) == len(self.questions):
break
print('\n还有{}次单选未运行...'.format(n))
n = n - 1
for i in range(len(self.q1_list)):
res = self.decryption(self.psq1_list[i][4:],answer,userExamId,self.q1_list[i][2:])
m, stime = res[0], res[1]
data = ''
res = self.res_work.post(url=url0, headers=self.headers, cookies=self.cookies, data=data)
3. 答题结果输出
为了能够统计答题情况,将答题结果输出于相应的txt文件中。
其实更好的措施,可以把结果写进sqlite数据库中,方便后续进行数据分析等操作。
核心代码如下:
def result_to_txt(self):
if not os.path.exists("results"): # 判断是否存在文件夹如果不存在则创建为文件夹
os.makedirs("results")
with open('results\{}.txt'.format(self.name), 'a+') as f:
f.write(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()) + '\n')
f.write('{} 得分:{}\n'.format(self.title, self.score))
f.write("该单元{}道单选题,{}道多选题,共{}道题\n".format(len(self.q1_list), len(self.q2_list), len(self.questions)))
f.write("\n")
三.性能提升,以及日志文件
为了提高性能,这里选用了multiprocessing 库,构建多进程池,实现多进程并发执行程序。性能需求高的话,可以引用协程,替换程序中的requests请求,实现异步请求处理,性能上能够得到一定提高。
def singleTask(username,address):
username = username.replace('\n','').strip()
address =address.replace('\n','').strip()
person = getCookiesBySelenium.StandardDemo(address, username, username)
person.main()
......
if __name__ == '__main__':
# 用于pyinstaller 打包多进程程序,否则打包会无法正常运行
import multiprocessing
multiprocessing.freeze_support()
executor = ThreadPoolExecutor(max_workers=3)
with open('datas.csv','r') as f:
datas = f.readlines()
for result in executor.map(singleTask, datas[1:],[datas[0]]*len(datas[1:])):
print("已经完成该账号下任务:".format(result))
调用logging库记录代码中出现的情况
案例代码:
def catch_error(func):
def wrapper(*args, **kw):
my_logger.info("函数{}(),正在运行!".format(func.__name__))
try:
return func(*args, **kw)
except Exception as e:
my_logger.exception(e)
return wrapper
@catch_error
def hello(a):
print(a)
print(1 / 0)
print('last')
import logging
# 1.创建一个logger(日志记录器)对象;
my_logger = logging.Logger("first_logger")
# 2.定义handler(日志处理器),决定把日志发到哪里;
my_handler = logging.FileHandler('test.log',encoding='utf-8')
# 3.设置日志级别(level)和输出格式Formatters(日志格式器)
my_handler.setLevel(logging.INFO)
my_handler.setFormatter(logging.Formatter("时间:%(asctime)s 行号:%(lineno)d 日志信息:%(message)s"))
my_logger.addHandler(my_handler)
总结
个人的第一次遇到js混淆的网站,研究了好久。程序中运用了selenium,requests,bs4,execjs,multiprocessing 等第三方库实现,高性能的对该网站接口实现模拟请求,能够改进点还有很多,仅供学习记录。