剛開始接觸Python,看很多人玩爬蟲我也想玩,找來找去發現很多人用網絡爬蟲干的第一件事就是模擬登陸,增加點難度就是模擬登陸后在獲取數據,但是網上好少有Python 3.x的模擬登陸Demo可以參考,加上自己也不怎么懂Html,所以這第一個Python爬蟲寫的異常艱難,不過最終結果還是盡如人意的,下面把這次學習的過程整理一下。
工具
- 系統:win7 64位系統
- 瀏覽器:Chrome
- Python版本:Python 3.5 64-bit
- IDE:JetBrains PyCharm (貌似很多人都用這個)
我把目標瞄準了我們的教務處,這次爬蟲的目的是從教務處獲取成績并且把成績輸入Excel表格中保存起來,我們學校教務處的地址是:http://jwc.ecjtu.jx.cn/ ,往常每次我們獲取成績都需要先進入教務處,然后點擊成績查詢,輸入公共的賬號密碼進入,最后輸入相關信息獲取成績表格,這里登陸不需要驗證碼省了我一番功夫,這樣我們先進入成績查詢系統登陸界面,先看看怎么模擬登陸這個過程,在Chrome瀏覽器下按F12打開開發者面板:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import requests
url = 'http://jwc.ecjtu.jx.cn/mis_o/login.php'
datas = {'user': 'jwc',
'pass': 'jwc',
'Submit': '%CC%E1%BD%BB'
}
headers = {'Referer': 'http://jwc.ecjtu.jx.cn/mis_o/login.htm',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 '
'(KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.8',
}
sessions = requests.session()
response = sessions.post(url, headers=headers, data=datas)
print(response.status_code)
代碼輸出:
200
說明我們模擬登陸成功了,這里用到了Requests模塊,還不會使用的可以查看中文文檔 ,它給自己的定義是:HTTP for Humans,因為簡單易用易上手,我們只需要傳入Url地址,構造請求頭,傳入post方法需要的數據,就可以模擬瀏覽器登陸了,這里因為有進一步獲取成績的操作所以使用了session來保持連接,這里單看最后的返回碼的話我們是成功了的,具體如何還要看下一步操作,接下來:
這里為了簡便代碼我們設定輸入學號查詢所有成績,減少其他判斷,同樣對Post數據進行抓包:
同樣查看Post的數據:
因為這里就分析輸入學號的情況所以其他都為空,這樣我們就可以寫出查詢成績的代碼:
score_healders = {'Connection': 'keep-alive',
'User - Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) '
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36',
'Content - Type': 'application / x - www - form - urlencoded',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Content - Length': '69',
'Host': 'jwc.ecjtu.jx.cn',
'Referer': 'http: // jwc.ecjtu.jx.cn / mis_o / main.php',
'Upgrade - Insecure - Requests': '1',
'Accept - Language': 'zh - CN, zh;q = 0.8'
}
score_url = 'http://jwc.ecjtu.jx.cn/mis_o/query.php?start=' + str(
pagenum) + '&job=see&=&Name=&Course=&ClassID=&Term=&StuID=' + num
score_data = {'Name': '',
'StuID': num,
'Course': '',
'Term': '',
'ClassID': '',
'Submit': '%B2%E9%D1%AF'
}
score_response = sessions.post(score_url, data=score_data, headers=score_healders)
content = score_response.content
這里解釋一下上面的代碼,上面的score_url
并不是瀏覽器上顯示的地址,我們要獲取真正的地址,在Chrome下右鍵--查看網頁源代碼,找到這么一行:
a href=query.php?start=1&job=see&=&Name=&Course=&ClassID=&Term=&StuID=xxxxxxx
這個才是真正的地址,點擊這個地址轉入的才是真正的界面,因為這里成績數據較多,所以這里采用了分頁顯示,這個start=1
說明是第一頁,這個參數是可變的需要我們傳入,還有StuID
后面的是我們輸入的學號,這樣我們就可以拼接出Url地址:
score_url = 'http://jwc.ecjtu.jx.cn/mis_o/query.php?start=' + str(pagenum) + '&job=see&=&Name=&Course=&ClassID=&Term=&StuID=' + num
同樣使用Post方法傳遞數據并獲取響應的內容:
score_response = sessions.post(score_url, data=score_data,headers=score_healders)
content = score_response.content
這里采用Beautiful Soup 4.2.0來解析返回的響應內容,因為我們要獲取的是成績,這里到教務處成績查詢界面,查看獲取到的成績在網頁中是以表格的形式存在:
觀察表格的網頁源代碼:
<table align=center border=1>
<tr><td bgcolor=009999>學期</td>
<td bgcolor=009999>學號</td>
<td bgcolor=009999>姓名</td>
<td bgcolor=009999>課程</td>
<td bgcolor=009999>課程要求</td>
<td bgcolor=009999>學分</td>
<td bgcolor=009999>成績</td>
<td bgcolor=009999>重考一</td>
<td bgcolor=009999>重考二</td></tr>
...
...
</tr></table>
這里拿出第一行舉例,雖然我不太懂Html但是從這里可以看出來<tr>
代表的是一行,而<td>
應該是代表這一行中的每一列,這樣就好辦了,取出每一行然后分解出每一列,打印輸出就可以得到我們要的結果:
from bs4 import BeautifulSoup
soup = BeautifulSoup(content, 'html.parser')
# 找到每一行
target = soup.findAll('tr')
這里分解每一列的時候要小心,因為這里表格分成了三頁顯示,每頁最多顯示30條數據,這里因為只是收集已經畢業的學生的成績數據所以不對其他數據量不足的學生成績的情況做統計,默認收集的都是大四畢業的學生成績數據。這里采用兩個變量i
和j
分別代表行和列:
# 注:這里的print單純是我為了驗證結果打印在PyCharm的控制臺上而已
i=0, j=0
for tag in target[1:]:
tds = tag.findAll('td')
# 每一次都是從列頭開始獲取
j = 0
# 學期
semester = str(tds[0].string)
if semester == 'None':
break
else:
print(semester.ljust(6) + '\t\t\t', end='')
# 學號
studentid = tds[1].string
print(studentid.ljust(14) + '\t\t\t', end='')
j += 1
# 姓名
name = tds[2].string
print(name.ljust(3) + '\t\t\t', end='')
j += 1
# 課程
course = tds[3].string
print(course.ljust(20, ' ') + '\t\t\t', end='')
j += 1
# 課程要求
requirments = tds[4].string
print(requirments.ljust(10, ' ') + '\t\t', end='')
j += 1
# 學分
scredit = tds[5].string
print(scredit.ljust(2, ' ') + '\t\t', end='')
j += 1
# 成績
achievement = tds[6].string
print(achievement.ljust(2) + '\t\t', end='')
j += 1
# 重考一
reexaminef = tds[7].string
print(reexaminef.ljust(2) + '\t\t', end='')
j += 1
# 重考二
reexamines = tds[8].string
print(reexamines.ljust(2) + '\t\t')
j += 1
i += 1
這里查了很多別人的博客都是用正則表達式來分解數據,表示自己的正則寫的并不好也嘗試了但是沒成功,所以無奈選擇這種方式,如果有人有測試成功的正則歡迎跟我說一聲,我也學習學習。
把數據保存到Excel
因為已經清楚了這個網頁保存成績的具體結構,所以順著每次循環解析將數據不斷加以保存就是了,這里使用xlwt
寫入數據到Excel,因為xlwt
模塊打印輸出到Excel中的樣式寬度偏小,影響觀看,所以這里還加入了一個方法去控制打印到Excel表格中的樣式:
file = xlwt.Workbook(encoding='utf-8')
table = file.add_sheet('achieve')
# 設置Excel樣式
def set_style(name, height, bold=False):
style = xlwt.XFStyle() # 初始化樣式
font = xlwt.Font() # 為樣式創建字體
font.name = name # 'Times New Roman'
font.bold = bold
font.color_index = 4
font.height = height
style.font = font
return style
運用到代碼中:
for tag in target[1:]:
tds = tag.findAll('td')
j = 0
# 學期
semester = str(tds[0].string)
if semester == 'None':
break
else:
print(semester.ljust(6) + '\t\t\t', end='')
table.write(i, j, semester, set_style('Arial', 220))
# 學號
studentid = tds[1].string
print(studentid.ljust(14) + '\t\t\t', end='')
j += 1
table.write(i, j, studentid, set_style('Arial', 220))
table.col(i).width = 256 * 16
# 姓名
name = tds[2].string
print(name.ljust(3) + '\t\t\t', end='')
j += 1
table.write(i, j, name, set_style('Arial', 220))
# 課程
course = tds[3].string
print(course.ljust(20, ' ') + '\t\t\t', end='')
j += 1
table.write(i, j, course, set_style('Arial', 220))
# 課程要求
requirments = tds[4].string
print(requirments.ljust(10, ' ') + '\t\t', end='')
j += 1
table.write(i, j, requirments, set_style('Arial', 220))
# 學分
scredit = tds[5].string
print(scredit.ljust(2, ' ') + '\t\t', end='')
j += 1
table.write(i, j, scredit, set_style('Arial', 220))
# 成績
achievement = tds[6].string
print(achievement.ljust(2) + '\t\t', end='')
j += 1
table.write(i, j, achievement, set_style('Arial', 220))
# 重考一
reexaminef = tds[7].string
print(reexaminef.ljust(2) + '\t\t', end='')
j += 1
table.write(i, j, reexaminef, set_style('Arial', 220))
# 重考二
reexamines = tds[8].string
print(reexamines.ljust(2) + '\t\t')
j += 1
table.write(i, j, reexamines, set_style('Arial', 220))
i += 1
file.save('demo.xls')
最后稍加整合,寫成一個方法:
# 獲取成績
# 這里num代表輸入的學號,pagenum代表頁數,總共76條數據,一頁30條所以總共有三頁
def getScore(num, pagenum, i, j):
score_healders = {'Connection': 'keep-alive',
'User - Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) '
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36',
'Content - Type': 'application / x - www - form - urlencoded',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Content - Length': '69',
'Host': 'jwc.ecjtu.jx.cn',
'Referer': 'http: // jwc.ecjtu.jx.cn / mis_o / main.php',
'Upgrade - Insecure - Requests': '1',
'Accept - Language': 'zh - CN, zh;q = 0.8'
}
score_url = 'http://jwc.ecjtu.jx.cn/mis_o/query.php?start=' + str(
pagenum) + '&job=see&=&Name=&Course=&ClassID=&Term=&StuID=' + num
score_data = {'Name': '',
'StuID': num,
'Course': '',
'Term': '',
'ClassID': '',
'Submit': '%B2%E9%D1%AF'
}
score_response = sessions.post(score_url, data=score_data, headers=score_healders)
# 輸出到文本
with open('text.txt', 'wb') as f:
f.write(score_response.content)
content = score_response.content
soup = BeautifulSoup(content, 'html.parser')
target = soup.findAll('tr')
try:
for tag in target[1:]:
tds = tag.findAll('td')
j = 0
# 學期
semester = str(tds[0].string)
if semester == 'None':
break
else:
print(semester.ljust(6) + '\t\t\t', end='')
table.write(i, j, semester, set_style('Arial', 220))
# 學號
studentid = tds[1].string
print(studentid.ljust(14) + '\t\t\t', end='')
j += 1
table.write(i, j, studentid, set_style('Arial', 220))
table.col(i).width = 256 * 16
# 姓名
name = tds[2].string
print(name.ljust(3) + '\t\t\t', end='')
j += 1
table.write(i, j, name, set_style('Arial', 220))
# 課程
course = tds[3].string
print(course.ljust(20, ' ') + '\t\t\t', end='')
j += 1
table.write(i, j, course, set_style('Arial', 220))
# 課程要求
requirments = tds[4].string
print(requirments.ljust(10, ' ') + '\t\t', end='')
j += 1
table.write(i, j, requirments, set_style('Arial', 220))
# 學分
scredit = tds[5].string
print(scredit.ljust(2, ' ') + '\t\t', end='')
j += 1
table.write(i, j, scredit, set_style('Arial', 220))
# 成績
achievement = tds[6].string
print(achievement.ljust(2) + '\t\t', end='')
j += 1
table.write(i, j, achievement, set_style('Arial', 220))
# 重考一
reexaminef = tds[7].string
print(reexaminef.ljust(2) + '\t\t', end='')
j += 1
table.write(i, j, reexaminef, set_style('Arial', 220))
# 重考二
reexamines = tds[8].string
print(reexamines.ljust(2) + '\t\t')
j += 1
table.write(i, j, reexamines, set_style('Arial', 220))
i += 1
except:
print('出了一點小Bug')
file.save('demo.xls')
在模擬登陸操作后增加一個判斷:
# 判斷是否登陸
def isLogin(num):
return_code = response.status_code
if return_code == 200:
if re.match(r"^\d{14}$", num):
print('請稍等')
else:
print('請輸入正確的學號')
return True
else:
return False
最后在__main__
中這么調用:
if __name__ == '__main__':
num = input('請輸入你的學號:')
if isLogin(num):
getScore(num, pagenum=0, i=0, j=0)
getScore(num, pagenum=1, i=31, j=0)
getScore(num, pagenum=2, i=62, j=0)
在PyCharm下按alt
+shift
+x
快捷鍵運行程序:
控制臺會有如下輸出(這里只截取部分,不要吐槽沒有對齊,這里我也用了格式化輸出還是不太行,不過最起碼出來了結果,而且我們的目的是輸出到Excel中不是嗎)
然后去程序根目錄找看看有沒有生成一個叫demo.xls
的文件,我的程序就放在桌面,所以去桌面找:
點開查看是否成功獲取:
至此,大功告成
小結
剛開始接觸Python一個星期的樣子,這次寫了這么一個簡單的網絡爬蟲檢驗一下學習成果,雖然程序還有些許Bug,不過總歸得到了一定收獲,當然也為下一步學習打下了基礎,嗯哼,為了接下來批量獲取網絡上美女圖片并分類保存我會繼續自學Python,荊軻刺秦王~