合肥工业大学教务系统选课分为三个阶段,第一个阶段不论人数,所有课程均可选择,由系统随机筛选中选学生(强烈吐槽!连续两年选课6门中1门,运气差到爆)。第二和第三阶段都是手动抢课,先到先得。
脚本在第二第三阶段有效,其原理就是不停监控课程已选学生数,一旦有空缺,就立即选课。而这空缺,一般是某些童鞋退课之后产生的空缺,或者相互交换课程时那一秒钟的空缺。
好了开始讲解令人激动的部分,脚本有两种形式,一种直接通过浏览器的控制台,使用JavaScript操作选课页面,不停刷新页面监控课程状态,不过这种方法运行久了之后比较吃内存,而且刷新速度一般(然而仍然比手动刷新快),但是操作简单,适合小白。另一种使用Python,资源消耗小,刷新速度快,但是有门槛。
JavaScript
var i=0;
function cir(){
var course = document.querySelectorAll('[data-id="262589"]')[0];
var status_course = document.querySelectorAll('[div="not-show-count\.text-danger"],[limit-count="300"]')[0];
var closebtn = document.querySelectorAll('[data-dismiss="modal"],[class="btn btn-default close-modal"]')[2];
if(status_course.innerText=="未满"){
course.click();
setTimeout(closebtn.click(),1500);
}
var next_page = document.querySelectorAll("a.page-link")[2];
next_page.click();
setTimeout(console.log(i++),1000);
var last_page = document.querySelectorAll("a.page-link")[1];
last_page.click();
}
setInterval(cir,2000);
详细解释:
- 变量 i 用来记录监控次数,
console.log
在控制台输出,不然看不到运行状态.setInterval(cir,2000);
即每2000ms运行一次函数cir()
. - 函数内部:
document.querySelectorAll('[data-id="262589"]')[0];
这是个选择器,筛选data-id
为262589的元素,此处需更改id,更改方法下一段介绍. 该语句返回一个NodeList,包含一个元素–该课程对应的“选课”按钮。 status_course
是该门课程的状态,选择div="not-show-count.text-danger",limit-count="300"
属性的元素,即当前容量下的“已满”或者“未满”。
点击选课按钮之后页面会出现弹窗,提示选课成功或者课程已满等等,弹窗有个关闭按钮,这就是closebtn,应该不需要更改(除非属性名称被改掉了,建议检查一下)。
if块的内部就是判断当前容量是已满还是未满,未满则点击“选课”按钮,并在1500ms之后点击弹窗上的关闭按钮
setTimeout(closebtn.click(),1500);
由于刷新页面会导致筛选课程里面填写的关键词消失,页面回到全校选课的首页,并且页面自动获取当前容量的信息间隔太长,因此刷新方法是点击页面底部的下一页,再点击上一页,完成刷新(确保使用合适的关键词,保证筛选到的课程有两页或以上).
var next_page = document.querySelectorAll("a.page-link")[2];
这行就是筛选下一页的按钮,next_page.click();
并点击。setTimeout(console.log(i++),1000);
这是在控制台输出,记录次数(运行时间过长吃内存或许是这个原因,可尝试在i为100或1000的整数倍时输出). 这里为什么要延时输出呢?其实我也不记得了。。
这样虽然看起来很麻烦,但是效率还可以,提高效率的方法是使用合适的关键词,使得该页的下一页也有需要抢的课程,这样就能在点击下一页之后,抢下一页的课程(这几乎不费时间)。
更改代码:
- course筛选时使用的
data-id
. 在需要抢的那门课的选课按钮上,右击,检查(Chrome)或者查看元素(Firefox)。元素可能会折叠,展开仔细查找data-id
项,双击复制即可。 status_course
筛选时使用的div="not-show-count.text-danger", limit-count="300"
. 在未满或者已满文字上右击,检查,没记错的话,应该出现的就是div折叠项目,展开div,检查div属性值是否正确. 将div项下能展开的都展开,找到limit-count
属性,双击复制值即可.- closebtn同理. 随便点击一个选课按钮,在弹窗上右击,检查属性值
next_page last_page
在页面底部,箭头图标上右击,检查属性。- 延时以及循环间隔自己调整,循环低于1500,浏览器页面加载未完成,会导致点击失败,具体自己尝试.
- 控制台输入
document.querySelectorAll('[div="not-show-count.text-danger"],[limit-count="300"]')
即使没有筛选到元素也会返回空列表,如果不好判断,使用document.querySelectorAll('[div="not-show-count.text-danger"],[limit-count="300"]') [0]
,如果返回undefined,则表示没有筛选到元素,检查属性值是否正确. - 关于status_course,很重要
document.querySelectorAll('[div="not-show-count.text-danger"],[limit-count="300"]')[0];
一般第0个元素就是目标课程,如果页面里含有多个limit-count="300"
属性的元素,那么这句代码返回的是一个含有多个元素的列表,在控制台输入document.querySelectorAll('[div="not-show-count.text-danger"],[limit-count="300"]')
,(300更改为目标课程的课程人数限制)回车之后返回NodeList,展开将鼠标放到List下每个元素上时,选课页面对应的课程就会高亮显示,以此判断目标课程是第几个元素,据此修改[0]
。类似这样:
Python
import requests
import time
headers = {
'User-agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36',
'Host':'jxglstu.hfut.edu.cn'
}
session = requests.Session()
session.verify = False
lessonId = ["299781"]
limit_count = 100
studentAssoc = "91160"
courseSelectTurnAssoc = "422"
virtualCost = "0"
cookies = {"SESSION": "79f3c2bb-84bf-46cc-8d0d-2232c8c25c2d", "SRVID": "s110"}
response = session.get("http://jxglstu.hfut.edu.cn/eams5-student/home", headers=headers, cookies=cookies)
print(response.status_code)
response = session.get("http://jxglstu.hfut.edu.cn/eams5-student/for-std/course-select/" + studentAssoc + "/turn/" + courseSelectTurnAssoc +"/select", headers=headers, cookies=cookies)
print(response.status_code)
i = 0
std_count = 0
while 1:
i = i+1
response = session.post("http://jxglstu.hfut.edu.cn/eams5-student/ws/for-std/course-select/std-count",
data={"lessonIds[]": lessonId}, headers=headers, cookies=cookies)
# print(response.text)
std_count = eval(response.text)[lessonId[0]]
print(str(std_count) + '+' + str(i), end="\n" if i % 100 == 0 else " ")
if std_count <= limit_count:
data = {'studentAssoc': "91160", 'lessonAssoc': lessonId[0], 'courseSelectTurnAssoc': courseSelectTurnAssoc,
'scheduleGroupAssoc': '', 'virtualCost': virtualCost}
response = session.post("http://jxglstu.hfut.edu.cn/eams5-student/ws/for-std/course-select/add-request",
headers=headers, data=data, cookies=cookies)
print(response.text, end="\n\n\n\n\n\n")
time.sleep(0.5)
不详解,会的自然懂,不会的,看看requests库。讲几个注意事项:
流程:使用cookie登录信息门户,发送请求获取对应课程id的已选课学生数std_count,如果小于课程人数限制limit_count,即可发送选课请求,完成抢课.
lessonId需更改,即JavaScript部分中course的data-id属性值,limit_count即status_course的limit-count属性值.
studentAssoc
是每个学生对应的序号,查看方法:打开信息门户的本科教务页面,点击任一项,比如“课表”,查看网址栏,最后的几位数字91160就是studentAssoc
courseSelectTurnAssoc
这是选课的轮次,选课未开放,无法查看。可在选课开放之后,使用Fiddle抓取点击选课按钮之后发送的数据包,webform一栏有courseSelectTurnAssoc
的值.virtualCost
似乎没什么用,不用更改.cookies字典下的两个键值修改. 登录选课页面,右击,检查,Application,Cookies,找到Domain为
jxglstu.hfut.edu.cn
的两个cookie(或更多),复制Value,替换代码中的值即可.同时抢多门课. 添加课程代码到lessonId列表中,并在while循环内添加判断. 自行调试.
延时建议0.5s以上,太快会导致服务器返回错误数据.
结尾:
本教程仅供学习使用,作者不承担使用者因滥用造成的一切后果. 谨慎使用