首次寫爬蟲!,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