剛開始接觸Python,看很多人玩爬蟲我也想玩皱坛,找來找去發(fā)現(xiàn)很多人用網(wǎng)絡(luò)爬蟲干的第一件事就是模擬登陸编曼,增加點(diǎn)難度就是模擬登陸后在獲取數(shù)據(jù),但是網(wǎng)上好少有Python 3.x的模擬登陸Demo可以參考剩辟,加上自己也不怎么懂Html掐场,所以這第一個(gè)Python爬蟲寫的異常艱難往扔,不過最終結(jié)果還是盡如人意的,下面把這次學(xué)習(xí)的過程整理一下熊户。
工具
- 系統(tǒng):win7 64位系統(tǒng)
- 瀏覽器:Chrome
- Python版本:Python 3.5 64-bit
- IDE:JetBrains PyCharm (貌似很多人都用這個(gè))
我把目標(biāo)瞄準(zhǔn)了我們的教務(wù)處萍膛,這次爬蟲的目的是從教務(wù)處獲取成績(jī)并且把成績(jī)輸入Excel表格中保存起來,我們學(xué)校教務(wù)處的地址是:http://jwc.ecjtu.jx.cn/ 嚷堡,往常每次我們獲取成績(jī)都需要先進(jìn)入教務(wù)處蝗罗,然后點(diǎn)擊成績(jī)查詢,輸入公共的賬號(hào)密碼進(jìn)入蝌戒,最后輸入相關(guān)信息獲取成績(jī)表格串塑,這里登陸不需要驗(yàn)證碼省了我一番功夫,這樣我們先進(jìn)入成績(jī)查詢系統(tǒng)登陸界面北苟,先看看怎么模擬登陸這個(gè)過程桩匪,在Chrome瀏覽器下按F12打開開發(fā)者面板:
#!/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模塊,還不會(huì)使用的可以查看中文文檔 菠发,它給自己的定義是:HTTP for Humans王滤,因?yàn)楹?jiǎn)單易用易上手,我們只需要傳入U(xiǎn)rl地址滓鸠,構(gòu)造請(qǐng)求頭雁乡,傳入post方法需要的數(shù)據(jù),就可以模擬瀏覽器登陸了糜俗,這里因?yàn)橛羞M(jìn)一步獲取成績(jī)的操作所以使用了session來保持連接踱稍,這里單看最后的返回碼的話我們是成功了的曲饱,具體如何還要看下一步操作,接下來:
這里為了簡(jiǎn)便代碼我們?cè)O(shè)定輸入學(xué)號(hào)查詢所有成績(jī)珠月,減少其他判斷扩淀,同樣對(duì)Post數(shù)據(jù)進(jìn)行抓包:
同樣查看Post的數(shù)據(jù):
因?yàn)檫@里就分析輸入學(xué)號(hào)的情況所以其他都為空,這樣我們就可以寫出查詢成績(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)
content = score_response.content
這里解釋一下上面的代碼啤挎,上面的score_url
并不是瀏覽器上顯示的地址驻谆,我們要獲取真正的地址,在Chrome下右鍵--查看網(wǎng)頁源代碼庆聘,找到這么一行:
a href=query.php?start=1&job=see&=&Name=&Course=&ClassID=&Term=&StuID=xxxxxxx
這個(gè)才是真正的地址胜臊,點(diǎn)擊這個(gè)地址轉(zhuǎn)入的才是真正的界面,因?yàn)檫@里成績(jī)數(shù)據(jù)較多掏觉,所以這里采用了分頁顯示区端,這個(gè)start=1
說明是第一頁,這個(gè)參數(shù)是可變的需要我們傳入澳腹,還有StuID
后面的是我們輸入的學(xué)號(hào)织盼,這樣我們就可以拼接出Url地址:
score_url = 'http://jwc.ecjtu.jx.cn/mis_o/query.php?start=' + str(pagenum) + '&job=see&=&Name=&Course=&ClassID=&Term=&StuID=' + num
同樣使用Post方法傳遞數(shù)據(jù)并獲取響應(yīng)的內(nèi)容:
score_response = sessions.post(score_url, data=score_data,headers=score_healders)
content = score_response.content
這里采用Beautiful Soup 4.2.0來解析返回的響應(yīng)內(nèi)容,因?yàn)槲覀円@取的是成績(jī)酱塔,這里到教務(wù)處成績(jī)查詢界面沥邻,查看獲取到的成績(jī)?cè)诰W(wǎng)頁中是以表格的形式存在:
觀察表格的網(wǎng)頁源代碼:
<table align=center border=1>
<tr><td bgcolor=009999>學(xué)期</td>
<td bgcolor=009999>學(xué)號(hào)</td>
<td bgcolor=009999>姓名</td>
<td bgcolor=009999>課程</td>
<td bgcolor=009999>課程要求</td>
<td bgcolor=009999>學(xué)分</td>
<td bgcolor=009999>成績(jī)</td>
<td bgcolor=009999>重考一</td>
<td bgcolor=009999>重考二</td></tr>
...
...
</tr></table>
這里拿出第一行舉例,雖然我不太懂Html但是從這里可以看出來<tr>
代表的是一行羊娃,而<td>
應(yīng)該是代表這一行中的每一列唐全,這樣就好辦了,取出每一行然后分解出每一列蕊玷,打印輸出就可以得到我們要的結(jié)果:
from bs4 import BeautifulSoup
soup = BeautifulSoup(content, 'html.parser')
# 找到每一行
target = soup.findAll('tr')
這里分解每一列的時(shí)候要小心邮利,因?yàn)檫@里表格分成了三頁顯示,每頁最多顯示30條數(shù)據(jù)垃帅,這里因?yàn)橹皇鞘占呀?jīng)畢業(yè)的學(xué)生的成績(jī)數(shù)據(jù)所以不對(duì)其他數(shù)據(jù)量不足的學(xué)生成績(jī)的情況做統(tǒng)計(jì)延届,默認(rèn)收集的都是大四畢業(yè)的學(xué)生成績(jī)數(shù)據(jù)。這里采用兩個(gè)變量i
和j
分別代表行和列:
# 注:這里的print單純是我為了驗(yàn)證結(jié)果打印在PyCharm的控制臺(tái)上而已
i=0, j=0
for tag in target[1:]:
tds = tag.findAll('td')
# 每一次都是從列頭開始獲取
j = 0
# 學(xué)期
semester = str(tds[0].string)
if semester == 'None':
break
else:
print(semester.ljust(6) + '\t\t\t', end='')
# 學(xué)號(hào)
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
# 學(xué)分
scredit = tds[5].string
print(scredit.ljust(2, ' ') + '\t\t', end='')
j += 1
# 成績(jī)
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
這里查了很多別人的博客都是用正則表達(dá)式來分解數(shù)據(jù)贸诚,表示自己的正則寫的并不好也嘗試了但是沒成功方庭,所以無奈選擇這種方式,如果有人有測(cè)試成功的正則歡迎跟我說一聲酱固,我也學(xué)習(xí)學(xué)習(xí)械念。
把數(shù)據(jù)保存到Excel
因?yàn)橐呀?jīng)清楚了這個(gè)網(wǎng)頁保存成績(jī)的具體結(jié)構(gòu),所以順著每次循環(huán)解析將數(shù)據(jù)不斷加以保存就是了运悲,這里使用xlwt
寫入數(shù)據(jù)到Excel龄减,因?yàn)?code>xlwt模塊打印輸出到Excel中的樣式寬度偏小,影響觀看扇苞,所以這里還加入了一個(gè)方法去控制打印到Excel表格中的樣式:
file = xlwt.Workbook(encoding='utf-8')
table = file.add_sheet('achieve')
# 設(shè)置Excel樣式
def set_style(name, height, bold=False):
style = xlwt.XFStyle() # 初始化樣式
font = xlwt.Font() # 為樣式創(chuàng)建字體
font.name = name # 'Times New Roman'
font.bold = bold
font.color_index = 4
font.height = height
style.font = font
return style
運(yùn)用到代碼中:
for tag in target[1:]:
tds = tag.findAll('td')
j = 0
# 學(xué)期
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))
# 學(xué)號(hào)
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))
# 學(xué)分
scredit = tds[5].string
print(scredit.ljust(2, ' ') + '\t\t', end='')
j += 1
table.write(i, j, scredit, set_style('Arial', 220))
# 成績(jī)
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')
最后稍加整合欺殿,寫成一個(gè)方法:
# 獲取成績(jī)
# 這里num代表輸入的學(xué)號(hào)寄纵,pagenum代表頁數(shù),總共76條數(shù)據(jù)脖苏,一頁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
# 學(xué)期
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))
# 學(xué)號(hào)
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))
# 學(xué)分
scredit = tds[5].string
print(scredit.ljust(2, ' ') + '\t\t', end='')
j += 1
table.write(i, j, scredit, set_style('Arial', 220))
# 成績(jī)
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('出了一點(diǎn)小Bug')
file.save('demo.xls')
在模擬登陸操作后增加一個(gè)判斷:
# 判斷是否登陸
def isLogin(num):
return_code = response.status_code
if return_code == 200:
if re.match(r"^\d{14}$", num):
print('請(qǐng)稍等')
else:
print('請(qǐng)輸入正確的學(xué)號(hào)')
return True
else:
return False
最后在__main__
中這么調(diào)用:
if __name__ == '__main__':
num = input('請(qǐng)輸入你的學(xué)號(hào):')
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
快捷鍵運(yùn)行程序:
控制臺(tái)會(huì)有如下輸出(這里只截取部分程拭,不要吐槽沒有對(duì)齊,這里我也用了格式化輸出還是不太行棍潘,不過最起碼出來了結(jié)果恃鞋,而且我們的目的是輸出到Excel中不是嗎)
然后去程序根目錄找看看有沒有生成一個(gè)叫demo.xls
的文件,我的程序就放在桌面亦歉,所以去桌面找:
點(diǎn)開查看是否成功獲刃衾恕:
至此,大功告成
小結(jié)
剛開始接觸Python一個(gè)星期的樣子肴楷,這次寫了這么一個(gè)簡(jiǎn)單的網(wǎng)絡(luò)爬蟲檢驗(yàn)一下學(xué)習(xí)成果水由,雖然程序還有些許Bug,不過總歸得到了一定收獲赛蔫,當(dāng)然也為下一步學(xué)習(xí)打下了基礎(chǔ)砂客,嗯哼,為了接下來批量獲取網(wǎng)絡(luò)上美女圖片并分類保存我會(huì)繼續(xù)自學(xué)Python呵恢,荊軻刺秦王~