首頁歷史 > 正文

某校教務管理系統post分析,Python實現自動查詢成績併發送簡訊

2021-09-11由 Python可樂 發表于 歷史

前言

本人是一名大三大學生,考完試不久,由於自己不知道期末考試什麼時候出考試成績,並且每次查詢成績特別麻煩(首先得登入VPN連線學校內網,然後再登入教務管理系統,再進入查詢介面,點選查詢成績等,相信各位大學生在家查詢成績也有同樣的麻煩),於是自己突發奇想,想做一個免VPN成績查詢的WEB介面(只需要輸入賬號,密碼就可以直接顯示出考試成績,下面會解釋這個專案是怎麼做的),另外還想做一個簡訊通知服務,就是隻要這門成績一出,就會自動給你發簡訊通知 什麼科目考了什麼成績等資訊。下面就詳細地介紹這個專案是怎麼做的。

步驟

一、整體架構

1、首先要完成這個小專案,第一步就是要認真分析需求

私信小編01即可獲取大量python學習資源

1.1、免VPN

1.2、WEB視覺化介面

1.3、簡訊通知功能

2、下面是根據這些需求具體的架構

首先免VPN,是讓使用者感覺到沒有用VPN,但是不用VPN是無法連線學校伺服器進入的,所以這裡我的想法是在伺服器端架設VPN,在前臺,使用者只需要把教務管理系統的賬號,密碼輸入,然後將資訊傳到伺服器,伺服器端再連線學校伺服器,將資訊傳入伺服器,分析get,post請求將查詢到的成績返回,WEB視覺化使用的python的flask, 簡訊通知就是寫一個python程式,讓它死迴圈地向學校伺服器傳送post請求,只要一檢測到成績就查詢每個使用者的程式並向用戶傳送簡訊

3、資料庫

這裡我使用的是mysql資料庫,我建了 3個表

表1、

com_user

,儲存的是有查詢許可權的使用者

(學號,姓名)

表2、

note_user

,儲存的是開通簡訊服務的使用者

(學號,姓名,密碼(加密的,這裡我採用了最簡單的Base64加密), 手機號)

表3、

kcs

,儲存的是已出的考試成績,因為簡訊服務需要每次查詢成績與資料庫的做對比,多的就是新出的,表的欄位是

(課程號(唯一確定一門課程), 課程名稱)

二、逆向分析教務管理系統的WEB介面實現自動登入

1、首先需要登入在家查詢成績所需的VPN,因為這樣才會登入學校的教務管理系統

某校教務管理系統post分析,Python實現自動查詢成績併發送簡訊

2、 我們從登入入口開始分析

某校教務管理系統post分析,Python實現自動查詢成績併發送簡訊

點選登入後發現密碼框中的資料變長了

某校教務管理系統post分析,Python實現自動查詢成績併發送簡訊

說明密碼是在前臺加密過的

再次回退再次登入,這時我們開啟F12,觀察傳送的資料,,點選登入後

某校教務管理系統post分析,Python實現自動查詢成績併發送簡訊

觀察第一個http請求,發現傳送的Form表單中yhm是學號,下面的mm是加密的密碼,上面的csrftoken現在也不知道是什麼東西,百度搜索後,發現好像是為使用者實現防止跨站請求偽造的功能

這裡先不管它

再次倒回,審查按鈕元素,觀察click事件

某校教務管理系統post分析,Python實現自動查詢成績併發送簡訊

進入,驚訝地發現密碼是RSA加密的,進一步除錯並百度後發現這個是RSA的PKCS的一種標準

某校教務管理系統post分析,Python實現自動查詢成績併發送簡訊

那麼公鑰是從哪裡來的呢?

回到最初的登入介面,開啟F12,在谷歌瀏覽器搜尋欄中輸入教務管理系統的URL,回車,捕捉HTTP資料包

某校教務管理系統post分析,Python實現自動查詢成績併發送簡訊

然後 再往上找,發現

某校教務管理系統post分析,Python實現自動查詢成績併發送簡訊

在這個資料包中,伺服器返回了csrftoken,

