以某一用户名和密码 登录请求脚本_武汉大学评教脚本

a6e6b788bc5ada9d696499b46575b07d.png

武汉大学评教脚本

用 Python 写的脚本,用于 2019 年秋季评教。

Table Of Contents

  1. 前言
  2. 登录
  3. 获取课程信息
  4. 进行评价
  5. 食用方法

1. 前言

啊啊啊啊啊没想到真的写出来了!兴奋!!先上结果图。

fabc22f919be67b33246bcd2397193ba.png
评教结果

其实也不难嘛,总共花了一个晚上和一个上午的时间,约 8 个小时(都怪我太菜,黑脸)。如果手动评教需要 5 分钟,那这次脚本评教所花的时间大概是手动评教的 96 倍,太亏了!不过开心就好啦哈哈哈。

2-4 为码字过程,脚本的使用方法直接看 5。


2. 登录

先上评教网站看看。在地址栏输入 http://s.ugsq.whu.edu.cn/studentpj,发现重定向到 https://cas.whu.edu.cn/authserver/login?service=http%3A%2F%2Fs.ugsq.whu.edu.cn%2Fcaslogin%2F。按快捷键 F12(或Fn + F12)打开开发者工具,切换到 Network 一栏。勾选 Preserve log,这样在页面刷新的时候请求记录还会保留,不会被刷新掉。

1d218e3ea1c4cee7ce9e71c5abc21ea2.png
使用开发者工具探查

输入用户名和密码,点击登录。这时浏览器往后端发送的请求都显示出来了。后缀为 js 和 css 的分别是对 JavaScript 脚本和 CSS 样式的请求,用于网页渲染,与评教无关,忽略即可。筛选后看到 5 个的请求。

93adfdcf8c7a490a7975d0a3938a47ca.png
登录请求

首先点击 login,查看右侧信息栏,选中 Headers 一栏。由 Request URL 看出它带有一个地址栏的参数 service,意思是从 http://s.ugsq.whu.edu.cn/caslogin/ 重定向过来的,一会登录了还要返回评教界面。另外,这是一个 POST 请求,返回码 302 表示重定向,拉到底端看到提交的参数。

ab7e9e704470bdf5a1a42e592c6152da.png
login 请求的参数

username 是学号,password 是登录密码(信息门户登录密码,默认为身份证后 6 位)加密之后的字符串,后面 5 个参数看不懂。看不懂的参数可能是固定参数,也可能是随机生成的,多刷新几次,发现 lt 和 execution 会变。

考虑到这个请求是前面填写表单点击登录时发起的,回到前面填写表单的页面,右键查看源代码。Ctrl + F 搜索 lt,在 156 行处发现跟前面一模一样的 5 个参数!看到它们的类型为 hidden,原来是网页经常用来传递数据的隐藏参数。

细心的朋友可能会注意到,隐藏参数里 lt 与前面请求的 lt 不一样。登录之前在 Elements 中搜索 lt,这里看到的 lt 和请求参数中的 lt 是一致的。我猜想,查看源代码时这些参数进行了更新。

0f7b16eb37e16476ac0fa6e7098953fb.png
隐藏参数

可以看见隐藏参数有 6 个,前面提交的参数只有 5 个,最后一个参数 pwdDefaultEncryptSalt 是用来加密的,一会再提。现在,先从源代码获取隐藏参数的值,再构造 login 请求。

import 

查看 login 请求的返回内容,发现还是登录页面,登录操作失败。别急,这是肯定的,请求里明显对密码进行了加密,我们直接发送明文当然会失败啦。

现在我们重新注意第 6 个隐藏参数 pwdDefaultEncryptSalt,如果发送请求之前对密码加密,那这个用于加密的参数肯定会出现在加密的方法里(function)。但搜索发现,源代码里并没有别的地方出现过 pwdDefaultEncryptSalt,那肯定是导入的 js 文件里了。

f13812f5068b95a8932546d919055a10.png
导入的 js 文件

