用 Python 實現(xiàn)模擬登錄正方教務(wù)系統(tǒng)搶課

最近學校開始選課檩坚,但是如果選課時間與自己的事情沖突疙赠,這時候就可以使用Python腳本自助搶課交掏,搶課的第一步即是模擬登錄,需要模擬登錄后保存登錄信息然后再進行操作刨肃。

而且整個流程是比較簡單古拴,這是因為正方教務(wù)系統(tǒng)是比較舊的,全文的IP地址部分遮擋真友,請換成你們學校的IP地址黄痪。

嘗試登錄

首先我們打開學校的教務(wù)系統(tǒng),隨便輸入锻狗,然后提交表單满力,打開Chrome的開發(fā)者工具中的Network準備抓包

?

學習過程中遇到什么問題或者想獲取學習資源的話,歡迎加入學習交流群:【923414804】轻纪,我們一起學Python。

把css 圖片之類的過濾掉叠纷,發(fā)現(xiàn)了default.aspx這個東西

如果你們學校教務(wù)系統(tǒng)不使用Cookie則會是這樣

?

我們可以發(fā)現(xiàn)刻帚,真實的請求地址為http://110.65.10.xxx/(bdq1aj45lpd42o55vqpfgpie)/default2.aspx

隨后我們發(fā)現(xiàn)這個網(wǎng)址括號圍起來的一串信息有點詭異,而且每次進入的時候信息都不一樣涩嚣,經(jīng)過資料查詢崇众,這是一種ASP.NET不使用Cookie會話管理的技術(shù)掂僵。

不使用 Cookie 的 ASP.NET 會話管理

那這樣就很好辦了,我們只需要登錄時記錄下這個數(shù)據(jù)即可保持登錄狀態(tài)顷歌。

經(jīng)過測試發(fā)現(xiàn)锰蓬,我們可以隨便偽造一個會話信息即可一直保持登錄狀態(tài),但是為了體現(xiàn)模擬登錄的科學性眯漩,我們需要先獲取該會話信息芹扭。

如果你們學校教務(wù)系統(tǒng)使用Cookie則會是這樣

?

服務(wù)器會返回一個Cookie值,然后在本地保存赦抖,這與下面的會不相同舱卡。

獲取會話信息(不使用Cookie)

這里我們要使用requests庫,并且要偽造header的UA信息

經(jīng)過測試發(fā)現(xiàn)队萤,我們只訪問學校的IP地址轮锥,會自動重定向至有會話信息的網(wǎng)址,所以我們先訪問一下IP地址要尔。

class Spider:

????def __init__(self, url):

????????self.__uid = ''

????????self.__real_base_url = ''

????????self.__base_url = url

????????self.__headers = {

????????????'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36',

????????}

????def __set_real_url(self):

????????request = requests.get(self.__base_url, headers=self.__headers)

????????real_url = request.url

????????self.__real_base_url = real_url[:len(real_url) - len('default2.aspx')]

????????return request

上面獲取的url即為帶有會話信息的網(wǎng)址舍杜,保存的url格式為http://110.65.10.xxx/(bdq1aj45lpd42o55vqpfgpie)/

保存為這樣的格式是因為我們要訪問其他地址

獲取會話信息(使用Cookie)

有些學校的教務(wù)系統(tǒng)是使用Cookie的,我們只需要首次get請求時保存Cookie即可赵辕,然后此后一直使用該cookie


def get_cookie():

????request = requests.get('http://xxx.xxx.xxx.xxx') #以某教務(wù)系統(tǒng)為例子

????cookie = requets.cookie

????return cookie

而requests中使用Cookie很簡單

只需要這樣

Python

1

2

def use_cookie(cookie):

????request = requests.get('http://xxx.xxx.xxx.xxx',cookie=cookie)

由于我們學校采用的是無Cookie方案蝴簇,所以下面的代碼均沒有發(fā)送Cookie,如果你的學校采用了Cookie匆帚,只需要像我上面這樣發(fā)送Cookie就行了熬词。

而如果你們學校使用Cookie,就不必獲取帶有會話信息的地址了吸重,直接存儲Cookie即可互拾。

或者也可以使用requests的Session自動管理會話信息,這樣文章下面的代碼的請求全部改成Session的請求即可嚎幸,但是首先需要在類的初始化方法中初始化颜矿。


def __init__(self):

????self.session = requests.Session()

然后我們首先訪問一次網(wǎng)站即可獲取Cookie并且儲存


def get(self):

????r = self.session.get(url,headers=headers)

更多的用法可以查詢文檔

