鍍金池/ 教程/ Python/ 一個爬蟲的誕生全過程(以山東大學(xué)績點運算為例)
一個簡單的百度貼吧的小爬蟲
亮劍!爬蟲框架小抓抓 Scrapy 閃亮登場!
Opener 與 Handler 的介紹和實例應(yīng)用
百度貼吧的網(wǎng)絡(luò)爬蟲(v0.4)源碼及解析
異常的處理和 HTTP 狀態(tài)碼的分類
利用 urllib2 通過指定的 URL 抓取網(wǎng)頁內(nèi)容
Python 中的正則表達(dá)式教程
爬蟲框架 Scrapy 的第一個爬蟲示例入門教程
抓取網(wǎng)頁的含義和 URL 基本構(gòu)成
urllib2 的使用細(xì)節(jié)與抓站技巧
一個爬蟲的誕生全過程(以山東大學(xué)績點運算為例)
糗事百科的網(wǎng)絡(luò)爬蟲(v0.3)源碼及解析(簡化更新)

一個爬蟲的誕生全過程(以山東大學(xué)績點運算為例)

先來說一下我們學(xué)校的網(wǎng)站:

http://jwxt.sdu.edu.cn:7777/zhxt_bks/zhxt_bks.html

查詢成績需要登錄,然后顯示各學(xué)科成績,但是只顯示成績而沒有績點,也就是加權(quán)平均分。

http://wiki.jikexueyuan.com/project/python-crawler/images/13.png" alt="" />

顯然這樣手動計算績點是一件非常麻煩的事情。所以我們可以用python做一個爬蟲來解決這個問題。

決戰(zhàn)前夜

先來準(zhǔn)備一下工具:HttpFox 插件。這是一款 http 協(xié)議分析插件,分析頁面請求和響應(yīng)的時間、內(nèi)容、以及瀏覽器用到的 COOKIE 等。以我為例,安裝在火狐上即可,效果如圖:

http://wiki.jikexueyuan.com/project/python-crawler/images/14.png" alt="" />

可以非常直觀的查看相應(yīng)的信息。點擊 start 是開始檢測,點擊 stop 暫停檢測,點擊 clear 清除內(nèi)容。 一般在使用之前,點擊 stop 暫停,然后點擊 clear 清屏,確??吹降氖窃L問當(dāng)前頁面獲得的數(shù)據(jù)。

深入敵后

下面就去山東大學(xué)的成績查詢網(wǎng)站,看一看在登錄的時候,到底發(fā)送了那些信息。先來到登錄頁面,把 httpfox 打開,clear 之后,點擊 start 開啟檢測:

http://wiki.jikexueyuan.com/project/python-crawler/images/15.png" alt="" />

輸入完了個人信息,確保 httpfox 處于開啟狀態(tài),然后點擊確定提交信息,實現(xiàn)登錄。這個時候可以看到,httpfox 檢測到了三條信息:

http://wiki.jikexueyuan.com/project/python-crawler/images/16.png" alt="" />

這時點擊 stop 鍵,確保捕獲到的是訪問該頁面之后反饋的數(shù)據(jù),以便我們做爬蟲的時候模擬登陸使用。

庖丁解牛

乍一看我們拿到了三個數(shù)據(jù),兩個是 GET 的一個是 POST 的,但是它們到底是什么,應(yīng)該怎么用,我們還一無所知。所以,我們需要挨個查看一下捕獲到的內(nèi)容。先看 POST 的信息:

http://wiki.jikexueyuan.com/project/python-crawler/images/17.png" alt="" />

既然是 POST 的信息,我們就直接看 PostData 即可??梢钥吹揭还?POST 兩個數(shù)據(jù),stuid 和 pwd。 并且從 Type 的 Redirect to 可以看出,POST 完畢之后跳轉(zhuǎn)到了 bks_login2.loginmessage 頁面。 由此看出,這個數(shù)據(jù)是點擊確定之后提交的表單數(shù)據(jù)。點擊 cookie 標(biāo)簽,看看 cookie 信息:

http://wiki.jikexueyuan.com/project/python-crawler/images/18.png" alt="" />

沒錯,收到了一個 ACCOUNT 的 cookie,并且在 session 結(jié)束之后自動銷毀。那么提交之后收到了哪些信息呢? 我們來看看后面的兩個 GET 數(shù)據(jù)。先看第一個,我們點擊 content 標(biāo)簽可以查看收到的內(nèi)容,是不是有一種生吞活剝的快感-。-HTML 源碼暴露無疑了:

http://wiki.jikexueyuan.com/project/python-crawler/images/19.png" alt="" />