在源代码 245 行处可以看见导入的 js 文件。第一个文件是 jQuery,与加密无关。第二个文件名称带 min,明显是某个 JavaScript 框架的轻型版本,也与加密无关。后面三个,第一直觉是 encrypt.js,打开它,搜索 pwdDefaultEncryptSalt,无结果。再打开 login.js 和 login-wisedu_v1.0.js,在 login-wisedu_v1.0.js 中找到关键代码。

343fc9b2dfc0e976d4772d25131e203a.png
加密的关键代码

找到 _etd2 的定义,发现它调用 encryptAES 函数对 password 进行加密。

03d3e19ff6d4d44aca84c873d0f8e409.png
调用 encryptAES 函数进行加密

再次搜索,发现 encryptAES 函数在 encrypt.js 中。

7d1d50ec2bc599fe6c5c86699d3c3b9d.png
encrypt.js 源代码节选

此处省略我阅读 encrypt.js 源代码,企图将它翻译成 Python 的一大段时间...

后来我突然醒悟,Python 能不能直接调用 JavaScript?既然它可以调用 Bash,调用 JavaScript 应该也没问题吧?哈哈,前方道路突然光明。

import 

然而,现实比较残酷,还是回到了最初的登录界面。回头看看,发现 5 个请求我只做了 login 一个!逐个查看,caslogin,loginSSO...懵了,这是在干啥?经过一番 Google,大概了解下 CAS 实现单点登录 SSO 的流程。

在这里,http://s.ugsq.whu.edu.cn 是 CAS Client,https://cas.whu.edu.cn 是 CAS Server。我们前面发送的 login 请求就是步骤 3 的认证过程。接下来要做的是步骤 4,获取 CAS Server 传递给浏览器的 service 和 ticket,带 ticket 向 service (即 CAS Client)发送请求。而步骤 5 和步骤 6 是后端实现的,我们不用管。等 CAS Client 收到 CAS Server 传给它的数据(登录成功的页面)后,CAS Client 将数据传给浏览器。这样就成功登录啦。

现在我们实现步骤 4,并模拟浏览器的其他请求。

import requests
from lxml import etree
import execjs

# 参数
username = '2017302580***'  # 学号
password = '******'  # 信息门户密码,默认为身份证后 6 位

# 各种 url
base_addr = 'http://s.ugsq.whu.edu.cn/'
login = 'https://cas.whu.edu.cn/authserver/login?service=http%3A%2F%2Fs.ugsq.whu.edu.cn%2Fcaslogin%2F'
caslogin = base_addr + 'caslogin/'
loginSSO = base_addr + 'loginSSO'
studentpj = base_addr + 'studentpj'

# 使用会话保持 cookie
s = requests.Session()

# 首次请求,获取隐藏参数
start_response = s.get(login)
start_html = etree.HTML(start_response.text, parser=etree.HTMLParser())
lt = start_html.xpath('//*[@id="casLoginForm"]/input[1]/@value')[0]
dllt = start_html.xpath('//*[@id="casLoginForm"]/input[2]/@value')[0]
execution = start_html.xpath('//*[@id="casLoginForm"]/input[3]/@value')[0]
_eventId = start_html.xpath('//*[@id="casLoginForm"]/input[4]/@value')[0]
rmShown = start_html.xpath('//*[@id="casLoginForm"]/input[5]/@value')[0]
pwdDefaultEncryptSalt = start_html.xpath('//*[@id="casLoginForm"]/input[6]/@value')[0]

# 调用 JavaScript 对密码加密
with open('encrypt.js', 'r') as f:
    js = f.read()
ctx = execjs.compile(js)
password = ctx.call('encryptAES', password, pwdDefaultEncryptSalt)

# 登录
data = {
    'username': username,
    'password': password,
    'lt': lt,
    'dllt': dllt,
    'execution': execution,
    '_eventId': _eventId,
    'rmShown': rmShown
}
login_response = s.post(login, data=data, allow_redirects=False)  # 重定向请求必须加上 allow_redirects=False

