用Python脚本实现刷课
2016年02月24日 标签:Programming, 北京大学, Python, 选课
https://blog.buriedjet.com/python-shuake/
本文使用Chrome浏览器,所有代码在Python 3.5.1下正常执行。
用按键精灵更简单,可以咨询会用的同学
此文章及代码仅供学习⚣交流使用,请自觉遵守学校规定,刷课后果自负/滑稽。
02.25日更新 续
基本原理
自动选课实现原理
登录选课网,进入退补选界面(补选退选)。在可以补选的课程的补选按钮上点击右键->检查,可以看到,这实际上是一个链接。
补选
可以看到,在点击补选链接时,系统先调用confirmSelect函数弹出确认窗口,用户确认后,进入链接:
http://elective.pku.edu.cn/elective2008/edu/pku/stu/elective/controller/supplement/electSupplement.do?index=0&seq=BKC00132351AT0000241
其中关键的后台组件是electSupplement.do,向其传送了两个参数index和seq,猜想后台此时验证网页上验证码框的内容,如果验证成功即可选上。
自动查询选课人数实现原理
同上,不能补选的课会有刷新按钮,例如社会性别研究,在其刷新上点击右键->检查,其代码如下:
刷新
可以看到,点击刷新时,调用了javascript函数refreshLimit(),查看其定义为:
function refreshLimit(courseName,classNo,cancelTimeMsg,index,seqNo,limitedNbr) {
var now = new Date();
clearMsg(); // 清除提示信息
dif = now.getTime()-refreshTime.getTime();
if(dif /1000 < 5){
alert("对不起,您需要5秒之后才可以刷新。");
return;
}
refreshIndex = index;
limitedNum = parseInt(limitedNbr);
refreshCourseName = courseName;
refreshClassNo = classNo;
refreshCancelTimeMsg = cancelTimeMsg;
refreshSeqNo = seqNo;
if (xmlHttp == null) {
createXMLHttpRequest();
}
var url = "/elective2008/edu/pku/stu/elective/controller/supplement/refreshLimit.do?index=" + index+"&seq="+seqNo;
xmlHttp.open("GET", url, true);
xmlHttp.onreadystatechange = refreshCallback;
xmlHttp.send(null);
}
可以开心地看到,前文需要的index和seq就是这个函数的第4、5个参数,而且五秒的刷新频率是在这里被设定的,我们可以绕过它。继续阅读还可以发现,当前选课人数是通过向refreshLimit.do传index和seq两个参数实现的,而且返回的是一个xml文档,以前文的“社会性别研究”为例,其index=2,seq=BKC03130280AT0006152,故其xml文档的地址为:
http://elective.pku.edu.cn/elective2008/edu/pku/stu/elective/controller/supplement/refreshLimit.do?index=2&seq=BKC03130280AT0006152
打开这个地址,我们得到这样一个xml文件:
150
其中的150显然就是当前的已选人数。
这样我们就得到了所有需要的内容,刷课完事就绪,只差代码了。
分步代码实现
为了方便地抓取网页,解析xml,实现发邮件、发出声音等多种高级功能。我们使用简洁的Python (3)作为编程语言。
我们使用url.request包中的build_opener()函数得到一个OpenerDirector对象,其用于附加Cookie,向elective请求数据。
import urllib.request;
opener = urllib.request.build_opener();
opener.addheaders.append((\'Cookie\',\'JSESSIONID=\' + cookieID));
其中cookieID为当前会话的cookie值,可以用Chrome的开发工具方便的获取。
先获取选课人数,使用opener发送参数请求refreshLimit.do,得到xml文件,为了方便地解析xml,我们需要导入xml.dom.minidom:
import xml.dom.minidom;
arg = \'?index=\' + myIndex + \'&seq=\' + myCourSeq;
renshu = \'http://elective.pku.edu.cn/elective2008/edu/pku/stu/elective/controller/supplement/refreshLimit.do\' + arg;
ele = opener.open(renshu).read;
c = str(ele, encoding = \'utf8\');
root = xml.dom.minidom.parseString(c);
electedCount = str(root.getElementsByTagName(\'electedNum\')[0].firstChild.nodeValue);
便可得到当前已选人数,这个方法比直接在网页前端刷新要更快,因为理论上不存在5秒刷新的限制(实际上内部服务器还是存在一定限制,刷新频率可以设在3秒左右。
检测到已选人数不等于满员时,理论上便可以传参数给electSupplement.do,进行自动选课:
arg = \'?index=\' + myIndex + \'&seq=\' + myCourSeq;
buxuan = \'http://elective.pku.edu.cn/elective2008/edu/pku/stu/elective/controller/supplement/electSupplement.do\' + arg;
ele = opener.open(buxuan);
而实际上服务器此时可能会严格检查会话是否超时、验证码是否超时等,会导致不一定能自动选课成功,详细情况待研究。但我们可以通过邮件、发声等方法通知用户当前可选,让用户手动选课。
基本原理便如上所述,下面提供一份现成的可用代码供大家交流学习。
可用代码
(请原谅我C一样的代码风格)
#shuake.py
#By Jet 2016
import urllib.request;
from urllib.error import HTTPError, URLError;
import xml.dom.minidom;
from time import sleep;
import smtplib;
from email.mime.text import MIMEText;
#Data Needed Filling
myIndex = \'***myIndex***\'; #填课程索引号
myCourMax = \'***myCourMax***\'; #填课满人数
myCourSeq = \'***myCourSeq***\'; #填课程序列号
cookieID = \'***cookieID***\'; #填你当前的cookie值
#Your QQMail
#可选,填写后,若可选或出错会自动发邮件
user = "***QQMailID***";
pwd = \'***QQMailPswd***\';
arg = \'?index=\' + myIndex + \'&seq=\' + myCourSeq;
renshu = \'http://elective.pku.edu.cn/elective2008/edu/pku/stu/elective/controller/supplement/refreshLimit.do\' + arg;
buxuan = \'http://elective.pku.edu.cn/elective2008/edu/pku/stu/elective/controller/supplement/electSupplement.do\' + arg;
opener = urllib.request.build_opener();
opener.addheaders.append((\'Cookie\',\'JSESSIONID=\' + cookieID));
errCnt = 0;
timeOut = 3;
ret = myCourMax;
timeCnt = 0;
def refresh():
global timeCnt, errCnt;
timeCnt += 1;
print(\'Still Trying... 当前剩余人数 = \' + ret + \' 总尝试次数 = \' + str(timeCnt) + \' 错误次数 = \' + str(errCnt));
try:
ele = opener.open(renshu).read();
c = str(ele, encoding = \'utf8\');
root = xml.dom.minidom.parseString(c);
return str(root.getElementsByTagName(\'electedNum\')[0].firstChild.nodeValue);
except HTTPError as e:
if (e.code == 500):
refresh();
else:
errCnt += 1;
return \'HTTP ERROR \' + str(e.code);
except URLError as f:
return \'URL ERROR \' + str(f.errno);
except:
return \'UNKNOW ERROR\';
def xuan():
print(\'正在尝试自动选课!!!\');
try:
ele = opener.open(buxuan);
print(\'Successfully BuXuan!\');
mail(\'课可以选了!!!快去看看自动选有没有成功!!\');
while (1):
Beep(1500, 1000);
except:
while (1):
Beep(1500, 1000);
def mail(m):
msg = MIMEText(m);
msg["Subject"] = m;
msg["From"] = user;
msg["To"] = user;
s = smtplib.SMTP_SSL("smtp.qq.com", timeout = 30);
s.login(user, pwd);
s.sendmail(user, user, msg.as_string());
s.close();
while (1):
sleep(timeOut);
ret = refresh();
if (ret == myCourMax):
errCnt = 0;
elif (ret == str(eval(myCourMax + \'-1\'))):
xuan();
break;
elif (ret[0] != \'H\'):
errCnt += 1;
if (errCnt >= 15):
print(\'Unknown Error\');
mail("发生了错误:\n" + ret);
break;
该脚本考虑了简单的错误处理,尤其是HTTP 500错误在elective服务器上是常见的,该错误被忽略。 其中要填的部分为:
#Data Needed Filling
myIndex = \'***myIndex***\'; #填课程索引号
myCourMax = \'***myCourMax***\'; #填课满人数
myCourSeq = \'***myCourSeq***\'; #填课程序列号
cookieID = \'***cookieID***\'; #填你当前的cookie值
#Your QQMail
#可选,填写后,若可选或出错会自动发邮件
user = "***QQMailID***";
pwd = \'***QQMailPswd***\';
这些参数要如何获取呢?在你要补选的课程的刷新按钮上点右键->检查,refreshLimit()函数的倒数第三个参数是myIndex,倒数第二个参数是myCourSeq,最后一个参数是myCourMax。例如下图: fig1 我们可以得到:
myIndex = \'2\';
myCourMax = \'150\';
myCourSeq = \'BKC03130280AT0006152\';
Cookie也很好获取,在当前网页上按F12,切换到Resources页,找到Cookies -> elective.pku.edu.cn,其中JSESSIONID一栏的值就是cookieID,如下图:
fig2
其中5Jh....开头的那一段就是cookieID。(注意,这一栏很长,而且带有连字符,容易少复制)我这次会话的cookieID为:
cookieID = \'5JhYWNjHLfyh59GkmNzGYyh8JCtGL2Q8FQlTQ8ZGcG293zwhvkK2!-501355683!-929504223\'
如果比这个短很多,说明复制漏了。
之后的QQ邮箱账号密码可填可不填,用于发通知邮件。
该脚本使用方法
先打开elective,登录,切换到补退选页面,按上述方法填好脚本参数,填好页面上的验证码,运行脚本即可。网页别关,脚本窗口别关,就可以去做别的事啦,可选时便会尝试自动选课并响铃提示,这时你应该手动刷新elective检查是否自动选课成功,若失败便可以手动选课。
如果脚本遇到无法处理的意外退出,再次运行时只需要重新填写cookieID、页面上的验证码即可,课程索引等相关信息是不会改变的。
认真部分结束
有人说,你这个脚本这么吼,是不是该闷声大发财啊,万一被教务发现了明年没法用了怎么办啊。
其实,我觉得这个系统这么辣鸡,迟早得改革啦,而且这种脚本的原理是十分简单的,年年都有人用啊,系统太naive,当然得升级啊,是不是。