看來這個只是顯示頁面的 html 源碼而已,點擊 cookie,查看 cookie 的相關(guān)信息:

http://wiki.jikexueyuan.com/project/python-crawler/images/20.png" alt="" />

啊哈,原來 html 頁面的內(nèi)容是發(fā)送了 cookie 信息之后才接受到的。再來看看最后一個接收到的信息:

http://wiki.jikexueyuan.com/project/python-crawler/images/21.png" alt="" />

大致看了一下應(yīng)該只是一個叫做 style.css 的 css文件,對我們沒有太大的作用。

冷靜應(yīng)戰(zhàn)

既然已經(jīng)知道了我們向服務(wù)器發(fā)送了什么數(shù)據(jù),也知道了我們接收到了什么數(shù)據(jù),基本的流程如下:

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

OK,看上去好像很簡單的樣紙。那下面我們就來試試看吧。但是在實驗之前,還有一個問題沒有解決,就是 POST 的數(shù)據(jù)到底發(fā)送到了哪里?再來看一下當(dāng)初的頁面:

http://wiki.jikexueyuan.com/project/python-crawler/images/22.png" alt="" />

很明顯是用一個 html 框架來實現(xiàn)的,也就是說,我們在地址欄看到的地址并不是右邊提交表單的地址。那么怎樣才能獲得真正的地址。右擊查看頁面源代碼:嗯沒錯,那個 name="w_right" 的就是我們要的登錄頁面。 網(wǎng)站的原來的地址是:
http://jwxt.sdu.edu.cn:7777/zhxt_bks/zhxt_bks.html
所以,真正的表單提交的地址應(yīng)該是:
http://jwxt.sdu.edu.cn:7777/zhxt_bks/xk_login.html
輸入一看,果不其然:

http://wiki.jikexueyuan.com/project/python-crawler/images/23.png" alt="" />

居然是清華大學(xué)的選課系統(tǒng)。。。目測是我校懶得做頁面了就直接借了。。結(jié)果連標(biāo)題都不改一下。。。 但是這個頁面依舊不是我們需要的頁面,因為我們的 POST 數(shù)據(jù)提交到的頁面,應(yīng)該是表單 form 的 ACTION 中提交到的頁面。也就是說,我們需要查看源碼,來知道 POST 數(shù)據(jù)到底發(fā)送到了哪里:

http://wiki.jikexueyuan.com/project/python-crawler/images/24.png" alt="" />

嗯,目測這個才是提交 POST 數(shù)據(jù)的地址。

整理到地址欄中,完整的地址應(yīng)該如下:
http://jwxt.sdu.edu.cn:7777/pls/wwwbks/bks_login2.login
(獲取的方式很簡單,在火狐瀏覽器中直接點擊那個鏈接就能看到這個鏈接的地址了)

小試牛刀

接下來的任務(wù)就是:用 python 模擬發(fā)送一個 POST 的數(shù)據(jù)并取到返回的 cookie 值。
關(guān)于 cookie 的操作可以看看這篇博文:
http://blog.csdn.net/wxg694175346/article/details/8925978
我們先準(zhǔn)備一個 POST 的數(shù)據(jù),再準(zhǔn)備一個 cookie 的接收,然后寫出源碼如下:

# -*- coding: utf-8 -*-  
#---------------------------------------  
#   程序:山東大學(xué)爬蟲  
#   版本:0.1  
#   作者:why  
#   日期:2013-07-12  
#   語言:Python 2.7  
#   操作:輸入學(xué)號和密碼  
#   功能:輸出成績的加權(quán)平均值也就是績點  
#---------------------------------------  

import urllib    
import urllib2  
import cookielib  

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

#需要POST的數(shù)據(jù)#  
postdata=urllib.urlencode({    
    'stuid':'201100300428',    
    'pwd':'921030'    
})  

#自定義一個請求#  
req = urllib2.Request(    
    url = 'http://jwxt.sdu.edu.cn:7777/pls/wwwbks/bks_login2.login',    
    data = postdata  
)  

#訪問該鏈接#  
result = opener.open(req)  

#打印返回的內(nèi)容#  
print result.read()     

如此這般之后,再看看運行的效果:

http://wiki.jikexueyuan.com/project/python-crawler/images/25.png" alt="" />

ok,如此這般,我們就算模擬登陸成功了。

偷天換日

接下來的任務(wù)就是用爬蟲獲取到學(xué)生的成績。再來看看源網(wǎng)站。開啟 HTTPFOX 之后,點擊查看成績,發(fā)現(xiàn)捕獲到了如下的數(shù)據(jù):