# 获取 Location,向 CAS 客户端发送请求
url = login_response.headers['Location']
s.get(url, allow_redirects=False)

# 对照浏览器执行相同的请求
s.get(caslogin)
s.post(loginSSO, data={'userId': username})
studentpj_response = s.get(studentpj)
print(studentpj_response.text)

这时输出了很长的 html 文本,可以写到 html 文件后用浏览器查看,但我比较喜欢直接在控制台看。出现了我的名字,登录成功!

c4fdada0755bb04578df0aa4e37bdb02.png
登录成功

3. 获取课程信息

此时我们来到了这个界面。

b1da01e5ac5d5cc97296a22bf2ab4962.png
登录成功后的评教界面

评价之前,我们首先要获取课程信息,比如我这学期选了韩波老师的系统级程序设计,获取到这门课的信息之后才能对它进行评价。点击框框查看课程列表。如果开着开发者工具会出现一个断点,点击继续即可。

3730577d0a89466e9f0b928cb1594b33.png
遇到断点时点击继续

观察 Network 中的请求,多出了 getkcpmid 和 evaluate2.jsp,用脚本进行模拟。

import requests
from lxml import etree
import execjs

# 参数
username = '2017302580***'  # 学号
password = '******'  # 信息门户密码,默认为身份证后 6 位
some_id = '41'  # 某个不知道干啥用的 id

# 各种 url
base_addr = 'http://s.ugsq.whu.edu.cn/'
login = 'https://cas.whu.edu.cn/authserver/login?service=http%3A%2F%2Fs.ugsq.whu.edu.cn%2Fcaslogin%2F'
caslogin = base_addr + 'caslogin/'
loginSSO = base_addr + 'loginSSO'
studentpj = base_addr + 'studentpj'
getkcpmid = base_addr + 'getkcpmid'
evaluate2 = base_addr + 'new/student/rank/evaluate2.jsp'

# 使用会话保持 cookie
s = requests.Session()

# 首次请求,获取隐藏参数
start_response = s.get(login)
start_html = etree.HTML(start_response.text, parser=etree.HTMLParser())
lt = start_html.xpath('//*[@id="casLoginForm"]/input[1]/@value')[0]
dllt = start_html.xpath('//*[@id="casLoginForm"]/input[2]/@value')[0]
execution = start_html.xpath('//*[@id="casLoginForm"]/input[3]/@value')[0]
_eventId = start_html.xpath('//*[@id="casLoginForm"]/input[4]/@value')[0]
rmShown = start_html.xpath('//*[@id="casLoginForm"]/input[5]/@value')[0]
pwdDefaultEncryptSalt = start_html.xpath('//*[@id="casLoginForm"]/input[6]/@value')[0]

# 调用 JavaScript 对密码加密
with open('encrypt.js', 'r') as f:
    js = f.read()
ctx = execjs.compile(js)
password = ctx.call('encryptAES', password, pwdDefaultEncryptSalt)

# 登录
data = {
    'username': username,
    'password': password,
    'lt': lt,
    'dllt': dllt,
    'execution': execution,
    '_eventId': _eventId,
    'rmShown': rmShown
}
login_response = s.post(login, data=data, allow_redirects=False)  # 重定向请求必须加上 allow_redirects=False

# 获取 Location,向 CAS 客户端发送请求
url = login_response.headers['Location']
s.get(url, allow_redirects=False)

# 对照浏览器执行相同的请求
s.get(caslogin)
s.post(loginSSO, data={'userId': username})
studentpj_response = s.get(studentpj)

# 进入课程列表界面
data = {
    'hdid': some_id,
    'xh': username
}
s.post(getkcpmid, data=data)
params = {
    'hdfaid': some_id,
    'overtime': 'timeNO',
    'sfkdcpj': '1',
    'sfqxzdf': '0',
    'zbtx': '267,268,266,265',
    'kkxy': '2302000',
    'roid': 'SCHOOL_ADMIN'
}
evaluate2_response = s.get(evaluate2, params=params)
with open('test.html', 'w', encoding='utf8') as f:
    f.write(evaluate2_response.text)

为了演示更直观,我将结果写进 html 文件,发现只获取了页面的框架,没有课程的信息。

d60a85390f2dbe4e567ee46039ab9327.png
页面框架

没有 js 和 css 的页面很丑吧?这就是我一般只在控制台查看响应结果的原因...再仔细观察,发现 SCHOOL_ADMIN 请求,它是 XHR,XHR 用于动态更新网页[1]

052102c801f361a0f76aa220face723a.png
SCHOOL_ADMIN 请求课程信息

SCHOOL_ADMIN 的参数直接从浏览器复制,转换成 Python 里的字典就行。模拟 SCHOOL_ADMIN,顺便对课程信息进行整理。

import requests
from lxml import etree
import execjs
import json

# 参数
username = '2017302580***'  # 学号
password = '******'  # 信息门户密码,默认为身份证后 6 位
some_id = '41'  # 某个不知道干啥用的 id
kkxy = '2302000'  # 某个不知道干啥用的 key

# 各种 url
base_addr = 'http://s.ugsq.whu.edu.cn/'
login = 'https://cas.whu.edu.cn/authserver/login?service=http%3A%2F%2Fs.ugsq.whu.edu.cn%2Fcaslogin%2F'
caslogin = base_addr + 'caslogin/'
loginSSO = base_addr + 'loginSSO'
studentpj = base_addr + 'studentpj'
getkcpmid = base_addr + 'getkcpmid'
evaluate2 = base_addr + 'new/student/rank/evaluate2.jsp'
SCHOOL_ADMIN = base_addr + 'getStudentPjPf/' + some_id + '/' + kkxy + '/SCHOOL_ADMIN'

# 使用会话保持 cookie
s = requests.Session()

# 首次请求,获取隐藏参数
start_response = s.get(login)
start_html = etree.HTML(start_response.text, parser=etree.HTMLParser())
lt = start_html.xpath('//*[@id="casLoginForm"]/input[1]/@value')[0]
dllt = start_html.xpath('//*[@id="casLoginForm"]/input[2]/@value')[0]
execution = start_html.xpath('//*[@id="casLoginForm"]/input[3]/@value')[0]
_eventId = start_html.xpath('//*[@id="casLoginForm"]/input[4]/@value')[0]
rmShown = start_html.xpath('//*[@id="casLoginForm"]/input[5]/@value')[0]
pwdDefaultEncryptSalt = start_html.xpath('//*[@id="casLoginForm"]/input[6]/@value')[0]

# 调用 JavaScript 对密码加密
with open('encrypt.js', 'r') as f:
    js = f.read()
ctx = execjs.compile(js)
password = ctx.call('encryptAES', password, pwdDefaultEncryptSalt)

# 登录
data = {
    'username': username,
    'password': password,
    'lt': lt,
    'dllt': dllt,
    'execution': execution,
    '_eventId': _eventId,
    'rmShown': rmShown
}
login_response = s.post(login, data=data, allow_redirects=False)  # 重定向请求必须加上 allow_redirects=False

# 获取 Location,向 CAS 客户端发送请求
url = login_response.headers['Location']
s.get(url, allow_redirects=False)

# 对照浏览器执行相同的请求
s.get(caslogin)
s.post(loginSSO, data={'userId': username})
studentpj_response = s.get(studentpj)

# 进入课程列表界面
data = {
    'hdid': some_id,
    'xh': username
}
s.post(getkcpmid, data=data)
params = {
    'hdfaid': some_id,
    'overtime': 'timeNO',
    'sfkdcpj': '1',
    'sfqxzdf': '0',
    'zbtx': '267,268,266,265',
    'kkxy': '2302000',
    'roid': 'SCHOOL_ADMIN'
}
evaluate2_response = s.get(evaluate2, params=params)

