1. 程式人生 > >python爬蟲之 正方教務管理系統查詢成績

python爬蟲之 正方教務管理系統查詢成績

目錄

         前言

0. 依賴及程式碼頭:

1. 登入

1) 驗證碼

2)登入請求構造

2. 跳轉到成績介面獲取成績

3. 輸出成績

4.整體程式碼


前言

以下的所有程式碼都基於python3

最近學習網路爬蟲...加上我們學校的教務系統經常因為各種奇怪的原因沒有辦法讀取介面啥啥啥的....所以這裡準備寫一個指令碼掛到伺服器上,之後增加到我的部落格介面....方便所有人查詢自己的成績以及課表,課表的話爬取比較簡單,因為跳轉次數較少,方式也大同小異,所以這裡只給出爬取成績的具體思路

0. 依賴及程式碼頭:

  1. Beautiful庫,解析網頁
  2. PrettyTable庫,格式化輸出
  3. PIL,抓取驗證碼並顯示
  4. urllib庫,http請求庫

以下是程式碼頭,這裡我們需要構造一個opener來模擬線上連續訪問

import re
import urllib.request
import urllib.parse
import http.cookiejar
import bs4
import getpass
import pickle
import os
import platform
import subprocess
from bs4 import BeautifulSoup
from prettytable import PrettyTable
from PIL import Image
from PIL import ImageEnhance
import pytesseract

#準備Cookie和opener,因為cookie存於opener中,所以以下所有網頁操作全部要基於同一個opener
cookie = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cookie))

1. 登入

首先我們看到登入介面,需要提供三個內容{使用者名稱密碼驗證碼},使用者名稱密碼都是我們已知的,那麼我們需要先解決第一個問題,就是這個驗證碼

1) 驗證碼

我們開啟F12開發者工具,開啟network,然後點選驗證碼刷新出一張新的驗證碼,然後就可以看到驗證碼的來源地址是http://zfxk.zjtcm.net/CheckCode.aspx

2)登入請求構造

我們先登入一次,然後同樣的在network中找到我們的請求介面中找到我們登入時提交的表單

這裡需要四個內容,{__VIEWSTATE,txtUserName,TextBox2,txtSecretCode},當然這裡的RadioButtonList1也是需要填寫的,這裡顯示不出來是因為這是中文表單,內容是“學生”,也就是我們登入時需要勾選的登入方式

__VIEWSTATE是為了防止攻擊增加的內容,這個東西我們可以在登入前的介面中找到

txtUserName就是使用者名稱,TextBox2中的就是密碼,txtSecretCode中就是驗證碼

那麼現在我們已經找齊了登入所需要的所有內容,現在只需要一個個組合成請求即可

使用者名稱密碼我們當然是需要輸入的

username=input("輸入使用者名稱: ")
password=input("輸入密碼 ")

驗證碼直接去驗證碼連結中儲存下圖片,然後手動輸入,當然也可以增加驗證碼識別功能....正方的驗證碼都是比較簡單的基礎驗證碼...稍微處理一下就可以達到較高的識別率,當然這裡就不作贅述

res = opener.open('http://zfxk.zjtcm.net/checkcode.aspx').read()
with open(r'D:\code.jpg','wb') as file:
file.write(res)
im = Image.open(r'D:\code.jpg')
im.show()
vcode = input('請輸入驗證碼:')
im.close()

以及獲取__VIEWSTATE

response = urllib.request.urlopen('http://zfxk.zjtcm.net/')
html = response.read().decode('gb2312')
viewstate = re.search('<input type="hidden" name="__VIEWSTATE" value="(.+?)"',html)

在獲取所有內容後,我們組成登入所需要的請求頭

params = {
            '__VIEWSTATE':viewstate.group(1)
            'txtUserName' : username,
            'Textbox1' : '',
            'Textbox2': password,
            'txtSecretCode':vcode
            'RadioButtonList1':'學生',
            'Button1' : '',
            'lbLanguage':'',
            'hidPdrs':'',
            'hidsc':'',
        }

接下來我們使用這個表單請求登入

data = urllib.parse.urlencode(params).encode('gb2312')
response = opener.open(loginurl,data)

就可以完成登入了

以下是完整的登入程式碼

username=input("輸入使用者名稱: ")
password=input("輸入密碼 ")

