最近看了CSDN上一個專欄《Python爬蟲入門教程》,其中最后一篇作者寫了個例子,用爬蟲計算山東大學績點,頓時想到前一陣子搞測評的時候還得拿計算器一點點算自己的平均學分績,也想寫一個自己學校的計算學分績的爬蟲。(吐槽:這么簡單的功能學校網站居然不實現以下!)
具體過程專欄作者寫得很清楚,詳見 [Python]網絡爬蟲(十):一個爬蟲的誕生全過程(以山東大學績點運算為例)。作者使用了Firefox的HTTPFox插件來進行POST和GET消息分析,並通過查看源代碼來查找到模擬登陸和查詢成績時的URL(其實這里使用Firefox的FireBug插件更簡單)。使用這兩個插件使我這個對網站編程一竅不通的人都能搞出個模擬登陸來。
根據那篇博文,整個過程分為以下幾步:
- 打開教務系統網站,查看登陸時發送了哪些信息,分析POST和GET信息,並在模擬登陸時使用;
- 進入網站后,查看如何與網站交互,同步驟一,分析這些信息,在模擬查詢時使用;
- 使用爬蟲模擬登陸和查詢,得到成績頁面的html代碼;
- 使用正則表達式提取得到的html代碼中的所有成績信息;
- 通過提取到的成績信息計算平均學分績。
具體操作細節可以參考上面那篇博文。此處不再贅述。
這里寫一下我遇到的問題。上述博文中,作者登陸成績查詢系統后,直接找到成績查詢頁面的URL就可以得到成績頁面的html代碼,而在我們學校的教務系統中我卻怎么也找不到該URL。經過N多次實驗發現,我們學校查詢的時候也會發送一個特定格式的POST信息,然后才會返回成績頁面,也就是說得再次模擬發送一次瀏覽成績信息。感覺是由於自己對網站相關知識不了解,認為只要模仿就能取得結果,殊不知這里查詢方式的不同,浪費了不少時間。
得到網頁信息后,就需要寫正則表達式來提取成績信息。關於Python的正則表達式詳見:[Python]網絡爬蟲(七):Python中的正則表達式教程。對於正則表達式不熟的人推薦一個在線測試的網站:Regex Tester ,這個網站可以測試寫的正則表達式能不能正確匹配要提取的信息。
附上源代碼:
1 # -*- coding: utf-8 -*- 2 # author: Kill Console 3 # 功能:計算西工大研究生本學期成績學分績 4 5 import urllib 6 import urllib2 7 import cookielib 8 import re 9 10 REQUIRED = '<font color=red >\xe5\xad\xa6\xe4\xbd\x8d\xe5\xbf\x85\xe4\xbf\xae\xe8\xaf\xbe</font>' # 必修 11 ELECTIVE = '\xe5\xad\xa6\xe4\xbd\x8d\xe9\x80\x89\xe4\xbf\xae\xe8\xaf\xbe' # 選修 12 13 class NPUSpider: 14 def __init__(self, stuid, pwd): 15 self.login_url = 'http://222.24.211.70/grsadmin/servlet/studentLogin' # 登陸url 16 self.query_url = 'http://222.24.211.70/grsadmin/servlet/studentMain' # 成績查詢url 17 self.cookie_jar = cookielib.CookieJar() # 初始化一個CookieJar來處理Cookie的信息 18 self.post_data_login = urllib.urlencode({'TYPE':'AUTH', 'glhj':'', 'USER':stuid, 'PASSWORD':pwd}) # POST登錄數據 19 self.post_data_query = urllib.urlencode({'MAIN_TYPE':'3', 'MAIN_SUB_ACTION':'瀏覽成績', 'MAIN_NEXT_ACTION':'LL', 'MAIN_PURPOSE':'/jsp/student_JhBrow.jsp'}) # POST查詢數據 20 self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cookie_jar)) 21 self.course_data = [] # 存儲一門課的信息,包括課程號、課程名、學分、成績等 22 self.weights = [] # 存儲學分 23 self.scores = [] # 存儲成績 24 self.GPA = 0.0 25 26 def init_spider(self): 27 req_login = urllib2.Request(url = self.login_url, data = self.post_data_login) # 自定義一個登錄請求 28 result_login = self.opener.open(req_login) # 訪問登錄頁面 29 req_query = urllib2.Request(url = self.query_url, data = self.post_data_query) # 自定義一個查詢請求 30 result_query = self.opener.open(req_query) # 訪問成績頁面 31 self.process_data(result_query.read().decode('gbk').encode('utf-8')) # 由於存在中文,先解碼再編碼 32 self.calculate_GPA() 33 34 def process_data(self, res_page): 35 self.course_data = re.findall(r'<TR>\s+<TD width=60 class=style2><div.*?>(.*?)</div>.*?<div.*?>(.*?)</div>.*?<div.*?>\s+(.*?)\s+</div>.*?<div.*?>(.*?)</div>.*?<div.*?>(.*?)</div>.*?<div.*?>(.*?)</div>.*?<div.*?>(.*?)</div>.*?</TR>', res_page, re.S) 36 for item in self.course_data: 37 self.weights.append(item[4]) 38 self.scores.append(item[5]) 39 40 def calculate_GPA(self): 41 sum_score = 0.0 42 sum_weight = 0.0 43 for i in range(len(self.course_data)): 44 if self.course_data[i][2] == REQUIRED and self.scores[i].isdigit(): # 判斷該科目是否是必修且是否有分數,若是則進行計算 45 sum_score += float(self.weights[i]) * float(self.scores[i]) 46 sum_weight += float(self.weights[i]) 47 self.GPA = sum_score / sum_weight 48 49 def print_GPA(self): 50 print u'課程代碼\t\t類型\t\t學期\t\t學分\t\t成績\t\t得分日期\t\t課程名稱' 51 print '-'*120 52 for item in self.course_data: 53 if item[2] == REQUIRED: 54 rqd = '\xe5\xad\xa6\xe4\xbd\x8d\xe5\xbf\x85\xe4\xbf\xae\xe8\xaf\xbe' 55 print '%s\t\t%s\t%s\t\t%s\t\t%s\t\t%s\t\t%s' % (item[0], rqd, item[3], item[4], item[5], item[6], item[1]) 56 else: 57 print '%s\t\t%s\t%s\t\t%s\t\t%s\t\t%s\t\t%s' % (item[0], item[2], item[3], item[4], item[5], item[6], item[1]) 58 print 59 print 'The GPA is %.4f' % self.GPA 60 61 62 def main(): 63 stuid = raw_input('student ID: ').strip() 64 pwd = raw_input('pass word: ').strip() 65 66 spider = NPUSpider(stuid, pwd) 67 spider.init_spider() 68 spider.print_GPA() 69 70 if __name__ == '__main__': 71 main()
以下是一個西工大本科生學分績計算腳本,由於測試時沒有進行教學評估的查詢不了,只能查詢本學期成績,所以計算的是本學期成績學分績(只計算必修):