# 获取课程列表数据
data = {
    'sEcho': '1',
    'iColumns': '6',
    'sColumns': '',
    'iDisplayStart': '0',
    'iDisplayLength': '10',
    'mDataProp_0': 'KCMC',
    'mDataProp_1': 'XM',
    'mDataProp_2': 'TJSJ',
    'mDataProp_3': 'YZ',
    'mDataProp_4': 'PJJGID',
    'mDataProp_5': '',
    'iSortCol_0': '0',
    'sSortDir_0': 'asc',
    'iSortingCols': '1',
    'bSortable_0': 'false',
    'bSortable_1': 'false',
    'bSortable_2': 'false',
    'bSortable_3': 'false',
    'bSortable_4': 'false',
    'bSortable_5': 'false'
}
SCHOOL_ADMIN_response = s.post(SCHOOL_ADMIN, data=data)
result = json.loads(SCHOOL_ADMIN_response.text)
all = result['iTotalRecords']  # 总课程数
count = 0  # 已评价的课程数
print('共有 {} 门课需要评价:'.format(all))
kc_list = result['aaData']
for kc in kc_list:
    if kc['TJSJ']:
        count += 1
        print('教务系统课程号:{},课程名称:{}({}),课程类型:{},教师姓名:{}({}),评价提交时间:{},一级指标得分:{},评价结果:{}'.format(
            kc['JXBDM'], kc['KCMC'], kc['KCH'], kc['KCLX'], kc['XM'], kc['GH'], kc['TJSJ'], kc['YZ'], kc['ZPDF']))
    else:
        print('教务系统课程号:{},课程名称:{}({}),课程类型:{},教师姓名:{}({}),未评价'.format(
            kc['JXBDM'], kc['KCMC'], kc['KCH'], kc['KCLX'], kc['XM'], kc['GH']))
print('已评价:{} 门 未评价:{} 门'.format(count, all - count))

结果如下。

e24d7185dac1700c5f0a6407d9a19fd5.png
课程信息

4. 进行评价

先手动评价一门课程,观察到 createStudentPjpf 请求。提交的参数是题号,对应的打分,和课程信息,学生信息。题号和对应的打分直接复制浏览器的参数即可,课程信息和学生信息传入上一步获取到的值。再加一个 if 判断,只对未评价的课程进行评价。

对了,注意到题号和打分参数有“一个键对应多个值”的情况,不能用字典传参,换成二元元组组成的列表。拷贝浏览器参数再转换成 Python 的格式有点麻烦,这次我是纯手工转的,可以考虑造个轮子。

为查看脚本运行结果,在所有课程都评价之后,重新获取课程信息,可以看见所有课程都已评价。再加上 click 传参,完美!

# coding=utf-8
import requests
from lxml import etree
import execjs
import json
import click

# 参数
some_id = '41'  # 某个不知道干啥用的 id
kkxy = '2302000'  # 某个不知道干啥用的 key
lt = ''
dllt = ''
execution = ''
_eventId = ''
rmShown = ''

# 各种 url
base_addr = 'http://s.ugsq.whu.edu.cn/'
login = 'https://cas.whu.edu.cn/authserver/login?service=http%3A%2F%2Fs.ugsq.whu.edu.cn%2Fcaslogin%2F'
caslogin = base_addr + 'caslogin/'
loginSSO = base_addr + 'loginSSO'
studentpj = base_addr + 'studentpj'
getkcpmid = base_addr + 'getkcpmid'
evaluate2 = base_addr + 'new/student/rank/evaluate2.jsp'
SCHOOL_ADMIN = base_addr + 'getStudentPjPf/' + some_id + '/' + kkxy + '/SCHOOL_ADMIN'
createStudentPjpf = base_addr + 'createStudentPjpf'

# 使用会话保持 cookie
s = requests.Session()