while True:
    params = {
            'txtUserName' : username,
            'Textbox1' : '',
            'Textbox2': password,
            'RadioButtonList1':'學生',
            'Button1' : '',
            'lbLanguage':'',
            'hidPdrs':'',
            'hidsc':'',
        }
    #獲取驗證碼
    res = opener.open('http://zfxk.zjtcm.net/checkcode.aspx').read()
    with open(r'D:\code.jpg','wb') as file:
        file.write(res)
    im = Image.open(r'D:\code.jpg')
    im.show()
    vcode = input('請輸入驗證碼:')
    im.close()
    params['txtSecretCode'] = vcode
    #獲取ViewState
    response = urllib.request.urlopen('http://zfxk.zjtcm.net/')
    html = response.read().decode('gb2312')
    viewstate = re.search('<input type="hidden" name="__VIEWSTATE" value="(.+?)"',html)
    params['__VIEWSTATE'] = viewstate.group(1)

    #嘗試登陸
    loginurl = 'http://zfxk.zjtcm.net/default2.aspx'
    data = urllib.parse.urlencode(params).encode('gb2312')
    response = opener.open(loginurl,data)
    if response.geturl() != 'http://zfxk.zjtcm.net/default2.aspx':
        #獲取學生姓名,之後需要使用
        catch='<span id="xhxm">(.*?)</span>'
        tmpname=re.search(catch,response.read().decode('gb2312'))
        name=tmpname.group(1)
        name=name[:-2]
        break

print(name)

2. 跳轉到成績介面獲取成績

同樣的在network上找到查詢成績的直接地址,可以看到是

http://zfxk.zjtcm.net/xscj_gc.aspx?xh=(.?)&xm=(.?)&gnmkdm=N121605

xh後面是自己的學號,即登陸的usernamexm則是登入後抓取的姓名在網頁編碼gb2312下的轉義

當然這裡有一點特殊的地方,如果我們直接在地址中輸入我們構造出的查詢介面的連結,會出現302跳轉,在python下可能會報錯

當然這也是一定程度上的網站防護措施

那麼解決這一問題也很簡單,我們可以猜想為什麼我們正常點選可以跳轉到頁面,而直接輸入連結卻不行,最簡單的猜想就是,在傳遞的表單中我們需要給出我們是從哪個介面跳轉過來的,只有某些特定介面的跳轉才被允許,並且檢視一下network中的表頭我們可以發現一項名為Referer

所以這一項也是非常重要的...需要同時構造在表頭中,所以在查詢成績的這一步中,我們不止需要構造請求表單data,還需要構造表頭headers

構造表單時,當然我們可以發現還是需要__VIEWSTATE這一項的,這一項當然可能會發生改變,所以我們去登入成功後的介面獲取這一項的內容

req = urllib.request.Request(url)
print(url)
req.add_header('Referer','http://zfxk.zjtcm.net/xs_main.aspx?xh='+username )
req.add_header('User-Agent','Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36')
response = opener.open(req)
html = response.read().decode('gb2312')
# print(html)
viewstate = re.search('<input type="hidden" name="__VIEWSTATE" value="(.+?)"',html)

然後按上面的方式構造表頭

params = {
    'ddlXN':'',
    'ddlXQ':'',
    'Button2':'在校學習成績',
}
params['__VIEWSTATE'] = viewstate.group(1)

構造完dataheaders

我們就可以訪問查詢成績的介面了

req = urllib.request.Request(url,urllib.parse.urlencode(params).encode('gb2312'))
req.add_header('Referer','http://zfxk.zjtcm.net/default2.aspx')
req.add_header('Origin','http://zfxk.zjtcm.net/')
response = opener.open(req)
soup = BeautifulSoup(response.read().decode('gb2312'),'html.parser')
html = soup.find('table',class_='datelist')

這裡的response就是我們需要的成績頁面,然後就是怎麼從成績頁面獲取我們需要的內容了,這裡我們使用BeautifulSoup這個庫,直接find所有成績標籤

3. 輸出成績

這裡使用prettytable庫來格式化輸出表單,當然這裡也可以儲存到excel中或者各種各樣個人喜歡的方式.....


print('你的所有成績如下:')
#指定要輸出的列,原網頁的表格列下標從0開始
outColumn = [1,2,3,4,6,7,8]
#用於標記是否是遍歷第一行
flag = True

for each in html:
    columnCounter = 0
    column = []
    if(type(each) == bs4.element.NavigableString):
        pass
    else:
        #遍歷列
        for item in each.contents:
            if(item != '\n'):
                if columnCounter in outColumn:
                    #要使用str轉換,不然陷入copy與deepcopy的無限遞迴
                    column.append(str(item.contents[0]).strip())
                columnCounter += 1
        if flag:
            table = PrettyTable(column)
            flag = False
        else:
            table.add_row(column)
