2.2從教務(wù)系統(tǒng)查詢成績并計(jì)算績點(diǎn)——山東建筑大學(xué)為例

前兩天面試時(shí)被問到績點(diǎn)是多少,但學(xué)校教務(wù)系統(tǒng)不提供績點(diǎn)查詢的功能,那么能不能寫一個(gè)爬蟲程序并計(jì)算出績點(diǎn)呢?答案是肯定的!

感謝該博客提供的思路

1. 準(zhǔn)備

HttpFox插件,是一款http協(xié)議分析插件,分析頁面請(qǐng)求和響應(yīng)的時(shí)間、內(nèi)容、以及瀏覽器用到的COOKIE等。是火狐瀏覽器的插件。谷歌瀏覽器和Safari都有自帶的分析工具,可是感覺太復(fù)雜,沒有這款好用。不過火狐瀏覽器對(duì)學(xué)校的教務(wù)系統(tǒng)兼容性不是很好,我還下載了IE tab插件。


插件的安裝

可以非常直觀的查看相應(yīng)的信息。
點(diǎn)擊start是開始檢測(cè),點(diǎn)擊stop暫停檢測(cè),點(diǎn)擊clear清除內(nèi)容。

2. 探究過程

下面就去山東建筑大學(xué)官網(wǎng)登錄到數(shù)字校園綜合信息門戶,看一看在登錄的時(shí)候,到底發(fā)送了那些信息。
先來到登錄頁面,把httpfox打開,clear之后,點(diǎn)擊start開啟檢測(cè):

開啟檢測(cè)

輸入完賬號(hào)密碼,確保httpfox處于開啟狀態(tài),然后點(diǎn)擊登錄。
這個(gè)時(shí)候可以看到,httpfox檢測(cè)到了好多信息:


捕捉到的信息

那么我們來分析一下這些數(shù)據(jù)

看起來紅框里的兩條數(shù)據(jù)比較有意思,先看看這個(gè)post

post數(shù)據(jù)

PostData中我們看到了比較熟悉的詞,username和password,學(xué)過java web的我們很清楚這段數(shù)據(jù)的含義,點(diǎn)擊登錄后將這用戶名和你們提交到服務(wù)器比對(duì)。


重定向到這里

可以看到這里使用get的方式在鏈接上以?的方式顯示的加上了參數(shù),跳轉(zhuǎn)到信息門戶。
我們的post的數(shù)據(jù)就發(fā)送到了這個(gè)地址

http://urpe.sdjzu.edu.cn/loginPortalUrlForIndexLogin.portal

需要的post數(shù)據(jù)是用戶名密碼,也就是說我們需要輸入這兩種數(shù)據(jù)來模擬登錄過程。

進(jìn)入到教務(wù)系統(tǒng)
點(diǎn)擊成績查詢

進(jìn)入教務(wù)系統(tǒng)后點(diǎn)擊成績查詢,我們看到請(qǐng)求的地址為

http://jwfw1.sdjzu.edu.cn/ssfw/jwnavmenu.do?menuItemWid=1E057E24ABAB4CAFE0540010E0235690

我們整理一下整個(gè)過程的思路。

  1. POST學(xué)號(hào)和密碼--->然后返回cookie的值
  2. 發(fā)送cookie給服務(wù)器--->返回頁面信息。
  3. 獲取到成績頁面的數(shù)據(jù),用正則表達(dá)式將成績和學(xué)分單獨(dú)取出并計(jì)算加權(quán)平均數(shù)。

ok,理順?biāo)悸泛笫O碌木椭挥芯幋a問題了。

3. 實(shí)驗(yàn)

我們先來實(shí)驗(yàn)下是否能夠獲得查詢成績界面的源碼

我們先準(zhǔn)備一個(gè)POST的數(shù)據(jù),再準(zhǔn)備一個(gè)cookie的接收,然后寫出源碼如下:

# coding=utf-8
import urllib
import urllib2
import cookielib

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

# 需要的POST數(shù)據(jù)
postdata = urllib.urlencode({
    'userName': '20140216064',
    'password': '*********'
})
# 自定義一個(gè)請(qǐng)求
req1 = urllib2.Request(
    url='http://urpe.sdjzu.edu.cn/loginPortalUrlForIndexLogin.portal',
    data=postdata
)

