2018.3.7 Update
分析
http://elite.nju.edu.cn/jiaowu/student/elective/readRenewCourseList.do?type=1
观察课程补选的网址我们发现整个选课程序应该是依赖于一个CourseList.do的文件,后面的type应该是几门不同分类的经典阅读。选课时点击选择就可以直接选课。
因此主要有两个思路:
- 1.用chrome的Tampermonkey插件,把表单中已选和限额两个数字提取出来,然后决定是否是否点击选择。
- 2.用chrome控制台拦截下选课时对服务器的请求,用Python不断发送请求
Tampermonkey尝试 (Failed)
简介
Tampermonkey是一个基于各大浏览器的插件,主要是读取web上的元素,并执行一些操作,例如:改变元素css属性,新增元素,或者进行点击、刷新等操作。
开启debugger模式
debugger是Tampermonkey脚本里面的一条命令,和断点作用相同,开启方式 在此不再赘述
提取元素
打开chrome控制台,找到限额、已选、选择的element:
从中我们可以看到,整个表格的id为tbCourseList,其中每一行为一个tr,行中每一个元素为一个td
对应js脚本为:
// ==UserScript==
// @name New Userscript
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match http://elite.nju.edu.cn/jiaowu/student/elective/readRenewCourseList.do?type=2
// @grant none
//加入JQuery
// @require http://code.jquery.com/jquery-1.11.0.min.js
// ==/UserScript==
(function() {
'use strict';
debugger;
//对tbCourseList中每个tr元素执行操作
$('table tbCourseList').find("tbody").find("tr").each(function(){
debugger;
//tr的第4个元素为为课程限额人数
var courseNum = $(this).children("td").eq(4).text();
//tr的第5个元素为已选人数
var selecNum = $(this).children("td").eq(5).text();
if(courseNum>selecNum){
console.log(selecNum);
}
else{
console.log("hello");
}
}
);
})();
报错
但是实际执行的时候出现了错误:
也就是说表单并没有加载出来就执行了脚本,所以无法找到table元素,关闭脚本刷新后刷新页面:
发现负责加载选课列表的coureseList.do是在800ms之后加载的,因此需要保证js脚本在800ms之后执行,因此在开头加入检测tbCourseList是否加载完成的函数:
$("tbCourseList").on("load", function () {
console.log("loadFinished");
});
但是实际使用中发现并没有起效,依旧在tbCourseList加载出来之前执行了脚本,tampermonkey尝试以失败告终
想法
其实主要还是对JS语法不熟悉(感觉JS语法和之前用过的所有语言差别太大了……),如果有熟悉JS语法的朋友欢迎与我交流。
写这篇Blog的时候又有一个想法,既然在800ms之后加载,那能不能直接在开头写一个延时2s的函数?不过既然已经拿python写完了,我也实在无心再尝试了。【但是不能否认这种方法会比python安全,走前段的话无论url,验证等等怎么变,只要学生能选课,这种方式就能抢课
python尝试(Succeed)
流程分析
整个选课流程主要分为三步:
1.登录教务网(有验证码)
2.进入选课列表
3.递交选课请求
向对应的python的程序流程应该为:
1.获取验证码图片,人工/CV识别之后递交验证码,保留成功cookie
2.用成功的cookie登录教务,并转到选课页面
3.chrome拦截选课请求,然后用python requests模拟发送请求
获取信息
在教务登录页面中用控制台拦截到验证码地址为:
拦截到登录请求为:
(data中为学号,密码,验证码)
拦截到选课请求为:
(data中为课程号,类型)
程序
个人觉得requests是接触到的python最伟大的一个库,以后urlib之类的库应该废掉了,不多说,上代码:
import requests
import pytesseract
import Image
import time
# -*- coding: utf-8 -*-
#验证码自动识别函数(二值法),准确率有待提高,暂不使用
# def varify():
# img = Image.open('Code.png')
# pix = img.load()
# for x in range(img.size[0]):
# pix[x, 0] = pix[x, img.size[1] - 1] = (255, 255, 255, 255)
# for y in range(img.size[1]):
# pix[0, y] = pix[img.size[0] - 1, y] = (255, 255, 255, 255)
# for y in range(img.size[1]):
# for x in range(img.size[0]):
# if pix[x, y][0] < 95 or pix[x, y][1] < 95 or pix[x, y][2] < 95:
# pix[x, y] = (0, 0, 0, 255)
# else:
# pix[x, y] = (255, 255, 255, 255)
# # img.convert('RGB')
# img.save("temp.jpg",format("jpeg"))
# text = pytesseract.image_to_string(Image.open('temp.jpg'))
# if len(text) == 4:
# text.replace('l', '1')
# return text
# else:
# return raw_input()
codeUrl = 'http://elite.nju.edu.cn/jiaowu/ValidateCode.jsp'
code = requests.get(url=codeUrl)
cookies = code.cookies#保存验证码cookies
f = open('Code.png', 'wb')
f.write(code.content)#按照content(二进制)写入
f.close()
codeNum = raw_input()#手动打开Code.png输入验证码
jw = requests.session()#开始登录教务
data = {'userName': "学号",
'password': '密码什么的怎么可能给你们',
'returnUrl': 'null',
'ValidateCode': codeNum}
jwRes = jw.post("http://elite.nju.edu.cn/jiaowu/login.do", data=data, cookies=cookies)
#假装进入读取课程列表,其实去了这步大概也可以?
readCourse = requests.post("http://elite.nju.edu.cn/jiaowu/student/elective/readRenewCourseList.do?type=2", cookies=cookies)
print readCourse.status_code
#开始构建选课请求
a = requests.session()
#我怀疑其实都不需要header……
a.headers = {
'Accept': 'text/javascript, text/html, application/xml, text/xml, */*',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'zh-CN,zh;q=0.8',
'Connection': 'keep-alive',
'Content-Length': '36',
'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Host': 'elite.nju.edu.cn',
'Origin': 'http://elite.nju.edu.cn',
'Referer': 'http://elite.nju.edu.cn/jiaowu/student/elective/readRenewCourseList.do?type=1',
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36',
'X-Prototype-Version': '1.5.1',
'X-Requested-With': 'XMLHttpRequest'
}
#data包含抢课的classid,type,method
data0={
'method':'readRenewCourseSelect',
'classid':78446,
'type':1
}
data1={
'method':'readRenewCourseSelect',
'classid':78448,
'type':1
}
data2={
'method':'readRenewCourseSelect',
'classid':78452,
'type':1
}
data3={
'method':'readRenewCourseSelect',
'classid':78453,
'type':1
}
data4={
'method':'readRenewCourseSelect',
'classid':78479,
'type':4
}
data5={
'method':'readRenewCourseSelect',
'classid':78480,
'type':4
}
i = 0#计算选课次数
while(1):
t = a.post("http://elite.nju.edu.cn/jiaowu/student/elective/courseList.do", cookies=cookies,data=data0)
time.sleep(0.02)
print t.status_code
t = a.post("http://elite.nju.edu.cn/jiaowu/student/elective/courseList.do", cookies=cookies, data=data1)
time.sleep(0.02)
t = a.post("http://elite.nju.edu.cn/jiaowu/student/elective/courseList.do", cookies=cookies, data=data2)
time.sleep(0.02)
t = a.post("http://elite.nju.edu.cn/jiaowu/student/elective/courseList.do", cookies=cookies, data=data3)
time.sleep(0.02)
t = a.post("http://elite.nju.edu.cn/jiaowu/student/elective /courseList.do", cookies=cookies, data=data4)
time.sleep(0.02)
t = a.post("http://elite.nju.edu.cn/jiaowu/student/elective/courseList.do", cookies=cookies, data=data5)
#理所应当的,jw没对请求频率有任何限制,加入time.sleep只是我怕jw Log崩了【选课请求这种事大概是不需要log的?
time.sleep(0.3)
i=i+1
print t.text#教训,还是应该看一下抢课response到底是要求重新登录,还是该课程已满
总结
目前整个教务系统都采用明文传递,验证码其实可有可无,所有依赖登录的服务都可以让用户自己填写来解决。
偶然发现NJU小助手不用验证码就获取成绩信息,比较好奇是怎么做到的,有时间的话想拦包看一眼,最近有时间的话会把抢课程序整理一下,部署到web,没时间的话就等下学期吧。
很多时候,很多事情都会背离最初的初衷,就像写这个程序一样,实际上我没有靠它就选到了想要的课,所以写下这篇Blog更多的可能是满足自己的成就感以及破败的虚荣心吧。
最后感谢python,去tmd JS
写在这里的话就没人可以看到了,笑,就像所有仅自己可见一样,只是说些无人可以聆听的话。我大概知道什么时候喜欢上你的,这次终于清楚自己喜欢你什么,233,我内心竟然有些期盼你早些去鼓楼,这样也许就可以早些告白,早些释怀。
真的很悲哀,喜欢上不适合,对自己不感兴趣的人,但是仔细想想,又有谁会喜欢我呢……又有谁会对我这种“有毒”的人感兴趣呢?
就这样吧,愿尽快了结,我这辈子,也许真的不配收获什么爱情。
【可是,写下这句话的时候,怎么会没有一丝妄想呢。】
Comments | 2 条评论
图 404 啦
@Bwoywan 当年用的kali一个md软件的自带图床,后来才知道是临时图床🙃原图我也找不到了