print(table)

 

以上就是簡單的爬取正方教務系統在校成績的程式碼

4.整體程式碼

import re
import urllib.request
import urllib.parse
import http.cookiejar
import bs4
import getpass
import pickle
import os
import platform
import subprocess
from bs4 import BeautifulSoup
from prettytable import PrettyTable
from PIL import Image
from PIL import ImageEnhance
import pytesseract

#準備Cookie和opener,因為cookie存於opener中,所以以下所有網頁操作全部要基於同一個opener
cookie = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cookie))

username=input("輸入使用者名稱: ")
password=input("輸入密碼 ")

while True:
    params = {
            'txtUserName' : username,
            'Textbox1' : '',
            'Textbox2': password,
            'RadioButtonList1':'學生',
            'Button1' : '',
            'lbLanguage':'',
            'hidPdrs':'',
            'hidsc':'',
        }
    #獲取驗證碼
    res = opener.open('http://zfxk.zjtcm.net/checkcode.aspx').read()
    with open(r'D:\code.jpg','wb') as file:
        file.write(res)
    im = Image.open(r'D:\code.jpg')
    im.show()
    vcode = input('請輸入驗證碼:')
    im.close()
    params['txtSecretCode'] = vcode
    #獲取ViewState
    response = urllib.request.urlopen('http://zfxk.zjtcm.net/')
    html = response.read().decode('gb2312')
    viewstate = re.search('<input type="hidden" name="__VIEWSTATE" value="(.+?)"',html)
    params['__VIEWSTATE'] = viewstate.group(1)

    #嘗試登陸
    loginurl = 'http://zfxk.zjtcm.net/default2.aspx'
    data = urllib.parse.urlencode(params).encode('gb2312')
    response = opener.open(loginurl,data)
    if response.geturl() != 'http://zfxk.zjtcm.net/default2.aspx':
        #獲取學生姓名,之後需要使用
        catch='<span id="xhxm">(.*?)</span>'
        tmpname=re.search(catch,response.read().decode('gb2312'))
        name=tmpname.group(1)
        name=name[:-2]
        break

print(name)

#構造url
url = ''.join([
        'http://zfxk.zjtcm.net/xscj_gc.aspx',
        '?xh=',
         username,
        '&xm=',
        urllib.parse.quote(name),
        '&gnmkdm=N121605',
    ])
#構建查詢全部成績表單
params = {
    'ddlXN':'',
    'ddlXQ':'',
    'Button2':'在校學習成績',
}
#構造Request物件,填入Header,防止302跳轉,獲取新的View_State
req = urllib.request.Request(url)
print(url)
req.add_header('Referer','http://zfxk.zjtcm.net/xs_main.aspx?xh='+username )
req.add_header('User-Agent','Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36')

response = opener.open(req)
html = response.read().decode('gb2312')
# print(html)
viewstate = re.search('<input type="hidden" name="__VIEWSTATE" value="(.+?)"',html)
params['__VIEWSTATE'] = viewstate.group(1)
#查詢所有成績
req = urllib.request.Request(url,urllib.parse.urlencode(params).encode('gb2312'))
req.add_header('Referer','http://zfxk.zjtcm.net/default2.aspx')
req.add_header('Origin','http://zfxk.zjtcm.net/')
response = opener.open(req)
soup = BeautifulSoup(response.read().decode('gb2312'),'html.parser')
html = soup.find('table',class_='datelist')

print('你的所有成績如下:')
#指定要輸出的列,原網頁的表格列下標從0開始
outColumn = [1,2,3,4,6,7,8]
#用於標記是否是遍歷第一行
flag = True

for each in html:
    columnCounter = 0
    column = []
    if(type(each) == bs4.element.NavigableString):
        pass
    else:
        #遍歷列
        for item in each.contents:
            if(item != '\n'):
                if columnCounter in outColumn:
                    #要使用str轉換,不然陷入copy與deepcopy的無限遞迴
                    column.append(str(item.contents[0]).strip())
                columnCounter += 1
        if flag:
            table = PrettyTable(column)
            flag = False
        else:
            table.add_row(column)
print(table)

 

當然這份程式碼沒有增加驗證碼識別以及美化成績介面,之後可能會為了方便考慮將這份程式碼連線html後寫入我的部落格中...這樣可以方便所有本校同學在各個地方查詢成績以及課表....