http://wiki.jikexueyuan.com/project/python-crawler/images/26.png" alt="" />

點擊第一個 GET 的數(shù)據(jù),查看內(nèi)容可以發(fā)現(xiàn) Content 就是獲取到的成績的內(nèi)容。

而獲取到的頁面鏈接,從頁面源代碼中右擊查看元素,可以看到點擊鏈接之后跳轉(zhuǎn)的頁面(火狐瀏覽器只需要右擊,“查看此框架”,即可):

http://wiki.jikexueyuan.com/project/python-crawler/images/27.png" alt="" />

從而可以得到查看成績的鏈接如下:
http://jwxt.sdu.edu.cn:7777/pls/wwwbks/bkscjcx.curscopre

萬事俱備

現(xiàn)在萬事俱備啦,所以只需要把鏈接應(yīng)用到爬蟲里面,看看能否查看到成績的頁面。從 httpfox 可以看到,我們發(fā)送了一個 cookie 才能返回成績的信息,所以我們就用 python 模擬一個 cookie 的發(fā)送,以此來請求成績的信息:

# -*- coding: utf-8 -*-  
#---------------------------------------  
#   程序:山東大學(xué)爬蟲  
#   版本:0.1  
#   作者:why  
#   日期:2013-07-12  
#   語言:Python 2.7  
#   操作:輸入學(xué)號和密碼  
#   功能:輸出成績的加權(quán)平均值也就是績點  
#---------------------------------------  

import urllib    
import urllib2  
import cookielib  

#初始化一個CookieJar來處理Cookie的信息#  
cookie = cookielib.CookieJar()  

#創(chuàng)建一個新的opener來使用我們的CookieJar#  
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie))  

#需要POST的數(shù)據(jù)#  
postdata=urllib.urlencode({    
    'stuid':'201100300428',    
    'pwd':'921030'    
})  

#自定義一個請求#  
req = urllib2.Request(    
    url = 'http://jwxt.sdu.edu.cn:7777/pls/wwwbks/bks_login2.login',    
    data = postdata  
)  

#訪問該鏈接#  
result = opener.open(req)  

#打印返回的內(nèi)容#  
print result.read()  

#打印cookie的值  
for item in cookie:    
    print 'Cookie:Name = '+item.name    
    print 'Cookie:Value = '+item.value  

#訪問該鏈接#  
result = opener.open('http://jwxt.sdu.edu.cn:7777/pls/wwwbks/bkscjcx.curscopre')  

#打印返回的內(nèi)容#  
print result.read()  

按下 F5 運行即可,看看捕獲到的數(shù)據(jù)吧:

http://wiki.jikexueyuan.com/project/python-crawler/images/29.png" alt="" />

既然這樣就沒有什么問題了吧,用正則表達(dá)式將數(shù)據(jù)稍稍處理一下,取出學(xué)分和相應(yīng)的分?jǐn)?shù)就可以了。

手到擒來

這么一大堆 html 源碼顯然是不利于我們處理的,下面要用正則表達(dá)式來摳出必須的數(shù)據(jù)。
關(guān)于正則表達(dá)式的教程可以看看這個博文:
http://blog.csdn.net/wxg694175346/article/details/8929576

我們來看看成績的源碼:

http://wiki.jikexueyuan.com/project/python-crawler/images/30.png" alt="" />

既然如此,用正則表達(dá)式就易如反掌了。

我們將代碼稍稍整理一下,然后用正則來取出數(shù)據(jù):

# -*- coding: utf-8 -*-  
#---------------------------------------  
#   程序:山東大學(xué)爬蟲  
#   版本:0.1  
#   作者:why  
#   日期:2013-07-12  
#   語言:Python 2.7  
#   操作:輸入學(xué)號和密碼  
#   功能:輸出成績的加權(quán)平均值也就是績點  
#---------------------------------------  

import urllib    
import urllib2  
import cookielib  
import re  

