用Python脚本实现刷课

时间:2024-02-20 12:15:18
用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,当然得升级啊,是不是。