# 获取课程信息
def get_kc(username, password):

    # 登录
    data = {
        'username': username,
        'password': password,
        'lt': lt,
        'dllt': dllt,
        'execution': execution,
        '_eventId': _eventId,
        'rmShown': rmShown
    }
    login_response = s.post(login, data=data, allow_redirects=False)  # 重定向请求必须加上 allow_redirects=False

    # 获取 Location,向 CAS 客户端发送请求
    url = login_response.headers['Location']
    s.get(url, allow_redirects=False)

    # 对照浏览器执行相同的请求
    s.get(caslogin)
    s.post(loginSSO, data={'userId': username})
    s.get(studentpj)

    # 进入课程列表界面
    data = {
        'hdid': some_id,
        'xh': username
    }
    s.post(getkcpmid, data=data)
    params = {
        'hdfaid': some_id,
        'overtime': 'timeNO',
        'sfkdcpj': '1',
        'sfqxzdf': '0',
        'zbtx': '267,268,266,265',
        'kkxy': '2302000',
        'roid': 'SCHOOL_ADMIN'
    }
    s.get(evaluate2, params=params)

    # 获取课程列表数据
    data = {
        'sEcho': '1',
        'iColumns': '6',
        'sColumns': '',
        'iDisplayStart': '0',
        'iDisplayLength': '10',
        'mDataProp_0': 'KCMC',
        'mDataProp_1': 'XM',
        'mDataProp_2': 'TJSJ',
        'mDataProp_3': 'YZ',
        'mDataProp_4': 'PJJGID',
        'mDataProp_5': '',
        'iSortCol_0': '0',
        'sSortDir_0': 'asc',
        'iSortingCols': '1',
        'bSortable_0': 'false',
        'bSortable_1': 'false',
        'bSortable_2': 'false',
        'bSortable_3': 'false',
        'bSortable_4': 'false',
        'bSortable_5': 'false'
    }
    SCHOOL_ADMIN_response = s.post(SCHOOL_ADMIN, data=data)
    result = json.loads(SCHOOL_ADMIN_response.text)
    all = result['iTotalRecords']  # 总课程数
    count = 0  # 已评价的课程数
    print('共有 {} 门课需要评价:'.format(all))
    kc_list = result['aaData']
    for kc in kc_list:
        if kc['TJSJ']:
            count += 1
            print('教务系统课程号:{},课程名称:{}({}),课程类型:{},教师姓名:{}({}),评价提交时间:{},一级指标得分:{},评价结果:{}'.format(
                kc['JXBDM'], kc['KCMC'], kc['KCH'], kc['KCLX'], kc['XM'], kc['GH'], kc['TJSJ'], kc['YZ'], kc['ZPDF']))
        else:
            print('教务系统课程号:{},课程名称:{}({}),课程类型:{},教师姓名:{}({}),未评价'.format(
                kc['JXBDM'], kc['KCMC'], kc['KCH'], kc['KCLX'], kc['XM'], kc['GH']))
    print('已评价:{} 门 未评价:{} 门'.format(count, all - count))

    return kc_list


