关于这个刷课机是如何工作的

代码

#!/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()可以全读到一个字符串里。