驗證碼的處理

分析r返回的文本信息

發(fā)現(xiàn)驗證碼的標簽的資源地址為 src=”CheckCode.aspx” ,我們可以直接requests然后下載驗證碼圖片嫉晶,下載圖片的一種優(yōu)雅的方式如下


def __get_code(self):

????????request = requests.get(self.__real_base_url + 'CheckCode.aspx', headers=self.__headers)

????????with open('code.jpg', 'wb')as f:

????????????f.write(request.content)

????????im = Image.open('code.jpg')

????????im.show()

????????print('Please input the code:')

????????code = input()

????????return code

上面的代碼把圖片保存為code.jpg骑疆,Python有一個Image模塊,可以實現(xiàn)自動打開圖片

這樣驗證碼就展示出來了替废,我們?nèi)斯ぽ斎牖蛘咿D(zhuǎn)入打碼平臺皆可

登錄數(shù)據(jù)的構(gòu)造

這是上面抓的登錄post的數(shù)據(jù)包箍铭,

?

發(fā)現(xiàn)有信息無法被解碼,應(yīng)該是gb2312編碼,查看解碼前的編碼

?

然后將不能解碼的代碼復(fù)制能夠解碼的地方

發(fā)現(xiàn)%D1%A7%C9%FA編碼解碼后為學生

這也就對應(yīng)了學生選項的登錄

學號和密碼和驗證碼能夠顯而易見地知道是哪些信息椎镣,但是我們發(fā)現(xiàn)有__VIEWSTATE這一項

查找一下诈火,這是一個表單隱藏信息,我們可以用BeautifulSoup庫解析可以得出該一項數(shù)據(jù)的值

?

這是完整的登錄數(shù)據(jù)包状答,

def __get_login_data(self, uid, password):

????????self.__uid = uid

????????request = self.__set_real_url()

????????soup = BeautifulSoup(request.text, 'lxml')

????????form_tag = soup.find('input')

????????__VIEWSTATE = form_tag['value']

????????code = self.__get_code()

????????data = {

????????????'__VIEWSTATE': __VIEWSTATE,

????????????'txtUserName': self.__uid,

????????????'TextBox2': password,

????????????'txtSecretCode': code,

????????????'RadioButtonList1': '學生'.encode('gb2312'),

????????????'Button1': '',

????????????'lbLanguage': '',

????????????'hidPdrs': '',

????????????'hidsc': '',

????????}

????????return data

登錄

如果登錄完成了冷守,如何判斷是否登錄成功呢刀崖?我們從登錄成功返回的界面發(fā)現(xiàn)有姓名這一標簽,而我們等一下也是需要學生姓名拍摇,所以我們用這個根據(jù)來判斷是否登錄成功亮钦。

?

代碼如下,進行了驗證碼用戶名和密碼的提示信息判別

def login(self,uid,password):

????????while True:

????????????data = self.__get_login_data(uid, password)

????????????request = requests.post(self.__real_base_url + 'default2.aspx', headers=self.__headers, data=data)

????????????soup = BeautifulSoup(request.text, 'lxml')

????????????try:

????????????????name_tag = soup.find(id='xhxm')

????????????????self.__name = name_tag.string[:len(name_tag.string) - 2]

????????????????print('歡迎'+self.__name)

????????????except:

????????????????print('Unknown Error,try to login again.')

????????????????time.sleep(0.5)

????????????????continue

????????????finally:

????????????????return True

獲取選課信息

接下來就是獲取選課信息了,這里我們以校公選課為例子充活,點擊進去蜂莉,進行抓包,headers沒有什么好注意的堪唐,我們只用關(guān)注get發(fā)送的包即可

?

??

發(fā)現(xiàn)有學號與姓名與gnmkdm這一項巡语,姓名我們需要編碼為gb2312的形式才能進行傳送

這里我們注意headers需要新增Referer項也就是當前訪問的網(wǎng)址,才能進行請求


def __enter_lessons_first(self):

????????data = {

????????????'xh': self.__uid,

????????????'xm': self.__name.encode('gb2312'),

????????????'gnmkdm': 'N121103',

????????}

????????self.__headers['Referer'] = self.__real_base_url + 'xs_main.aspx?xh=' + self.__uid

????????request = requests.get(self.__real_base_url + 'xf_xsqxxxk.aspx', params=data, headers=self.__headers)

????????self.__headers['Referer'] = request.url

????????soup = BeautifulSoup(request.text, 'lxml')

????????self.__set__VIEWSTATE(soup)

