用前说明:
本文章仅对 Web 开发,Python 开发进行探讨,进行实验时请遵守学校的规章制度。 任务自动化本来就是程序员的一大乐趣,无关价值观。
废话
很多人有误区,以为抢课脚本就是像游戏外挂一样,利用什么教务处系统漏洞去搞到课,而事实上抢课脚本只是模拟浏览器和服务器进行交互而已。而它们之间交互的方式是使用 HTTP 协议,换言之你的程序只要通过 HTTP 协议与服务器进行交互,就可以模拟浏览器能完成的事情。
当然,最朴素的抢课脚本应当数按键精灵。但是按键精灵依赖于浏览器的渲染速度,而使用 HTTP 协议交互的脚本可以忽略一切 CSS JS 文件的解析,直接发请求,资源占用和速度都优化了一个量级。
简而言之,由于 HTTP 是无状态协议,所以每次提交请求,服务器无法判断你是否是之前的用户,因此在动态网页中常用 Cookies 来鉴别用户身份,Cookies 是附加在每次 HTTP 请求中用来识别用户身份的数据。
在 Python 中,直接使用 urllib2 默认的 urlopen 是不能处理好 Cookies 的,这里的 urlopen 可以看成是一个能向目标 URL 发请求的对象。所以我们使用 cookielib 来构造一个能处理 Cookies 的 opener。
cookie = cookielib.CookieJar()
handler = urllib2.HTTPCookieProcessor(cookie)
opener = urllib2.build_opener(handler)
以后,我们使用 opener.open(url) 就可以带 Cookies 去发 GET 请求了。
环境要求
- Python 3
- selenium
- chrome
- chromedriver
登录
首先用 Chrome 分析一下教务系统的逻辑是怎样的,打开 Inspect 然后打开教务处网站
屏幕快照 2018-01-09 14.29.59.png
12.png
可以看出,前端一点都不简洁,估计编写代码的人技术不是很高,从文件名可以简单地看出 cas.js 应该是散列函数,应该是处理密码用的。
然后我们手动登录一下,看看登录的请求是怎样的。
132.png
屏幕快照 2018-01-09 14.47.21.png
可以看出发送了一个 POST 请求,请求地址是 http://xk.ccnu.edu.cn//xtgl/index_initMenu.html。表单内容有 username、password、j_code 和一堆不知道含义的东西。显然 username 是学号了,password 有经验的可以看出来是的 MD5 散列之后的结果,j_code 是验证码结果。
直接对应填上即可。
post_data = {}
post_data['username'] = username
post_data['password'] = pasw_cas
post_data['lt'] = ''
post_data['_eventId'] = 'submit'
post_data['submit'] = '登录'
post_data = urllib.urlencode(post_data)
opener.open(login_url, post_data)
CAS
既然知道了和 CAS 有关系,可以用 Python 的 cas 模块进行尝试。
pasw_cas = cas.new()
pasw_cas.update(password)
pasw_cas = pasw_cas.hexdigest().upper()
一测试发现,提交的就是密码的 CAS 散列,问题就轻易解决了。当然如果不对,一般来说可以尝试用户名+密码的组合,还有是加上时间戳的组合,最后不行就去逆向 JS 文件吧。
抢课请求
相信到这里很多人已经摸到套路了,无非就是抓一个请求,分析,然后伪造这个请求。
这里发现选课只需要发一个 POST 请求到 http://xk.ccnu.edu.cn//xtgl/,当然也有带上 SID,此外还有一个参数,想必就是课程编号了。
和登录一样,我们构造一个字典,然后用 urllib 中的 urlencode 功能进行编码,最后使用 opener.open(res, elect_post_data) 发送请求即可。
elect_post_data = {};
elect_post_data['sid'] = sid
res = urllib2.Request(elect_url)
opener.open(res, elect_post_data)
如果再套上一个死循环,这个抢课脚本就写完了。当然,肯定是有很多问题的,我们慢慢来解决
Delay
如果直接死循环而不加上延时,服务器多半会挂掉。因为客户端的一个请求对应服务器的一次数据库查询,多半这样的压力学校服务器是受不了的。而且过高的发包很容易被查水表,一般来说 0.5s 到 0.3s 的延时已经足够超越极大部分单身二十年少年的手速。
浏览器伪装
一般的 Web 都是会对 HTTP 的请求头进行检测,以防止一些最简单的爬虫,很幸运的是,教务系统并没有。但是一旦对方的 Web Server 开启了访问记录,很容易把这些 HTTP 请求头为空的请求筛选出来,然后慢慢查水表之类的,所以为了安全起见,我们需要伪造浏览器的请求头。
UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36'
Referer = 'http://xk.ccnu.edu.cn/zftal-ui-v5-1.0.2/assets/js/zftal/jquery.extends.contact-min.js?ver=20171211' + sid
X_Requested_With = 'XMLHttpRequest'
res.add_header('User-Agent', UA)
res.add_header('Referer', Referer)
res.add_header('X_Requested_With', X_Requested_With)
解析响应
上面的代码我们只处理了发送请求,而并没有理会响应,这样即使抢到了课,程序还是会继续运行下去。
我们先分析一下响应是怎样的,Chrome 中 Inspect 有一个很好用的功能是 Copy as cURL,可以将这次请求提取为 cURL 命令,方便丢到命令行进行分析。
5.png
可以看出这是一个 JSON 格式的响应,在 Python 处理 JSON 很方便,我们可以用 json 库中的 response_json = json.loads(response) 将 JSON 字符串转换为一个字典。这样你就可以根据响应来后续处理,例如是微信通知之类的。
注意
- 不建议在一台电脑上开启多个。
- 本脚本仅为个人程序开发之练习,切勿随意传播。
- 改选时间尚未开放,该脚本暂未测试,暂未测试,暂未测试!
致谢:
原文地址:www.iooy.com
网友评论