好了,到了這裡我們先停一下,先用python模擬一下自動登入,看看是否能登入成功

# _*_ coding : utf-8 _*_# User: 19164# Date: 2020/1/19 13:43# Name: main。py# Tool: PyCharmimport hashlibimport timefrom Crypto。Util。number import *import requestsfrom bs4 import BeautifulSoupimport base64import rsa get_grade_url = “http://********。edu。cn/jwglxt/xtgl/login_slogin。html”headers = { “User-Agent” : ‘Mozilla/5。0 (Windows NT 10。0; Win64; x64) AppleWebKit/537。36 (KHTML, like Gecko) ’ ‘Chrome/79。0。3945。88 Safari/537。36 ’}get_rsa_pubkey_url = “http://********。edu。cn/jwglxt/xtgl/login_getPublicKey。html” def get_rsa_encry_pwd(session, password) : # 將密碼進行RSA加密 time_ = round(time。time() * 1000) get_key_url = get_rsa_pubkey_url + “?time=” + str(time_) + “&_=” + str(time_ + 1753) pubkey_json = session。get(get_key_url, headers=headers)。json() int_exponent = bytes_to_long(base64。b64decode(pubkey_json[‘exponent’])) int_modulus = bytes_to_long(base64。b64decode(pubkey_json[‘modulus’])) rsa_pubkey = rsa。PublicKey(int_modulus, int_exponent) crypto = rsa。encrypt(password。encode(), rsa_pubkey) password = base64。b64encode(crypto)。decode() return password def login(username, pwd) : session = requests。session() res = session。get(get_grade_url, headers=headers) bs = BeautifulSoup(res。text, “html。parser”) token = bs。find(‘input’, id=‘csrftoken’)[‘value’] password = get_rsa_encry_pwd(session, pwd) form_data = { “csrftoken” : token, “yhm” : username, “mm” : [password, password] } url = get_grade_url + “?time=” + str(round(time。time() * 1000)) res = session。post(url, data=form_data, headers=headers) with open(“login。html”, “wb”) as f : f。write(res。content) if __name__ == “__main__” : xh = “*************” pwd = “*************” login(xh, pwd)

我們將寫入的login。html用瀏覽器開啟,

某校教務管理系統post分析,Python實現自動查詢成績併發送簡訊

發現已經成功登入了(其實這裡不知道自己改程式碼改了多少遍,上面是最終的完整的原始碼),好了,到目前為止,其實已經成功了一大半,然後我們開始分析查詢成績傳送的資料及URL

我們進入查詢介面

某校教務管理系統post分析,Python實現自動查詢成績併發送簡訊

點開F12,點選查詢

某校教務管理系統post分析,Python實現自動查詢成績併發送簡訊

某校教務管理系統post分析,Python實現自動查詢成績併發送簡訊

發現只發送一個post請求

分析form表單資料,經過多次試驗發現

xnm是查詢的學期,xqm 3是上學期,12是下學期

,其他的是不變的

而返回的成績

某校教務管理系統post分析,Python實現自動查詢成績併發送簡訊

經過分析,Items中 kch是課程號,kcmc是課程名稱,cj是成績,,jd是績點,,,,都是拼音首字母,,也是醉了,下面就完善指令碼實現自動查詢併發送簡訊

3、簡訊通知實現

關於簡訊的實現,我是上某寶買的簡訊介面,20元200條簡訊,內容報備後可以24小時傳送

這裡貼出簡訊伺服器的程式碼