1 # -*- coding: utf-8 -*- 2 # author: Kill Console 3 # 功能:計算西工大本科生本學期成績學分績 4 5 import urllib 6 import urllib2 7 import cookielib 8 import re 9 10 REQUIRED = '\xe5\xbf\x85\xe4\xbf\xae' # 必修 11 ELECTIVE = '\xe4\xbb\xbb\xe9\x80\x89' # 選修 12 13 class NPUSpider: 14 def __init__(self, stuid, pwd): 15 self.login_url = 'http://jw.nwpu.edu.cn/loginAction.do' # 登陸url 16 self.result_url = 'http://jw.nwpu.edu.cn/bxqcjcxAction.do' # 本學期成績查詢url 17 self.cookie_jar = cookielib.CookieJar() # 初始化一個CookieJar來處理Cookie的信息 18 self.post_data = urllib.urlencode({'zjh':stuid, 'mm':pwd}) # POST的數據 19 self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cookie_jar)) 20 self.course_data = [] # 存儲一門課的信息,包括課程號、課程名、學分、成績等 21 self.weights = [] # 存儲學分 22 self.scores = [] # 存儲成績 23 self.GPA = 0.0 24 25 def init_spider(self): 26 req = urllib2.Request(url = self.login_url, data = self.post_data) # 自定義一個請求 27 result = self.opener.open(req) # 訪問登錄頁面 28 result = self.opener.open(self.result_url) # 訪問成績頁面 29 self.process_data(result.read().decode('gbk').encode('utf-8')) 30 self.calculate_GPA() 31 32 def process_data(self, res_page): 33 self.course_data = re.findall(r'<tr class=.*?<td.*?>\s+(.*?)\s+</td>.*?<td.*?>\s+(.*?)\s+</td>.*?<td.*?>\s+(.*?)\s+</td>.*?<td.*?>\s+(.*?)\s+</td>.*?<td.*?>\s+(.*?)\s+</td>.*?<td.*?>\s+(.*?)\s+</td>.*?<td.*?>\s+(.*?)\s+</td>.*?</tr>', res_page, re.S) 34 for item in self.course_data: 35 self.weights.append(item[4]) 36 self.scores.append(item[6]) 37 38 def calculate_GPA(self): 39 sum_score = 0.0 40 sum_weight = 0.0 41 for i in range(len(self.course_data)): 42 if self.course_data[i][5] == REQUIRED and self.scores[i].isdigit(): 43 sum_score += float(self.weights[i]) * float(self.scores[i]) 44 sum_weight += float(self.weights[i]) 45 self.GPA = sum_score / sum_weight 46 47 def print_GPA(self): 48 print 'The GPA is %.4f' % self.GPA 49 50 def main(): 51 stuid = raw_input('student ID: ').strip() 52 pwd = raw_input('pass word: ').strip() 53 54 spider = NPUSpider(stuid, pwd) 55 spider.init_spider() 56 spider.print_GPA() 57 58 if __name__ == '__main__': 59 main()