# 进行评价
def pingjia(kc):
    if not kc['TJSJ']:
        data = [
            ('dxid', '908'),
            ('dxid', '909'),
            ('dxid', '910'),
            ('dxid', '911'),
            ('dxid', '912'),
            ('dxid', '913'),
            ('dxid', '914'),
            ('dxid', '915'),
            ('dxid', '916'),
            ('dxid', '917'),
            ('dxid', '918'),
            ('dxid', '919'),
            ('dxid', '920'),
            ('dxid', '921'),
            ('dxid', '922'),
            ('dxid', '923'),
            ('dxid', '924'),
            ('dxid', '925'),
            ('dxid', '926'),
            ('dxid', '927'),
            ('dxid', '928'),
            ('dxid', '929'),
            ('dxvalue', '10'),
            ('dxvalue', '5'),
            ('dxvalue', '5'),
            ('dxvalue', '5'),
            ('dxvalue', '5'),
            ('dxvalue', '5'),
            ('dxvalue', '5'),
            ('dxvalue', '7'),
            ('dxvalue', '7'),
            ('dxvalue', '7'),
            ('dxvalue', '8'),
            ('dxvalue', '8'),
            ('dxvalue', '8'),
            ('dxvalue', '2'),
            ('dxvalue', '3'),
            ('dxvalue', '2'),
            ('dxvalue', '3'),
            ('dxvalue', '3'),
            ('dxvalue', '3'),
            ('dxvalue', '3'),
            ('dxvalue', '3'),
            ('dxvalue', '3'),
            ('sfjft', '1'),
            ('sfjft', '1'),
            ('sfjft', '1'),
            ('sfjft', '1'),
            ('sfjft', '1'),
            ('sfjft', '1'),
            ('sfjft', '1'),
            ('sfjft', '1'),
            ('sfjft', '1'),
            ('sfjft', '1'),
            ('sfjft', '1'),
            ('sfjft', '1'),
            ('sfjft', '1'),
            ('sfjft', '1'),
            ('sfjft', '1'),
            ('sfjft', '1'),
            ('sfjft', '1'),
            ('sfjft', '1'),
            ('sfjft', '1'),
            ('sfjft', '1'),
            ('sfjft', '1'),
            ('sfjft', '1'),
            ('wdid', '930'),
            ('wdid', '931'),
            ('wdid', '932'),
            ('wdid', '933'),
            ('wdvalue', '0'),
            ('wdvalue', '0'),
            ('wdvalue', '0'),
            ('wdvalue', '无'),
            ('rwid', some_id),  # 不知道是啥,41
            ('xqid', kc['XQID']),  # 不知道是啥,12
            ('jsgh', kc['GH']),  # 教师工号
            ('kch', kc['KCH']),  # 课程号
            ('bzxh', kc['BZXH']),  # 不知道是啥,None
            ('jxbdm', kc['JXBDM']),  # 教务系统课程号
            ('xsxh', kc['XH']),  # 学号
            ('zf', '100.00'),
            ('pjjgid', kc['PJJGID'])  # 评价结果 id
        ]
        createStudentPjpf_response = s.post(createStudentPjpf, data=data)
        if createStudentPjpf_response.text == '{}':
            print('{}评价成功'.format(kc['KCMC']))


@click.command()
@click.option('--username', prompt='学号')
@click.option('--password', prompt='信息门户密码(默认身份证后 6 位)')
def pingjiao(username, password):
    global lt
    global dllt
    global execution
    global _eventId
    global rmShown

    # 首次请求,获取隐藏参数
    start_response = s.get(login)
    start_html = etree.HTML(start_response.text, parser=etree.HTMLParser())
    lt = start_html.xpath('//*[@id="casLoginForm"]/input[1]/@value')[0]
    dllt = start_html.xpath('//*[@id="casLoginForm"]/input[2]/@value')[0]
    execution = start_html.xpath('//*[@id="casLoginForm"]/input[3]/@value')[0]
    _eventId = start_html.xpath('//*[@id="casLoginForm"]/input[4]/@value')[0]
    rmShown = start_html.xpath('//*[@id="casLoginForm"]/input[5]/@value')[0]
    pwdDefaultEncryptSalt = start_html.xpath('//*[@id="casLoginForm"]/input[6]/@value')[0]

    # 调用 JavaScript 对密码加密
    with open('encrypt.js', 'r') as f:
        js = f.read()
    ctx = execjs.compile(js)
    password = ctx.call('encryptAES', password, pwdDefaultEncryptSalt)

    kc_list = get_kc(username, password)
    for kc in kc_list:
        pingjia(kc)
    get_kc(username, password)


if __name__ == '__main__':
    pingjiao()

结果图见前言哈。


5. 食用方法

参见 GitHub。

参考

  1. ^维基百科编者. XMLHttpRequest[G/OL]. 维基百科, 2019(20190212)[2019-02-12]. https://zh.wikipedia.org/zh-cn/XMLHttpRequest
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值