# 資料庫部分是自己封裝了一個類# _*_ coding : utf-8 _*_# User: 19164# Date: 2020/1/19 14:35# Name: consql。py# Tool: PyCharmimport pymysql class ConMySql: def __init__(self): self。db = pymysql。connect(“localhost”, “root”, “password”, “jwglxt”) self。cursor = self。db。cursor() def __del__(self): self。db。close() # 增加,刪除,修改資料 def execute_sql(self, sql): try: self。cursor。execute(sql) self。db。commit() return True except: self。db。rollback() return False # 查詢已開通簡訊服務的使用者 def query_note_user(self): try: userList = [] sql = “SELECT * FROM note_user;” self。cursor。execute(sql) results = self。cursor。fetchall() for row in results: tmp = [] tmp。append(row[0]) tmp。append(row[1]) tmp。append(row[2]) tmp。append(row[3]) userList。append(tmp) return userList except: return False # 傳入課程號,判斷這門課程是否是新出的 def is_new_class(self, kch): kcList = [] sql = “SELECT * FROM kcs where kch = ” + “‘” + kch + “’;” self。cursor。execute(sql) results = self。cursor。fetchall() # 如果不是新課程,會從資料庫中查詢到,返回False,是新課程的話就返回True if len(results) == 1: return False else: return True # 往表 kcs 中新增新出的課程 def add_new_class(self, kch, kcmc): sql = “INSERT INTO kcs VALUES (” + “‘” + kch + “’,‘” + kcmc + “’);” return self。execute_sql(sql)

