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

剛開始接觸Python,看很多人玩爬蟲我也想玩,找來找去發(fā)現(xiàn)很多人用網絡爬蟲干的第一件事就是模擬登陸,增加點難度就是模擬登陸后在獲取數(shù)據(jù),但是網上好少有Python 3.x的模擬登陸Demo可以參考,加上自己也不怎么懂Html,所以這第一個Python爬蟲寫的異常艱難,不過最終結果還是盡如人意的,下面把這次學習的過程整理一下。

工具

  • 系統(tǒng):win7 64位系統(tǒng)
  • 瀏覽器:Chrome
  • Python版本:Python 3.5 64-bit
  • IDE:JetBrains PyCharm (貌似很多人都用這個)

我把目標瞄準了我們的教務處,這次爬蟲的目的是從教務處獲取成績并且把成績輸入Excel表格中保存起來,我們學校教務處的地址是:http://jwc.ecjtu.jx.cn/ ,往常每次我們獲取成績都需要先進入教務處,然后點擊成績查詢,輸入公共的賬號密碼進入,最后輸入相關信息獲取成績表格,這里登陸不需要驗證碼省了我一番功夫,這樣我們先進入成績查詢系統(tǒng)登陸界面,先看看怎么模擬登陸這個過程,在Chrome瀏覽器下按F12打開開發(fā)者面板:

開發(fā)者面板
這里我們學校的教務處查詢系統(tǒng)的密碼是公共的jwc也就是拼音縮寫,我們輸入用戶名和密碼點擊登陸,這時候注意POST請求:
注意post請求
發(fā)現(xiàn)了什么,好像Chrome并沒有把Post提交的表單信息保留下來直接跳轉到了另一個界面然后展示另一個界面的數(shù)據(jù),這里就需要我們自己動手操作一下,注意開發(fā)者面板左上角的小紅點表示這時候正在抓取數(shù)據(jù),如果點擊一下就會變成灰色,就可以變相地保存下當時抓取到的包,我在點擊登陸后新界面未刷新出來之前點擊了這個小紅點,如愿以償?shù)牡玫搅薖ost的表單數(shù)據(jù):
得到post表單數(shù)據(jù)
這樣就獲取了瀏覽器在登陸時候向服務器傳遞的表單數(shù)據(jù),看一下這個表單都有些什么:
查看表單數(shù)據(jù)
這里看到我們需要傳遞三個參數(shù),分別是: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方法需要的數(shù)據(jù),就可以模擬瀏覽器登陸了,這里因為有進一步獲取成績的操作所以使用了session來保持連接,這里單看最后的返回碼的話我們是成功了的,具體如何還要看下一步操作,接下來:

抓包

這里為了簡便代碼我們設定輸入學號查詢所有成績,減少其他判斷,同樣對Post數(shù)據(jù)進行抓包:

對post數(shù)據(jù)抓包

同樣查看Post的數(shù)據(jù):

查看post數(shù)據(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下右鍵--查看網頁源代碼,找到這么一行:

a href=query.php?start=1&job=see&=&Name=&Course=&ClassID=&Term=&StuID=xxxxxxx

這個才是真正的地址,點擊這個地址轉入的才是真正的界面,因為這里成績數(shù)據(jù)較多,所以這里采用了分頁顯示,這個start=1說明是第一頁,這個參數(shù)是可變的需要我們傳入,還有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方法傳遞數(shù)據(jù)并獲取響應的內容:

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條數(shù)據(jù),這里因為只是收集已經畢業(yè)的學生的成績數(shù)據(jù)所以不對其他數(shù)據(jù)量不足的學生成績的情況做統(tǒng)計,默認收集的都是大四畢業(yè)的學生成績數(shù)據(jù)。這里采用兩個變量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

這里查了很多別人的博客都是用正則表達式來分解數(shù)據(jù),表示自己的正則寫的并不好也嘗試了但是沒成功,所以無奈選擇這種方式,如果有人有測試成功的正則歡迎跟我說一聲,我也學習學習。

把數(shù)據(jù)保存到Excel

因為已經清楚了這個網頁保存成績的具體結構,所以順著每次循環(huán)解析將數(shù)據(jù)不斷加以保存就是了,這里使用xlwt寫入數(shù)據(jù)到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()  # 為樣式創(chuàng)建字體
    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代表頁數(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
            # 學期
            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的文件,我的程序就放在桌面,所以去桌面找:

桌面圖標

點開查看是否成功獲?。?/p>

最終獲取結果

至此,大功告成

小結
剛開始接觸Python一個星期的樣子,這次寫了這么一個簡單的網絡爬蟲檢驗一下學習成果,雖然程序還有些許Bug,不過總歸得到了一定收獲,當然也為下一步學習打下了基礎,嗯哼,為了接下來批量獲取網絡上美女圖片并分類保存我會繼續(xù)自學Python,荊軻刺秦王~

源碼

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容