人生苦短,我用Python--爬蟲模擬登陸教務處并且保存數據到本地

剛開始接觸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打開開發者面板:

開發者面板
這里我們學校的教務處查詢系統的密碼是公共的jwc也就是拼音縮寫,我們輸入用戶名和密碼點擊登陸,這時候注意POST請求:
注意post請求
發現了什么,好像Chrome并沒有把Post提交的表單信息保留下來直接跳轉到了另一個界面然后展示另一個界面的數據,這里就需要我們自己動手操作一下,注意開發者面板左上角的小紅點表示這時候正在抓取數據,如果點擊一下就會變成灰色,就可以變相地保存下當時抓取到的包,我在點擊登陸后新界面未刷新出來之前點擊了這個小紅點,如愿以償的得到了Post的表單數據:
得到post表單數據
這樣就獲取了瀏覽器在登陸時候向服務器傳遞的表單數據,看一下這個表單都有些什么:
查看表單數據
這里看到我們需要傳遞三個參數,分別是:user、pass、Submit,可以很容易的理解這幾個單詞的字面意思,這樣有了思路,我們就可以寫出這次代碼的第一步:模擬登陸教務處直接上代碼:

#!/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數據抓包

同樣查看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條數據,這里因為只是收集已經畢業的學生的成績數據所以不對其他數據量不足的學生成績的情況做統計,默認收集的都是大四畢業的學生成績數據。這里采用兩個變量ij分別代表行和列:

# 注:這里的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,荊軻刺秦王~

源碼

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,185評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,656評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,647評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,446評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,951評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,189評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,718評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,800評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,419評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,420評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,755評論 2 371

推薦閱讀更多精彩內容