# _*_ coding : utf-8 _*_# User: 19164# Date: 2020/1/19 13:43# Name: main。py# Tool: PyCharmimport hashlibimport timefrom Crypto。Util。number import *import requestsfrom bs4 import BeautifulSoupimport base64import rsaimport threadingfrom consql import ConMySql # 登入的URLget_grade_url = “http://***********。edu。cn/jwglxt/xtgl/login_slogin。html”headers = { “User-Agent” : ‘Mozilla/5。0 (Windows NT 10。0; Win64; x64) AppleWebKit/537。36 (KHTML, like Gecko) ’ ‘Chrome/79。0。3945。88 Safari/537。36 ’}# 得到登入公鑰的URLget_rsa_pubkey_url = “http://**********。edu。cn/jwglxt/xtgl/login_getPublicKey。html”# 查詢成績的URLquery_url = ‘http://**********。edu。cn/jwglxt/cjcx/cjcx_cxDgXscj。html?doType=query&gnmkdm=N305005’ # 記錄查詢日誌def WriteFile(content) : with open(“grade。log”, “a+”) as f : f。write(content + “\n”) # 簡訊介面 1def send_one_note(str_send_note, phonenum) : “”“ :param str_send_note: 簡訊內容 :param phonenum: 手機號碼 :return: ”“” try : url_sendNote = “http://**********:9000/sms。aspx” data_sendNote = { “userid” : 4972, “account” : “zsky”, “password” : “**********”, “mobile” : phonenum, “content” : str_send_note, “sendTime” : “”, “action” : “send”, “extno” : “” } rs = requests。post(url_sendNote, data=data_sendNote) # print(rs。text) if “Success” in rs。text : WriteFile(str_send_note + “傳送成功”) return True else : return False except : return False # 向所有註冊簡訊服務的使用者傳送簡訊def send_all_note(my_con, kch, kcmc) : “”“ :param my_con: 資料庫物件 :param kch: 課程號 :param kcmc: 課程名稱 :return: ”“” # 【成績通知】尊敬的***你好,***成績出了,您的成績為***,績點為*** userlist = my_con。query_note_user() if userlist : # 開始遍歷發簡訊 for i in range(len(userlist)) : try : # 獲取相關的資訊 xh = userlist[i][0] name = userlist[i][1] pwd = base64。b64decode(userlist[i][2])。decode() cj, jd = get_grade(xh, pwd, kch) phone_num = userlist[i][3] content = “【成績通知】尊敬的%s使用者,您好,%s成績出了,您的成績為%s,績點為%s” % (name, kcmc, cj, jd) th1 = threading。Thread(target=send_one_note, args=(content, phone_num,)) th1。run() time。sleep(2) # 等2秒之後再發下一個 except : WriteFile(“在查詢” + name + “成績時出現異常”) else : WriteFile(“沒有查詢到userlist”) return False # 根據公鑰獲得加密後的密碼def get_rsa_encry_pwd(session, password) : “”“ :param session: 建立連線的session物件 :param password: 密碼 :return: ”“” # 將密碼進行RSA加密 time_ = round(time。time() * 1000) get_key_url = get_rsa_pubkey_url + “?time=” + str(time_) + “&_=” + str(time_ + 1753) pubkey_json = session。get(get_key_url, headers=headers)。json() int_exponent = bytes_to_long(base64。b64decode(pubkey_json[‘exponent’])) int_modulus = bytes_to_long(base64。b64decode(pubkey_json[‘modulus’])) rsa_pubkey = rsa。PublicKey(int_modulus, int_exponent) crypto = rsa。encrypt(password。encode(), rsa_pubkey) password = base64。b64encode(crypto)。decode() return password # 登入def login(session, username, pwd) : “”“ :param session: 建立連線的session物件 :param username: 使用者名稱 :param pwd: 密碼 :return: ”“” res = session。get(get_grade_url, headers=headers) bs = BeautifulSoup(res。text, “html。parser”) token = bs。find(‘input’, id=‘csrftoken’)[‘value’] password = get_rsa_encry_pwd(session, pwd) form_data = { “csrftoken” : token, “yhm” : username, “mm” : [password, password] } url = get_grade_url + “?time=” + str(round(time。time() * 1000)) res = session。post(url, data=form_data, headers=headers) # 遍歷成績,返回成績json資料def query(session) : “”“ :param session: 建立連線的session物件 :return: ”“” form_data = { ‘xnm’ : 2019, ‘xqm’ : ‘3’, ‘_search’ : ‘false’, ‘nd’ : round(time。time() * 1000), ‘queryModel。showCount’ : 15, ‘queryModel。currentPage’ : 1, ‘queryModel。sortName’ : ‘’, ‘queryModel。sortOrder’ : ‘asc’, ‘time’ : 0 } query_res = session。post(query_url, data=form_data, headers=headers)。json() items = query_res[‘items’] return items # <——- 個人查詢成績 ——># return item[‘cj’], item[‘jd’]def get_grade(_xh, _pwd, _kch) : “”“ :param _xh:學號 :param _pwd: 密碼 :param _kch: 課程號 :return: 返回這門課程的成績和績點 ”“” try : # 登入教務管理系統 username = _xh pwd = _pwd session = requests。session() login(session, username, pwd) return query_grade(session, _kch) except : return False def query_grade(session, _kch) : items = query(session) for item in items : if item[‘kch’] == _kch : return item[‘cj’], item[‘jd’] return False # <——- 總查詢 ——># 如果出現新的成績,就將其寫入資料庫,並且send_all_notedef begin_query__(my_con) : “”“ :param my_con: 資料庫連線物件 :return: ”“” # 登入教務管理系統 username = “**********” pwd = “**********” session = requests。session() login(session, username, pwd) # 查詢是否出新成績 query_class__(session, my_con) def query_class__(session, my_con) : items = query(session) for item in items : if my_con。is_new_class(item[‘kch’]) : my_con。add_new_class(item[‘kch’], item[‘kcmc’]) WriteFile(“出了新成績——->%s” % item[‘kcmc’]) send_all_note(my_con, item[‘kch’], item[‘kcmc’]) if __name__ == “__main__” : my_con = ConMySql() count = 0 while True : count = count + 1 begin_query__(my_con) WriteFile(str(count) + “: ” + time。strftime(‘%Y-%m-%d %H:%M:%S’, time。localtime(time。time()))) time。sleep(60 * 10) # 每十分鐘查詢一次成績

4、WEB介面

最後需要做一個WEB介面,裡面提供是否開通簡訊服務,如果開通,就把資訊寫入

note_user

中,這裡就不貼出程式碼了,,

5、成果展示

某校教務管理系統post分析,Python實現自動查詢成績併發送簡訊

現在,我已經將這個服務服務於我們班的同學,同學們還是挺喜歡這個東西的,畢竟方便了不少

6、擴充套件

這種直接分析get post資料的手段其實不僅用來查詢成績,還可以做一些擴充套件,比如在選網課的時候,可以根據這種方法寫指令碼自動篩選網課,並選課等(因為在選課期間,由於訪問人數太多,學校伺服器挺不住的),用指令碼的方法可以幫助選網課。

頂部