req2 = urllib2.Request(
    url='http://jwfw1.sdjzu.edu.cn/ssfw/jwnavmenu.do?menuItemWid=1E057E24ABAB4CAFE0540010E0235690'
)

# 訪問登錄鏈接
opener.open(req1)
result = opener.open(req2)
# 打印返回的內(nèi)容
print result.read()
運(yùn)行結(jié)果

很棒呦,看來跟我們預(yù)期的一樣。

4. 整理數(shù)據(jù)

獲得了成績查詢界面的源碼后我們需要將數(shù)據(jù)進(jìn)行整理,獲得我們想要的數(shù)據(jù),課程名稱,學(xué)分,成績。

將網(wǎng)頁源碼貼到Sublime Text中,方便我們查看源碼

分析源碼

分析源碼

通過查看源碼我們看到從這個(gè)<div>開始到3640行都是關(guān)于成績的代碼,而成績是存放在這個(gè)table標(biāo)簽下。


需要提取的信息

紅色框中分別為課程名,學(xué)分,以及成績。這些是我們需要抽取出來的數(shù)據(jù)。

看到這里竟然有意外收獲!注意黃色框被注釋的部分,看來學(xué)校的教務(wù)系統(tǒng)有計(jì)算績點(diǎn)的功能的,不知由于何種原因不用呢?

這里是這段程序中最困難的部分,我也踩了很多坑。我參照的博客是使用正則表達(dá)式來抽取想要的信息的,但我對(duì)正則表達(dá)式掌握的并不好,弄了好久也沒寫出合適的表達(dá)式,于是我果斷放棄了使用正則表達(dá)式,采用BeautifulSoup來進(jìn)行信息的篩選。
BeautifulSoup用法參考

 # 將內(nèi)容從頁面源碼中提取出來
    def deal_data(self, myPage):

        soup = BeautifulSoup(myPage)

        # 從title屬性為有效成績的標(biāo)簽中獲取所有class屬性為t_con的TAG(tr標(biāo)簽)
        trs = soup.find(attrs={"title": "有效成績"}).findAll(attrs={"class": "t_con"})

        # 從tr標(biāo)簽中的td標(biāo)簽中獲取需要的信息。下標(biāo)為3,7,8的分別為課程名,學(xué)分,成績
        for tr in trs:
            for index, td in enumerate(tr.findAll('td')):  # enumerate能在for循環(huán)中使用下標(biāo)

                if index == 3:
                    print td.text
                elif index == 7:
                    self.weights.append(td.text.encode('utf8'))
                    print td.text
                elif index == 8:
                    self.points.append(td.text.encode('utf8'))
                    print td.text

            print

整個(gè)邏輯我簡單說一下,我覺得還可以改進(jìn)。
首先查找title屬性為”有效成績“的標(biāo)簽,通過上文的截圖我們可以知道這是那個(gè)div標(biāo)簽,之后在該div標(biāo)簽中定位class為t_con的tr標(biāo)簽。你也許會(huì)問為什么不直接定位到tr標(biāo)簽,因?yàn)楹竺娴木W(wǎng)頁代碼中還存在class 為t_con的tr標(biāo)簽,但不是我們需要的成績。然后在每個(gè)tr標(biāo)簽下抽取下標(biāo)為3,7,8的標(biāo)簽,這里我是把它存到數(shù)組里了。
接下來就清晰了,先打印成績信息,然后計(jì)算績點(diǎn)。

運(yùn)行截圖
運(yùn)行截圖

學(xué)渣一個(gè),績點(diǎn)低請(qǐng)忽略。

源碼

# encoding=utf8
import urllib
import urllib2
import cookielib
import re
import string
from BeautifulSoup import BeautifulSoup
import sys

reload(sys)
sys.setdefaultencoding('utf8')


