1. 程式人生 > >首次寫爬蟲!,requests庫加beautifulsoup(美味湯)爬取學校教室課程表

首次寫爬蟲!,requests庫加beautifulsoup(美味湯)爬取學校教室課程表

一個學校朋友讓我幫他做一個查詢空餘教室的小程式,然而又沒有現成的資料,只有自己動手找了.

不過我也是第一次寫爬蟲

我們學校教務網中有一個欄目是關於全校課程的彙總,其中有一個就是按教室進行分類,每一間教室都有一張獨立的課程表,其中記錄了該教室一個周要上的課和上課週數.

找到了資料,那就開始動手爬吧!

高能! 由於學校的教務系統不知道是哪個年代做的了,所以裡面可能會出現各種各樣的坑.

首先選定我們的資料庫,我習慣於使用mysql資料庫,而Python也有很成熟的類庫可以使用.

然後設計儲存結構,首先我們需要看一下我們可以得到的資料和需要的資料:

       

從這裡可以看到,我們可以從這個表格中獲取到教室位置, 校區, 容納人數, 教室類別這幾種資料

從教室的課表中可以得到上課周時間,單雙週上課情況,上課人數,上課課程等等. 目前我們需要的資料只有上課周,教室位置,校區,容納人數,單雙週上課情況,教室種類.

現在可以建立資料表了:

    {

int id,
//主鍵
varchar address,
//教室位置
varchar odd,
//單週
varchar even,
//雙週
varchar area,
//校區
varchar category,
//種類
varchar population,
//容納人數
 }

我們先在資料庫中建立好資料表,具體步驟我就不寫了.

現在開啟第一張圖的網頁,檢視請求頭和原始碼

呃,好像有點兒長,就這樣吧.

開始動手寫

import requests as req
from bs4 import BeautifulSoup as bs
import re
import MySQLdb

構建initReq函式:

def initReq(url, header):
    response = req.get(url, headers=header)
    content = bs(response.content, "html.parser")
    return content
首先獲取教室表格
header={
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299",
    "Referer": "http://jiaowu.xxxxxx.edu.cn/web/web/lanmu/kebiao.asp",
    "Cookie": "Hm_lvt_20274609f261feba8dcea77ff3f7070c=1523195886; ASPSESSIONIDCCAADCDT=GPKEFCGBHNKNLHIJBDMHBMGD; jcrj%5Fuser=web; jcrj%5Fpwd=web; jcrj%5Fauth=True; jcrj%5Fsession=jwc%5Fcheck%2Cauth%2Cuser%2Cpwd%2Cjwc%5Fcheck%2Ctymfg%2Csf%2C; jcrj%5Fjwc%5Fcheck=y; jcrj%5Ftymfg=%C0%B6%C9%AB%BA%A3%CC%B2; jcrj%5Fsf=%D1%A7%C9%FA"}
    response=res.initReq("http://jiaowu.xxxxx.edu.cn/web/web/lanmu/jshi.asp",header=header)
    res.getClassRoom(response)

構建GetClassRoom函式,從已經獲取到html中獲取教室資訊:

def getClassRoom(html):
    table = html.find_all("table")
    tr = table[-2].find_all("tr")#find_all方法會遍歷出所有的指定元素,返回一個列表
    for i in tr[1:]:
        roomInfo = i.find_all("td")
        area = roomInfo[1].get_text()
        category = roomInfo[4].get_text()
        address = roomInfo[0].find("input")
        address = address["value"]
        link = roomInfo[-1].find("a")
        link = link["href"]
        population = roomInfo[3].get_text()
        getRoomInfo(link=link, address=address, category=category, area=area, population=population)
def getTable(url):
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299",
        "Referer": "http://jiaowu.xxxxx.edu.cn/web/web/lanmu/jshi.asp",
        "Cookie": "Hm_lvt_20274609f261feba8dcea77ff3f7070c=1523195886; ASPSESSIONIDCCAADCDT=GPKEFCGBHNKNLHIJBDMHBMGD;jcrj%5Fuser=web; jcrj%5Fpwd=web; jcrj%5Fauth=True;jcrj%5Fsession=jwc%5Fcheck%2Cauth%2Cuser%2Cpwd%2Cjwc%5Fcheck%2Ctymfg%2Csf%2C;jcrj%5Fjwc%5Fcheck=y;jcrj%5Ftymfg=%C0%B6%C9%AB%BA%A3%CC%B2;jcrj%5Fsf=%D1%A7%C9%FA"}
    result = initReq("http://jiaowu.xxxx.edu.cn/web/web/lanmu/" + url, headers)
    return result