class SDU_Spider:    
    # 申明相關(guān)的屬性    
    def __init__(self):      
        self.loginUrl = 'http://jwxt.sdu.edu.cn:7777/pls/wwwbks/bks_login2.login'   # 登錄的url  
        self.resultUrl = 'http://jwxt.sdu.edu.cn:7777/pls/wwwbks/bkscjcx.curscopre' # 顯示成績的url  
        self.cookieJar = cookielib.CookieJar()                                      # 初始化一個CookieJar來處理Cookie的信息  
        self.postdata=urllib.urlencode({'stuid':'201100300428','pwd':'921030'})     # POST的數(shù)據(jù)  
        self.weights = []   #存儲權(quán)重,也就是學(xué)分  
        self.points = []    #存儲分?jǐn)?shù),也就是成績  
        self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cookieJar))  

    def sdu_init(self):  
        # 初始化鏈接并且獲取cookie  
        myRequest = urllib2.Request(url = self.loginUrl,data = self.postdata)   # 自定義一個請求  
        result = self.opener.open(myRequest)            # 訪問登錄頁面,獲取到必須的cookie的值  
        result = self.opener.open(self.resultUrl)       # 訪問成績頁面,獲得成績的數(shù)據(jù)  
        # 打印返回的內(nèi)容  
        # print result.read()  
        self.deal_data(result.read().decode('gbk'))  
        self.print_data(self.weights);  
        self.print_data(self.points);  

    # 將內(nèi)容從頁面代碼中摳出來    
    def deal_data(self,myPage):    
        myItems = re.findall('<TR>.*?<p.*?<p.*?<p.*?<p.*?<p.*?>(.*?)</p>.*?<p.*?<p.*?>(.*?)</p>.*?</TR>',myPage,re.S)     #獲取到學(xué)分  
        for item in myItems:  
            self.weights.append(item[0].encode('gbk'))  
            self.points.append(item[1].encode('gbk'))  

    # 將內(nèi)容從頁面代碼中摳出來  
    def print_data(self,items):    
        for item in items:    
            print item  

#調(diào)用    
mySpider = SDU_Spider()    
mySpider.sdu_init()    

水平有限,正則是有點丑。運行的效果如圖:

http://wiki.jikexueyuan.com/project/python-crawler/images/31.png" alt="" />

ok,接下來的只是數(shù)據(jù)的處理問題了。。

凱旋而歸

完整的代碼如下,至此一個完整的爬蟲項目便完工了。

# -*- coding: utf-8 -*-  
#---------------------------------------  
#   程序:山東大學(xué)爬蟲  
#   版本:0.1  
#   作者:why  
#   日期:2013-07-12  
#   語言:Python 2.7  
#   操作:輸入學(xué)號和密碼  
#   功能:輸出成績的加權(quán)平均值也就是績點  
#---------------------------------------  

import urllib    
import urllib2  
import cookielib  
import re  
import string  

class SDU_Spider:    
    # 申明相關(guān)的屬性    
    def __init__(self):      
        self.loginUrl = 'http://jwxt.sdu.edu.cn:7777/pls/wwwbks/bks_login2.login'   # 登錄的url  
        self.resultUrl = 'http://jwxt.sdu.edu.cn:7777/pls/wwwbks/bkscjcx.curscopre' # 顯示成績的url  
        self.cookieJar = cookielib.CookieJar()                                      # 初始化一個CookieJar來處理Cookie的信息  
        self.postdata=urllib.urlencode({'stuid':'201100300428','pwd':'921030'})     # POST的數(shù)據(jù)  
        self.weights = []   #存儲權(quán)重,也就是學(xué)分  
        self.points = []    #存儲分?jǐn)?shù),也就是成績  
        self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cookieJar))  

    def sdu_init(self):  
        # 初始化鏈接并且獲取cookie  
        myRequest = urllib2.Request(url = self.loginUrl,data = self.postdata)   # 自定義一個請求  
        result = self.opener.open(myRequest)            # 訪問登錄頁面,獲取到必須的cookie的值  
        result = self.opener.open(self.resultUrl)       # 訪問成績頁面,獲得成績的數(shù)據(jù)  
        # 打印返回的內(nèi)容  
        # print result.read()  
        self.deal_data(result.read().decode('gbk'))  
        self.calculate_date();  

    # 將內(nèi)容從頁面代碼中摳出來    
    def deal_data(self,myPage):    
        myItems = re.findall('<TR>.*?<p.*?<p.*?<p.*?<p.*?<p.*?>(.*?)</p>.*?<p.*?<p.*?>(.*?)</p>.*?</TR>',myPage,re.S)     #獲取到學(xué)分  
        for item in myItems:  
            self.weights.append(item[0].encode('gbk'))  
            self.points.append(item[1].encode('gbk'))  

    #計算績點,如果成績還沒出來,或者成績是優(yōu)秀良好,就不運算該成績  
    def calculate_date(self):  
        point = 0.0  
        weight = 0.0  
        for i in range(len(self.points)):  
            if(self.points[i].isdigit()):  
                point += string.atof(self.points[i])*string.atof(self.weights[i])  
                weight += string.atof(self.weights[i])  
        print point/weight  

#調(diào)用    
mySpider = SDU_Spider()    
mySpider.sdu_init()