昨天有人问我关于奖学金评选的综合成绩是多少,当时我还没算。作为一名程序员当然不能用手工计算啦。不如写个程序从教务系统爬取成绩然后计算吧!
登陆教务系统
要爬去成绩当然要先登陆咯。思路是这样的,首先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)]