昨天有人问我关于奖学金评选的综合成绩是多少,当时我还没算。作为一名程序员当然不能用手工计算啦。不如写个程序从教务系统爬取成绩然后计算吧!

登陆教务系统

要爬去成绩当然要先登陆咯。思路是这样的,首先GET到验证码,下载到本地,用于人工识别;同时存下Cookie,用户登陆之后获取成绩。

存Cookie

cookie = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie))
urllib2.install_opener(opener)

获取验证码

urllib2.urlopen(url).read('http://210.42.121.134/servlet/GenImg')

将用户名等参数发送到服务器,然后通过正则表达式<div id=\"nameLable\">(.*?)<获取登陆之后的用户名,若获取失败则说明登陆失败。注意正则查找时要加入re.DOTALL参数表示单行模式,因为要匹配的字符在源码中是有换行的。根据返回的数据可以分为用户名、密码错误或者是验证码错误

def get_data(url):
    return urllib2.urlopen(url).read()

def decode_captcha(img_content):
    """
    人工识别验证码
    """
    img = open('captcha', 'wb')
    img.write(img_content)
    img.close()

    captcha = raw_input('captcha > ')
    os.remove('captcha')
    return captcha

def login(self):
        """
        登录教务系统主页
        登录成功则返回用户姓名
        :param callback: 解析验证码的回调函数
        :param persist: 是否保存验证码以便下次登陆时使用
        :return: 用户姓名或错误(None)
        """

        img_content = get_data('http://210.42.121.134/servlet/GenImg')
        self.captcha = decode_captcha(img_content)

        params = {'id': self.user, 'pwd': self.password, 'xdvfb': self.captcha}
        params = urllib.urlencode(params)

        # 发送post请求
        response = urllib2.urlopen('http://210.42.121.134/servlet/Login', params).read().decode('gb2312')

        # 判断是否成功登录主页
        m = re.search('<div id=\"nameLable\">(.*?)<', response, re.DOTALL)
        if m is not None:
            return m.group(1).strip()
        elif response.find(u'用户名/密码错误') > 0:
            return None
        else:
            return self.login()

(类的定义已省略)

获取成绩

登陆之后要利用保存的Cookie查询2014-2015学年的成绩,然后将必修与选修分开,分别计算成绩。

查询成绩,同样是利用正则表达式<td>(.*?)</td>获取数据。由于源码中只有一个table,因此所有获得的数据都是课程信息。根据对表格的分析,发现每11条数据为一组课程信息,然后再根据表头的排列抽取数据咯

def query(self):
        """
        爬取课程信息
        :return:
        """
        params = urllib.urlencode({'year': '2014', 'term': '', 'learnType': '', 'scoreFlag': '0', 'submit': '所有'})
        response = urllib2.urlopen('http://210.42.121.134/servlet/Svlt_QueryStuScore', params).read().decode('gb2312')
        pattern = '<td>(.*?)</td>'
        m = re.findall(pattern, response, re.DOTALL)

        courses = []
        for i in range(0, len(m), 11):
            courses.append({
                'name': m[i + 1],
                'type': m[i + 2],
                'credit': float(m[i + 3]),
                'grade': float(m[i + 9]),
            })
        return courses

计算成绩

利用上面获取到的成绩,接着进行计算。先将成绩分为必修与选修两类,直接计算必修的加权平均。对于选修,计算之后取最高8位。sorted真好用~~

def calc_grade(self):
        """
        计算总成绩,同时打印所选的选修课程
        :return: 总成绩
        """
        courses = self.query()
        bixiu = [c for c in courses if c['type'].find(u'必修') > 0]
        xuanxiu = [c for c in courses if c['type'].find(u'选修') > 0]

        # 必修课程
        sum = 0
        credit = 0
        for c in bixiu:
            credit += c['credit']
            sum += c['credit'] * c['grade']

        # 选修课程
        add = 0
        xuanxiu = sorted(xuanxiu, key=lambda c: c['credit'] * c['grade'], reverse=True)[:8]
        for c in xuanxiu:
            add += c['credit'] * c['grade']

        return {
            'bixiu': bixiu,
            'xuanxiu': xuanxiu,
            'sum_bixiu': sum / credit,
            'sum_xuanxiu': add * 0.002,
            'sum': sum / credit + add * 0.002
        }

部署到服务器

当然,上面的程序只能在本地跑一跑,为了服务大家(装逼而已),需要将程序搞到服务器上面。因此需要做一点修改。最令人头疼的是Cookie的问题,因为要多人使用,就不能用一个全局的Opener了,于是选择了自己获取教务系统服务器Cookie并将其存到用户的Cookie中。

def get_captcha():
    # 将学校服务器发来的cookie保存
    o = urllib2.urlopen('http://210.42.121.134/servlet/GenImg')
    session['cookie'] = o.info().items()[1][1].split(';')[0]
    return o.read()

之后的请求都要设置请求头。

 # 设置cookie
 opener = urllib2.build_opener()
 opener.addheaders = [('Cookie', self.cookie)]

点击查看部署