关于这个刷课机是如何工作的
代码
#!/usr/local/bin/python
# vim: set fileencoding=utf8 :
# 下面的东西反正是各种引用,有的或许还不需要……
import urllib2
import urllib
import cookielib
import lxml.html
import subprocess
import getpass
import time
import random
from lxml import etree
# 各种变量声明……
hosturl = "http://zhjwxk.cic.tsinghua.edu.cn";
# 用户名密码,密码还是当场输入比较安全
username = "";
password = getpass.getpass("Password: ");
loginfrm = "https://zhjwxk.cic.tsinghua.edu.cn:443/j_acegi_formlogin_xsxk.do";
# 这个是在OCR之前执行的语句……
ocrcmd_pre = "convert captcha.jpg captcha.bmp ; cuneiform captcha.bmp > /dev/null";
#gocr -a 80 -s 100 -C \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\"";
# 这句用来获取OCR结果……
ocrcmd_res = "cat cuneiform-out.txt";
urllogin = hosturl + "/xsxk_index.jsp";
# 有这几句就有Cookie支持了…… 请求之前会保留Cookie信息
jar = cookielib.CookieJar()
handler = urllib2.HTTPCookieProcessor(jar)
opener = urllib2.build_opener(handler)
urllib2.install_opener(opener)
h=urllib2.HTTPHandler(debuglevel=1)
opener = urllib2.build_opener(h)
# 另一些变量,主要是刷哪个课……
coursemajor = "30240422"
courseminor = "0"
term = "2010-2011-1"
coursetype = "xx" # rx
searchtype = "xxSearch" # rxSearch
type_id = "p_xxk_id" # p_rx_id
savetype = "saveXxKc" # saveRxKc
arg2 = "?m=" + searchtype + "&p_xnxq=" + term + "&tokenPriFlag=" + coursetype + "&p_kch=" + coursemajor
success = False
trytime = 0
# 好,开工
while not success:
# 先登录
while True:
# 抓登录页面
data = urllib2.urlopen(urllogin).read()
# print data;
# 抓登录验证码图片的路径
img = lxml.html.fromstring(data).get_element_by_id("captcha");
# 抓验证码图片
captcha = urllib2.urlopen(hosturl + img.get("src")).read();
# 把图片写到文件用来OCR
imgfile = open("captcha.jpg", "w");
imgfile.write(captcha);
imgfile.close();
# 运行OCR之前要跑的东西
subprocess.Popen(args=ocrcmd_pre, shell=True).wait();
# 跑OCR程序
ocrproc = subprocess.Popen(args=ocrcmd_res, stdout=subprocess.PIPE, shell=True);
# 拿结果,根据一般规律换掉一些OCR搞错的
ocrret = ocrproc.stdout.read().replace(" ","").replace("\n","").replace("/","J").replace("1","J").replace("I","J").replace("S","8");
# 构造登录的参数,用户名/密码/验证码等
loginfrm_arg = urllib.urlencode({'j_username': username,
'j_password': password,
'captchaflag': "login1",
'_login_image_': ocrret})
loginres = ""
try:
# 尝试登录!
loginres = urllib2.urlopen(loginfrm, loginfrm_arg).read();
except:
loginres = ""
if loginres.find("<div align=\"center\"") == -1 :
# 成功了!否则再试……
break
print "Logged in!"
# print urllib2.urlopen(hosturl + "/xkBks.vxkBksXkbBs.do?m=rxSearch&p_xnxq=2010-2011-1&tokenPriFlag=rx&p_kch=00430093").read()
# 抓登录之后的界面的一部分
mainpage = lxml.html.fromstring(urllib2.urlopen(hosturl + "/xkBks.vxkBksXkbBs.do" + arg2).read())
selurl = hosturl + "/" + mainpage.forms[0].action
while True:
trytime = trytime + 1
print "Round ", trytime
seldict = {}
try:
# 枚举每个输入部分,看看哪些要处理
for input in mainpage.forms[0].inputs:
if input.name != None and input.value != None:
if input.name != 'submit1' and input.name != 'bt':
print input.name.encode("utf-8"),":", input.value.encode("utf-8")
# 有些是要填的
if input.name == 'm':
input.value = savetype
if (input.name == None):
continue
# 有些可以空着
if (input.value == None and input.name != 'j_captcha_bks_xk'):
seldict[input.name] = ''
continue
# 一般的input需要原样返回,比如一些hidden的
if (input.type != 'button') & (input.type != 'reset') :
seldict[input.name] = input.value
# 恩,这个是验证码……
if input.name == 'j_captcha_bks_xk':
# 有这个说明页面上要你填验证码了
img = mainpage.get_element_by_id("captcha");
# 抓验证码存下来
captcha = urllib2.urlopen(hosturl + img.get("src")).read();
imgfile = open("captcha.jpg", "w");
imgfile.write(captcha);
imgfile.close();
# 老办法识别验证码
subprocess.Popen(args=ocrcmd_pre, shell=True).wait();
ocrproc = subprocess.Popen(args=ocrcmd_res, stdout=subprocess.PIPE, shell=True);
ocrret = ocrproc.stdout.read().replace(" ","").replace("\n","").replace("/","J").replace("1","J").replace("I","J").replace("S","8");
# 把验证码填进去
seldict[input.name] = ocrret
print "Img Capt: ",ocrret
except:
break
# 要选啥课
seldict[type_id] = term + ";"+ coursemajor + ";" + courseminor + ";"
# 构造选课时提交的参数
selparam = urllib.urlencode(seldict)
print selparam
# 过会,不急
time.sleep(0.5+0.01*random.randint(0, 100))
# 选课!
selres = urllib2.urlopen(selurl, selparam).read().decode('gbk')
# 把结果存下来,主要是调试用,另可以观察结果页面
selpage = open("selpage" + str(trytime % 2) + ".htm", "w");
selpage.write(selres.encode("utf-8"));
selpage.close();
# 搞定了?
if selres.encode("utf-8").find("成功") != -1:
success = True;
break
# 悲剧了,重新登陆吧,多半超时了
if selres.encode("utf-8").find("Forbidden") != -1:
timeout = True;
break;
# 别的问题,多半是满了
mainpage = lxml.html.fromstring(selres)
alertStart = selres.find('showMsg("') + 9;
alertEnd = selres.find('");', alertStart);
print "Result: ", selres[alertStart:alertEnd]
# print selres.encode("utf-8")
# 全部完成!
print "FINISHED!"
主要用到的方法
其实还包括用到的成员变量
如何抓页面及提交请求
- urllib2.urlopen(url)
非常好用的方法,用HTTP抓某个页面。这个样子会用HTTP GET抓。
对返回的东西调用read()就会返回整个页面,在一个字符串里,想干嘛都可以。
- urllib2.urlopen(url, param)
这个样子会用HTTP POST提交某个页面,一般点按钮之类都会提交某个form,这个时候用的多半都是HTTP POST方法。
所以比如登录啦、选课啦都是POST方法,而获取登录页面啦、获取选课页面啦都是GET。
- urllib.urlencode(dict)
给他一个dict,就能返回一个可以给上面那个方法用的param。
- 关于Cookie的那四行
反正就是给系统一个存Cookie的地方,并且让系统用它。这样各个请求的Cookie信息会保留,在下一个请求用。
HTML页面的分析
- lxml.html.fromstring(string)
lxml是个很方便的分析XML/HTML的库,这个方法能够读一个string,返回一个html对象,里面是HTML文档处理之后得到的结构化的结果。然后在这个对象上就能干各种事情了。以下把其返回的叫html。
- html.get_element_by_id(id)
有了上面那个的结构化的结果,就能在里面找某个特定id的对象了。
- html.forms[]
包括了该页所有form的数组,一般你要找的form就在其中…… 以下把其成员称为form
- form.inputs
某form的所有输入,一般我们只关心这个…… 以下把其成员称为input
- input.value, input.name
某输入项的值与名字。你可以拿去用,在提交的时候还要交回去。一般提交的时候,参数里都有 名字=值 这样的好多对。
- string.decode(encoding), string.encode(encoding)
反正是用来转编码的,陌生的字符串最好decode一下,要find东西的时候最好encode一下
子进程相关
- subprocess.Popen(args=cmd, shell=True)
开一个进程跑某物,要不要shell有时候不一样。以下把其返回的叫proc
- proc.wait()
等某进程结束,这样才好拿它输出的东西。
- proc.stdout()
某进程的输出,用.read()可以全读到一个字符串里。