注意到上面有一個設(shè)置VIEWSTATE值的函數(shù)淮菠,這里等下在選課構(gòu)造數(shù)據(jù)包的時候會講

模擬選課

隨便選一門課男公,然后提交,抓包合陵,看一下有什么數(shù)據(jù)發(fā)送

??

前三個值可以在原網(wǎng)頁中input標簽中找到枢赔,由于前兩項為空,就不獲取了拥知,而第三項我們使用soup解析獲取即可踏拜,由于這個操作是每請求一次就變化的,我們寫成一個函數(shù)低剔,每次請求完成就設(shè)置一次速梗。

?

Python

1

2

3

def __set__VIEWSTATE(self, soup):

????????__VIEWSTATE_tag = soup.find('input', attrs={'name': '__VIEWSTATE'})

????????self.__base_data['__VIEWSTATE'] = __VIEWSTATE_tag['value']

而其他數(shù)據(jù),我們通過搜索響應(yīng)網(wǎng)頁就可以知道他們是干什么用的襟齿,這里我只說明我們要用的數(shù)據(jù)姻锁。

TextBox1為搜索框數(shù)據(jù),我們可以用這個來搜索課程猜欺,dpkcmcGrid:txtPageSize為一頁顯示多少數(shù)據(jù)位隶,經(jīng)過測試,服務(wù)器最多響應(yīng)200條开皿。

值得注意的是ddl_xqbs這個校區(qū)數(shù)據(jù)信息涧黄,我所在的校區(qū)的數(shù)字代號為2,也許不同學校設(shè)置有所不同赋荆,需要自己設(shè)置一下笋妥,也可以從網(wǎng)頁中獲取

下面是基礎(chǔ)數(shù)據(jù)包,由于我們搜索課程與選擇課程都要使用這個基礎(chǔ)數(shù)據(jù)包糠睡,所以我們直接在init函數(shù)里面新增

??self.__base_data = {

????????????'__EVENTTARGET': '',

????????????'__EVENTARGUMENT': '',

????????????'__VIEWSTATE': '',

????????????'ddl_kcxz': '',

????????????'ddl_ywyl': '',

????????????'ddl_kcgs': '',

????????????'ddl_xqbs': '2',

????????????'ddl_sksj': '',

????????????'TextBox1': '',

????????????'dpkcmcGrid:txtChoosePage': '1',

????????????'dpkcmcGrid:txtPageSize': '200',

????????}

然后我們關(guān)注一下這條數(shù)據(jù)挽鞠,我們搜索一下,發(fā)現(xiàn)這是課程的提交選課的代碼狈孔,所以我們也可以直接從網(wǎng)頁中獲取信认,而on表示選項被選上

?

Python

1kcmcGrid:_ctl2:xk:'on'

搜索課程

課程有很多信息,比如名字均抽,上課時間嫁赏,地點,這些東西確定好了才知道選的是哪門課油挥,所以我們先新建一個類來存儲信息

??class Lesson:

????????def __init__(self, name, code, teacher_name, Time, number):

????????????self.name = name

????????????self.code = code

????????????self.teacher_name = teacher_name

????????????self.time = Time

????????????self.number = number

????????def show(self):

????????????print('name:' + self.name + 'code:' + self.code + 'teacher_name:' + self.teacher_name + 'time:' + self.time)

有了這個類,我們就可以進行搜索課程了,具體代碼看下面代碼渠退,解析網(wǎng)頁內(nèi)容就不細講了块仆。

def __search_lessons(self, lesson_name=''):

????????self.__base_data['TextBox1'] = lesson_name.encode('gb2312')

????????request = requests.post(self.__headers['Referer'], data=self.__base_data, headers=self.__headers)

????????soup = BeautifulSoup(request.text, 'lxml')

????????self.__set__VIEWSTATE(soup)

????????return self.__get_lessons(soup)

????def __get_lessons(self, soup):

????????lesson_list = []

????????lessons_tag = soup.find('table', id='kcmcGrid')

????????lesson_tag_list = lessons_tag.find_all('tr')[1:]

????????for lesson_tag in lesson_tag_list:

????????????td_list = lesson_tag.find_all('td')

????????????code = td_list[0].input['name']

????????????name = td_list[1].string

????????????teacher_name = td_list[3].string

????????????Time = td_list[4]['title']

????????????number = td_list[10].string

????????????lesson = self.Lesson(name, code, teacher_name, Time, number)

????????????lesson_list.append(lesson)

????????return lesson_list

進行選課

