Python模擬登陸正方教務系統並抓取成績單


學校的教務系統是正方的,在大學期間無論是選課、報名還是查成績,幾乎都要和它打交道,上學期在積累了一定的爬蟲和web知識后,我就想着用Python模擬登陸教務系統,實現在命令行里方便地進行成績或課表的查詢。

首先先來看看登陸的過程。

首先是向default.aspx POST過去了一些字段,其中txtUserName是我的學號,TextBox2是密碼,RadioButtonList1是學生選項,還有個txtSecretCode是驗證碼。

此外,還出現了__EVENTVALIDATION和__VIEWSTATE這兩個相對陌生的東西,百度了一下:

 

__EVENTVALIDATION:

__EVENTVALIDATION只是用來驗證事件是否從合法的頁面發送,只是一個數字簽名,所以一般很短。

“id”屬性為“__EVENTVALIDATION”的隱藏字段是ASP.NET 2.0的新增的安全措施。該功能可以阻止由潛在的惡意用戶從瀏覽器端發送的未經授權的請求.

為了確保每個回發和回調事件來自於所期望的用戶界面元素,ASP.NET運行庫將在事件中添加額外的驗證層。服務器端通過檢驗表單提交請求的內容,將其與“id”屬性為“__EVENTVALIDATION”隱藏字段中的信息進行匹配。根據匹配結果來驗證未在瀏覽器端添加額外的輸入字段(有可能為用戶在瀏覽器端惡意添加的字段),並且該值是在服務器已知的列表中選擇的。ASP.NET運行庫將在生成期間創建事件驗證字段,而這是最不可能獲取該信息的時刻。像視圖狀態一樣,事件驗證字段包含散列值以防止發生瀏覽器端篡改。

說明:“id”屬性為“__EVENTVALIDATION”隱藏字段一般在表單的最下方,如果表單在瀏覽器端尚未解析完畢時,用戶提交數據有可能導致驗證失敗。

 

__VIEWSTATE
ViewState是ASP.NET中用來保存WEB控件回傳時狀態值一種機制。在WEB窗體(FORM)的設置為runat="server",這個窗體(FORM)會被附加一個隱藏的屬性_VIEWSTATE。_VIEWSTATE中存放了所有控件在ViewState中的狀態值。 
ViewState是類Control中的一個域,其他所有控件通過繼承Control來獲得了ViewState功能。它的類型是system.Web.UI.StateBag,一個名稱/值的對象集合。 
當請求某個頁面時,ASP.NET把所有控件的狀態序列化成一個字符串,然后做為窗體的隱藏屬性送到客戶端。當客戶端把頁面回傳時,ASP.NET分析回傳的窗體屬性,並賦給控件對應的值

 

了解了這么多后,我們知道這兩個字段肯定是不可缺少的,那么它們可以從哪獲取到呢?

我們右鍵查看網頁的源代碼,在源代碼中發現了兩個type為hidden的輸入框,它們的值正是我們所需要的。

 

在此,我們可以先構造一個payload字段:

payload={'__VIEWSTATE':'', 'txtUserName':'', 'TextBox2':'', 'txtSecretCode':'', 'RadioButtonList1':'', 'Button1':'', 'lbLanguage':'', 'hidPdrs':'', 'hidsc':'', '__EVENTVALIDATION':'', }

對於__EVENTVALIDATION和__VIEWSTATE值的獲取我用了BeautifulSoup這個庫。

soup=BeautifulSoup(index.content,'lxml') value1=soup.find('input',id='__VIEWSTATE')['value'] value2=soup.find('input',id='__EVENTVALIDATION')['value']

登錄時還要輸入圖形驗證碼,這種驗證碼大部分都傾斜和粘連,用簡單的OCR估計處理不了,所以我就把驗證碼下載到了本地然后顯示出來手動輸入,此處用到了PIL庫。

我使用requests庫處理get和post請求,第一行使用requests.Session()會話對象讓你能夠跨請求保持某些參數。它也會在同一個 Session 實例發出的所有請求之間保持 cookie。這樣我們就不用額外操心cookie的變更與存儲了。

s=requests.Session() index=s.get(url,headers=headers) img=s.get(checkcode,stream=True,headers=headers) with open('checkcode.gif','wb') as f: f.write(img.content) image=Image.open('checkcode.gif') image.show()

 最后把各字段該輸入的輸入,該賦值的賦值,整合到payload字典里就行啦

payload['txtUserName']=raw_input("UserName:") payload['TextBox2']=raw_input("Password:") payload['txtSecretCode']=raw_input("checkcode:") #下面的value1和value2都不需要轉碼,直接post過去即可。在此浪費了好長時間
payload['__VIEWSTATE']=value1 payload['__EVENTVALIDATION']=value2 payload['RadioButtonList1']= '%D1%A7%C9%FA' post1=s.post(url,data=payload,headers=headers)

POST過去后我們發現http狀態碼返回的是302,302代表的意思是 Move temporarily

這樣的重定向是臨時的,我們還要繼續向原有地址發送以后的請求。

 

我們直接找到成績查詢的url:http://xk2.edu.cn/xscjcx.aspx?xh=xxxxxx&xm=xxx&gnmkdm=N121605

此URL中xh是學號,xm是經過URL編碼的學生姓名。

后面我們要做的和前面所講的差不多,首先要通過GET方法獲取頁面源代碼,從中取得__EVENTVALIDATION和__VIEWSTATE的值,然后再次POST過去。

data={ 'btn_zcj':'%C0%FA%C4%EA%B3%C9%BC%A8',#學年成績:btn_xn 歷年成績:btn_zcj
    'ddlXN':'', 'ddlXQ':'', '__EVENTVALIDATION': '', '__EVENTTARGET':'', '__EVENTARGUMENT' :'', '__VIEWSTATE':'', 'hidLanguage':'', 'ddl_kcxz':'', } #注意!先獲取框架源代碼,提取__EVENTARGUMENT和__VIEWSTATE值后作為post內容進行下一步
get_source=s.get('http://xk2.edu.cn/xscjcx.aspx?xh=xxxxxx&xm=xxx&gnmkdm=N121605',headers=headers).content soup=BeautifulSoup(get_source,'lxml') value3=soup.find('input',id='__VIEWSTATE')['value'] value4=soup.find('input',id='__EVENTVALIDATION')['value'] data['__VIEWSTATE']=value3 data['__EVENTVALIDATION']=value4 get_score=s.post('http://xk2.edu.cn/xscjcx.aspx?xh=xxxxxx&xm=xxx&gnmkdm=N121605',data=data,headers=headers) print (get_score.content)

 

最后通過解析get_score.content中的源代碼就可以獲得歷年成績數據了。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM