用Python模拟登录正方教务系统并抓取初始网页的一些个人笔记

SCAU有着和许多大学一样极其--的教务教务管理系统和比较--的选课经历。
教务系统有四个服务器,带公网IP,IIS 6.0 , 正方教务系统明显是ASP.NET开发。即便如此(‘如此’......这用词)也还是救不了饥渴求学莘莘学子啊 :-( 每逢选课时节,多少风声鹤唳草木皆兵。为了选到心仪老师的课多少学生一遍又一遍地刷着选课列表,3个区来回刷,再加上不合理的查询页面设计......实在受不了一遍一遍去刷公选课列表再对照可用时间去一个个选可以上的课所花的时间了,想要动手写个小软件把公选课的列表抓下来自动匹配我的可用时间进行筛选。第一步就是要搞定登陆问题。


模拟登陆抓取网页基础知识:
【用Python模拟登录正方教务系统并抓取初始网页的一些个人笔记】起码要懂HTTP协议里的method GET,POST这最常用的两种。“你打开一个网页背后到底发生了什么”
既然要登录那大部分都会涉及到cookies的使用,要了解是什么回事。懂得怎么用。
HTML要懂。 其实这些东西很容易找,大牛们都自发做成专题了,各取所需就好了。


百度一下“Python+正方教务系统”......貌似有一位前辈和我出发点一模一样,还把Python代码post上去了。还有一位本校的师兄在Github上面有已经做好的项目,无奈是Java,目前还不懂。只能看Python。依样画葫芦结果出了很大的问题。


首先这位前辈说正方教务系统有五个登陆界面
/default1.aspx ~ /default5.aspx


事实的确如此,但是SCAU上面能用的只有1-4,而且不存在没有验证码的登陆界面。而且在非默认打开的登陆界面里面输入账号密码是怎么样都登不上的(后来仔细分析POST过去的包原来是因为每个登录界面POST过去的data格式都不一样,只有默认页的格式能拿到response)。后来在一些论坛上面发现有人讨论过这个问题。用的虽然都是正方系统,但实际情况是不同大学enable的登陆界面编号都不一样。建议就是要把所有的都试遍,反正不会封IP......
: -D


用Chromium的Ctrl + Swift + I。看了一下打开登陆界面首页发送接收的包。
一个没有什么特殊的GET请求,然后就是一个普通的response。里面是一个16进制的SessionId的cookie。不难。


还有一个__VIEWSTATE 和 __VIEWSTATEGENERATOR 和各自值的组合。仔细观察可以在defaultx.aspx的源代码里面找到(ASP.NET的什么什么) 既然POST要用,就要把它从网页中提取出来。我用的是正则表达式。


人手操作登录了几次,然后试着打开不同功能的页面,发现所有的POST和GET都是带着这个cookie不变的,所以以后POST都得靠这个pageOpener.open()方法。至于GET,由于可以得到一个类文本对象,所以用.read()就没有问题了。点击登录按钮后会发送一个POST请求。


抓出了POST登录数据的包。主要是构造好几个请求的细节就可以了。

一个是loginData。用一个urllib.urlencode()就可以了。headers无关紧要。粗略地看了一下文档, 发现urlencode()接受的一个{},urllib2.Request()构造一个请求对象的时候headers也是接受一个 {}.那干脆就两个一起括起来吧,看起来整洁多了。



还有一个比较麻烦的就是验证码。目前还没有精力和水平去研究那些高大上的验证码自动识别技术,只能用笨方法了。这个缺点很明显。一是要gthumb,而是要把gthumb打开的窗口关掉程序才能自动执行下去。

然后就是POST过去了

抓包的时候发现POST以后得到的response是没有什么实质性的东西的。而且登录以后打开的主界面URL都有固定格式,直接用之前提到的opener就好了。


目前就搞到这里。如果读取公选课列表其实可以跳过打开主界面这一步,直接请求框架就可以了。不过就是因为对ASP.NET一无所知,所以遇到了很多麻烦。

tips:

1.一定要人手去试着登录多几次,用工具查看(Chromium和firefox都有相应的工具集成,比较简单了),看看有什么规律,POST/GET都是什么时候发生,有什么cookies,headers哪些是必须要有的,data又要哪些,生成请求对象的时候有没有漏了编码这一步。

2.然后写程序一定要严格一步一步地按照POST/GET的顺序去写代码,不要跳步......要cookies的不要漏了。



贴出一些简单的关键代码,还不够pythonic。等实现终极目标以后就整理一下一起贴上来吧。


import urllib import urllib2 import cookielib import webbrowser import re import os# GET student ID and password loginID = [] loginID.append(raw_input('Please enter your student ID\n')) loginID.append(raw_input('Please enter your password\n'))# server list svr1 = '' svr2 = '' svr3 = '' svr4 = '' next = '\n' print 'severs available:\n' print svr1 + next , svr2 + next , svr3 + next , svr4 + next # GET the server No. and return URL svrID = input('Please enter the No. of server\n') def chooseSvr(svrID): if svrID == 1 : svrIP = svr1 elif svrID == 2 : svrIP = svr2 elif svrID == 3 : svrIP = svr3 elif svrID ==4 : svrIP = svr4 return svrIP loginURL = 'http://' + chooseSvr(svrID)# cookie and opener studentCookie = cookielib.CookieJar() pageOpener = urllib2.build_opener(urllib2.HTTPCookieProcessor(studentCookie))# open the login page loginPageRequest = urllib2.Request(loginURL) loginPageHTML = pageOpener.open(loginPageRequest).read()# GET viewstate and viewstategenerator value viewstatePattern = re.compile(r'name="__VIEWSTATE" value="https://www.it610.com/article/(.*?)" /',re.DOTALL) result=viewstatePattern.search(loginPageHTML) try: viewstate = result.group(1) print 'viewstate value is ' , result.group(1) , '\n' except: print 'cannot find the value of viewstate'viewstategeneratorPattern = re.compile(r' name="__VIEWSTATEGENERATOR" value="https://www.it610.com/article/(.*?)" /',re.DOTALL) result=viewstategeneratorPattern.search(loginPageHTML) try: viewstategenerator = result.group(1) print 'viewstategenerator value is' , result.group(1) ,'\n' except: print 'cannot find the value of viewstategenerator'# GET the checkcode checkcodeURL = loginURL + '/checkcode.aspx' checkcodeCachePath = '~/checkcodeCache.gif' checkcodePic = pageOpener.open(checkcodeURL).read() f = file(checkcodeCachePath,'wb') f.write(checkcodePic) f.close() command = 'gthumb ' + checkcodeCachePath os.system(command) if os.system == 0: pass icode = raw_input('Plesea enter the checkcode \n') print 'the checkcode is' , icode# login data loginData = https://www.it610.com/article/{'__VIEWSTATE':viewstate, '__VIEWSTATEGENERATOR':viewstategenerator, 'TextBox1':loginID[0], 'TextBox2':loginID[1], 'TextBox3':icode, 'RadioButtonList1':r'%D1%A7%C9%FA', 'Button1':'', 'lbLanguage':'' }# headers loginHeader = { 'Accept':'text/html,application/xhtml+xml,application/xml; q=0.9,image/webp,*/*; q=0.8', 'Accept-Encoding':'gzip,deflate,sdch', 'Accept-Language':'en-US,en; q=0.8,zh-CN; q=0.6,zh; q=0.4', 'Cache-Control':'max-age=0', 'Connection':'keep-alive', 'Content-Length':'197', 'Content-Type':'application/x-www-form-urlencoded', 'Host':chooseSvr(svrID), 'Origin':loginURL, 'Referer':loginURL + '/default2.aspx', 'User-Agent':'' }# request object loginRequest = urllib2.Request(loginURL , urllib.urlencode(loginData) , loginHeader)# LOGIN: HTTP POST loginResponse = pageOpener.open(loginRequest)# OPEN student main page : HTTP GET getMainPageHeader = { 'Accept':'text/html,application/xhtml+xml,application/xml; q=0.9,image/webp,*/*; q=0.8', 'Accept-Encoding':'gzip,deflate,sdch', 'Accept-Language':'en-US,en; q=0.8,zh-CN; q=0.6,zh; q=0.4', 'Cache-Control':'max-age=0', 'Connection':'keep-alive', 'Host':chooseSvr(svrID), 'Referer':loginURL + '/default2.aspx', 'User-Agent':'' } mainPageURL = loginURL + '/xs_main.aspx?xh=' + loginID[0] mainPageRequest = urllib2.Request(mainPageURL , headers = getMainPageHeader) mainPageHTML = pageOpener.open(mainPageRequest).read()print mainPageHTML







    推荐阅读