某教育网站的接口分析(已失效,仅供学习参考)

提示:第一篇技术博客


前言

例如:记录一次对于某教育网站的接口分析,实现平时作业等功能的自动实现…不是完整教程,只展示核心部分。


一、获取账户登陆的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 等第三方库实现,高性能的对该网站接口实现模拟请求,能够改进点还有很多,仅供学习记录。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经过一个月的迭代开发,终于初步完成酷瓜云课堂的 v1.2.0 版本,此次更新修复了以往版本出现的问题,最主要的是完成了客户端所需要的 API,以后 H5 移动端,小程序,APP 客户端就可以陆续的发布了。 还有一个重大变化就是使用协议的变更,原来的自定协议变更为常见的 GPL2.0 协议,再也不是网友戏称的挂羊头卖狗肉的“假开源”了。 项目介绍 酷瓜云课堂,依托腾讯云基础服务架构,采用 C 扩展框架 Phalcon 开发,GPL-2.0 开源协议,致力开源网课系统,开源网校系统,开源在线教育系统。 系统功能 实现了点播、直播、专栏、会员、微聊等,是一个完整的产品,具体功能我也不想写一大堆,自己体验吧! 托管仓库 gitee 仓库 github 仓库 友情提示 系统配置低(1 核 1G 1M 跑多个容器),切莫压测 课程数据来源于网络(无实质内容),切莫购买 管理后台已禁止数据提交,私密配置已过滤 演示帐号:13507083515 / 123456 (前后台通用) 项目组件 后台框架:phalcon 3.4.5 前端框架:layui 2.5.6, layim 3.9.5(已授权) 全文检索:xunsearch 1.4.9 即时通讯:workerman 3.5.22 基础依赖:php7.3, mysql5.7, redis5.0 安装指南 运行环境搭建 系统服务配置 开发计划 桌面端:进行中 移动端:进行中 小程序:待启动 通过这个项目能学到什么? 项目规划,phalcon,缓存,JWT,即时通讯,全文检索 docker,supervisor,devops git,linux,php,mysql,redis,nginx 有阿里云版吗? 阿里云版规划中,之前阿里云服务过期未续费,所以腾讯云版本先出。 代码有加密吗? 所有代码都公开(授权代码除外,例如 layim),没有所谓的商业版和付费插件。 源码更新日志: 酷瓜云课堂(腾讯云版)v1.2.0 完成客户端数据接口,以及H5移动端 修正退款项目空白以及弹窗自适应 修复编辑器图片上传,增加上传文件身份认证,markdown内容解析 移除Mobile模块,修复API请求章节信息权限问题 修正点击退款404 删除过度设计的api验证, app应用管理 优化调整点播直播
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值