defTable是為了得到課程表

我先一步一步的對html進行解剖,然後提取相應的資料.

在這一步可以得到每一間教室的課程表連線,現在還需要一個獲取課標資訊的操作,也就是上面我們call的getRoomInfo函式.

def getRoomInfo(link, category, area, address, population):
    result = getTable(link)
    table = result.find_all("table")
    table = table[2].find_all("table")
    tr = table[2].find_all("tr")
    # print(tr[1].find_all("tr"))
    l = 0
    tr = tr[1].find_all("tr")
    for i in tr:
        l += 1
        tds = i.find_all("td")
        w = 0
        for td in tds[-7:]:
            w += 1
            result = getLeason(td)
            leason = str(w) + "-" + str(l)
            db.con(category=category, area=area, address=address, curLeasonInfo=result, population=population,
                   leason=leason)

現在提取出整張課程表,


從htm中可以看出tr的子節點td中就是我們需要的資料資訊,我們可以通過find_all方法提出所有的tr,然後再遍歷tr提取出其中td,最後使用get_text()方法得到節點中的資料

但是根據課表可以看出,tr中存在合併後的單元格,那麼在提取資料時如果從右向左進行遍歷肯定是不行的,我們需要截取出真正蘊含資訊的部分.

在這裡的時候我遇到了一個問題


這裡存在一個空tr,這個空tr為啥會出現呢?有什麼意義?我不知道,但是我知道他給我接下來的操作造成了麻煩.

我用find_all獲取tr標記的時候發現出現了問題,最後資料提取出來全都是錯亂的,課程時間完全不對,甚至一天多出來了十節課. 等我輸出這個tr的時候發現, tr中原本應該只有七條的列表多出來好幾條.然後我想到了這個多出來的tr標記.


這裡看不清,放個大圖


現在可以看到,這個tr中蘊含了我需要的所有tr. 接下來,我要驗證我的猜想(教務網編寫人員的失誤----tr未結束):


這是原始html.

這是在火狐中開發者模式中的.

測試程式碼

from bs4 import BeautifulSoup as bs
text="""
<table>
<tbody>
	<tr><td>a</td></tr>	<tr>  <tr><td>a</td></tr>	<tr><td>a</td></tr>	<tr><td>a</td></tr>
</tbody>
"""
html=bs(text,"html.parser")
print(html.find_all("tr"))
結果:


從結果中我們可以看到,這裡我們原本未結束的tr在find_all執行的時候自動把它給補全了!!!!!而之前的瀏覽器開發者模式中顯示的又是瀏覽器自動優化後的結果,這誤導直接造成了我們最後的結果錯誤.

def getTr(html):
    tr = []
    j = 0
    for i in html[1:]:
        j += 1
        k = 0
        trTemp = i.find_all("tr")
        if trTemp:
            for z in trTemp:
                if len(z) > 3:  # 此處存在判定問題,因為教務網中一個tr錯誤,這裡trtemp列表中會多出一條包含所有的剩餘tr的項
                    k += 1
                    tr.append(z)
                    pass
                pass
            pass
    return tr

上面給出的程式碼是改造好的程式碼. 我們可以tr列表中看到列表第二項就已經包含了所有資訊,所以我們只需要得到第二項就可以了

def getLeason(td):
    text = td.get_text()
    mask = re.compile("[1-9]-[1-9]{2}")
    week = mask.findall(text)
    single = 0  # 是否單週有課
    double = 0
    if len(week) != 0:
        if re.search(r"\(單\)", text):
            temp = re.compile("\(單\)\n[1-9]-[0-9]{2}")  # 編譯尋找單週上課時間的上課周的正則
            text1 = temp.search(text)
            if text1:
                result = mask.search(text1.group())  # 找出上課周
                single = result.group()  # 單週上課時間
            pass
        elif re.search(r"\(雙\)", text):
            double = 1
            temp = re.compile("\(雙\)\n[1-9]-[0-9]{2}")  # 編譯尋找單週上課時間的上課周的正則
            text1 = temp.search(text)
            if text1:
                result = mask.search(text1.group())  # 找出上課周
                double = result.group()  # 雙週上課時間
            pass
        else:
            single = double = week[0]  # 上課不分單雙週
            pass
    else:
        print("本堂沒課")
        pass
    return {"single": single, "double": double}

使用re庫提取處對應資訊,這裡需要區分單雙週上課,所以正則表示式

\(單\)\n[1-9]-[0-9]{2}

這一步為了得到整個單週上課資訊,然後再提出上課時間

[1-9]-[0-9]{2}
End