選課我們只要將lesson_list傳入即可,這就是我們之前創(chuàng)建的Lesson類的實例的列表惋鹅,’Button’的內(nèi)容為’ 提交 ‘则酝,這兩邊各有一個空格,完事后我們可以進行發(fā)送請求進行選課闰集。

這里我們用正則提取了錯誤信息沽讹,比如選課時間未到、上課時間沖突這些錯誤信息來提示用戶武鲁,我們還解析了網(wǎng)頁的已選課程爽雄,這里也不細講了,都是基礎(chǔ)的網(wǎng)頁解析沐鼠。

def __select_lesson(self, lesson_list):

????????data = copy.deepcopy(self.__base_data)

????????data['Button1'] = '??提交??'.encode('gb2312')

????????for lesson in lesson_list:

????????????code = lesson.code

????????????data[code] = 'on'

????????request = requests.post(self.__headers['Referer'], data=data, headers=self.__headers)

????????soup = BeautifulSoup(request.text, 'lxml')

????????self.__set__VIEWSTATE(soup)

????????error_tag = soup.html.head.script

????????if not error_tag is None:

????????????error_tag_text = error_tag.string

????????????r = "alert\('(.+?)'$$;"

????????????for s in re.findall(r, error_tag_text):

????????????????print(s)

????????print('已選課程:')

????????selected_lessons_pre_tag = soup.find('legend', text='已選課程')

????????selected_lessons_tag = selected_lessons_pre_tag.next_sibling

????????tr_list = selected_lessons_tag.find_all('tr')[1:]

????????for tr in tr_list:

????????????td = tr.find('td')

????????????print(td.string)

總結(jié)

這次我們完成了模擬正方教務(wù)系統(tǒng)選課的過程挚瘟,由于這個教務(wù)系統(tǒng)技術(shù)比較陳舊,所以比較好弄饲梭,事實上搶課的時候用Fiddler即可完成操作乘盖,因為我們只需要提前登錄然后記錄網(wǎng)址即可。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末排拷,一起剝皮案震驚了整個濱河市侧漓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌监氢,老刑警劉巖布蔗,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異浪腐,居然都是意外死亡纵揍,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門议街,熙熙樓的掌柜王于貴愁眉苦臉地迎上來泽谨,“玉大人,你說我怎么就攤上這事“杀ⅲ” “怎么了骨杂?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長雄卷。 經(jīng)常有香客問我搓蚪,道長,這世上最難降的妖魔是什么丁鹉? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任妒潭,我火速辦了婚禮,結(jié)果婚禮上揣钦,老公的妹妹穿的比我還像新娘雳灾。我一直安慰自己,他們只是感情好冯凹,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布谎亩。 她就那樣靜靜地躺著,像睡著了一般谈竿。 火紅的嫁衣襯著肌膚如雪团驱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天空凸,我揣著相機與錄音嚎花,去河邊找鬼。 笑死呀洲,一個胖子當著我的面吹牛紊选,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播道逗,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼兵罢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了滓窍?” 一聲冷哼從身側(cè)響起卖词,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吏夯,沒想到半個月后此蜈,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡噪生,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年裆赵,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片跺嗽。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡战授,死狀恐怖页藻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情植兰,我是刑警寧澤份帐,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站钉跷,受9級特大地震影響弥鹦,放射性物質(zhì)發(fā)生泄漏肚逸。R本人自食惡果不足惜爷辙,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望朦促。 院中可真熱鬧膝晾,春花似錦、人聲如沸务冕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽禀忆。三九已至臊旭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間箩退,已是汗流浹背离熏。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留戴涝,地道東北人滋戳。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像啥刻,于是被迫代替她去往敵國和親奸鸯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 這是我另一個階段的開始可帽,謹以此文作一個了結(jié)娄涩。人生如白駒過隙,這是我大學中最后的一年映跟。這四年中蓄拣,我知道什么是生活中最...
    _阿蘿閱讀 197評論 1 1
  • 如果可以剃法,一生只做一件事碎捺,吟詩也好,飲歡也好。
    旋木i閱讀 366評論 0 0
  • 《成熟職業(yè)人的職涯定位》課題綱要 課題類別: 《人力資源管理》提升類課題收厨。 培訓對象: 企業(yè)中層管理者晋柱、基層管理者...
    初小湄閱讀 143評論 0 0
  • 《花開如夢》 綠茵茵的草地, 已經(jīng)綠了很多年诵叁, 從腳下綠向遠方雁竞, 從春天綠滿冬天。 我扎根在草地拧额, 相信草地也有滿...
    不語不問閱讀 336評論 0 1