class SDJZU_Crawler:
    # 聲明相關(guān)的屬性
    def __init__(self):

        self.loginUrl = 'http://urpe.sdjzu.edu.cn/loginPortalUrlForIndexLogin.portal'  # 登錄的url
        self.resultUrl = 'http://jwfw1.sdjzu.edu.cn/ssfw/jwnavmenu.do?menuItemWid=1E057E24ABAB4CAFE0540010E0235690'  # 查詢成績的url
        self.cookieJar = cookielib.CookieJar()  # 初始化一個(gè)CookieJar來處理Cookie的信息
        self.postdata = urllib.urlencode({'userName': '', 'password': ''})  # 登錄需要POST的數(shù)據(jù)
        self.weights = []  # 存儲(chǔ)權(quán)重,也就是學(xué)分
        self.points = []  # 存儲(chǔ)分?jǐn)?shù),也就是成績
        self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cookieJar))

    def sdjzu_init(self):
        username = raw_input('請(qǐng)輸入學(xué)號(hào):')  # 這里不要用input,二者區(qū)別請(qǐng)自行查詢
        password = raw_input('請(qǐng)輸入密碼:')
        self.postdata = urllib.urlencode({'userName': username, 'password': password})  # 將用戶名密碼加入到POST中
        # 初始化鏈接并且獲取cookie
        myRequest = urllib2.Request(url=self.loginUrl, data=self.postdata)  # 自定義一個(gè)請(qǐng)求
        result = self.opener.open(myRequest)  # 訪問登錄頁面,獲取到必須的cookie的值
        result = self.opener.open(self.resultUrl)  # 訪問成績頁面,獲得成績的數(shù)據(jù)
        self.deal_data(result.read())
        self.calculate_gpa()

    # 將內(nèi)容從頁面源碼中提取出來
    def deal_data(self, myPage):

        soup = BeautifulSoup(myPage)

        # 從title屬性為有效成績的標(biāo)簽中獲取所有class屬性為t_con的TAG(tr標(biāo)簽)
        trs = soup.find(attrs={"title": "有效成績"}).findAll(attrs={"class": "t_con"})

        # 從tr標(biāo)簽中的td標(biāo)簽中獲取需要的信息。下標(biāo)為3,7,8的分別為課程名,學(xué)分,成績
        for tr in trs:
            for index, td in enumerate(tr.findAll('td')):  # enumerate能在for循環(huán)中使用下標(biāo)

                if index == 3:
                    print td.text
                elif index == 7:
                    self.weights.append(td.text.encode('utf8'))
                    print td.text
                elif index == 8:
                    self.points.append(td.text.encode('utf8'))
                    print td.text

            print

    # 計(jì)算績點(diǎn),如果成績還沒出來,就不算該成績,
    def calculate_gpa(self):
        point = 0.0  # 成績
        weight = 0.0  # 學(xué)分
        for i in range(len(self.points)):
            if self.points[i].isdigit() and (self.weights[i] != 0):
                point += string.atof(self.points[i]) * string.atof(self.weights[i])  # 成績*學(xué)分累加求和
                weight += string.atof(self.weights[i])  # 學(xué)分累加求和

        print "績點(diǎn)為:"
        print point / weight  # 輸出績點(diǎn) 值成績*學(xué)分累加求和 / 學(xué)分累加求和


# 調(diào)用
mySpider = SDJZU_Crawler()
mySpider.sdjzu_init()

我的github地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 初衷 大一下學(xué)期期末只剩下高數(shù)考試時(shí),考前時(shí)間比較充裕,想自學(xué)Java,同時(shí)還看了很多爬蟲的故事,那是我第一次知道...
    pujiaxun閱讀 2,864評(píng)論 19 30
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,292評(píng)論 25 708
  • 我,窮學(xué)生一個(gè),以前的發(fā)音也很普通,舍不得花銀子去新東方報(bào)班,就探索了發(fā)音的自我訓(xùn)練方法,資料費(fèi)花了不到200塊,...
    佳娃讀書識(shí)人閱讀 3,584評(píng)論 0 7
  • 最近一直沒更簡書,實(shí)在是沒啥可寫的~畫是有藝堆半成品,因?yàn)樽罱恢痹诿χ隹椗?近期的新款都是以線跟手工為主的,...
    小雨_姐姐閱讀 337評(píng)論 27 39
  • Elsa站在地鐵兩節(jié)車廂的銜接處,晃晃悠悠,雙手一刻不停在手機(jī)屏幕上圈圈點(diǎn)點(diǎn),完成每天的英文單詞任務(wù)100個(gè),西班...
    艾瑞卡卡閱讀 282評(píng)論 0 1