考試系統自動答題,你還在為不及格煩惱麼?
學校要求登入教務處網站做一個測試題,我做了看了看,30分鐘300道題,240分幾個,題量不少,題還不好做。研究了一下發現原來在網站上就有題庫的,但是一道題只有6s 的時間作答,邊查邊做肯定是時間不夠的。靈光一閃,人生苦短,我用Python,寫個自動答題的機器人吧。
思路:
- 爬取題庫並存儲到資料庫
- 模擬登入教務系統
- 進入答題頁面,遍歷題目,匹配資料庫中記錄,給出答案
- 提交資料
用到的工具:
- Python
- requests
- BeautifulSoup
- mongodb
實現過程:
-
模擬登入
以前研究過學校教務系統的登入,現在終於在正事上排上用場了。學校教務系統的登入還算簡單,沒有驗證碼,唯一一點兒小障礙是登入表單會有幾個隱藏欄位,有個欄位會動態改變,解決就是先GET一下登入網址,獲取這幾個欄位的值,再隨表單進行POST。程式碼:
import requests from bs4 import BeautifulSoup import os headers = { 'Connection': 'keep-alive', 'Cache-Control': 'max-age=0', 'Upgrade-Insecure-Requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Encoding': 'gzip, deflate, sdch', 'Accept-Language': 'zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4' } session = requests.session() # 先GET一下登入頁面獲得隱藏欄位 resp = session.get("http://ids.qfnu.edu.cn/authserver/login?service=http%3A%2F%2F202.194.188.19%2Fcaslogin.jsp", headers=headers) bsObj = BeautifulSoup(resp.text, "html.parser") lt = bsObj.find('input', {'name':'lt'})['value'] execution = bsObj.find('input', {'name':'execution'})['value'] params = { 'username': os.environ.get('STU_ID'), # 環境變數獲取的學號 'password': os.environ.get('STU_PWD'), # 環境變數獲取的密碼 'lt': lt, 'execution': execution, '_eventId': 'submit', 'rmShown': '1' } # post 資料登入成功 resp = session.post("http://ids.qfnu.edu.cn/authserver/login?service=http%3A%2F%2F202.194.188.19%2Fcaslogin.jsp", data=params, headers=headers)
-
題庫爬取
用了Python的requests庫進行頁面的爬取,勵志小哥寫的這個庫的確好用,特別是運用 requests.session()
之後,處理好登入之後,再也不用管那些煩人的cookie 啥的了,用了BeautifulSoup來進行頁面解析,用起來也是很順手。開始有亂碼,一看網頁編碼是gb2312的,稍微設定一下也OK了。
內容有了,當然要儲存起來了,不然每次答題都有爬題庫多麻煩,何況是些千年不變的東西。開始用的是MySQL,然而編碼問題讓人頭疼,設定編碼為gb2312,儲存的時候說有內容 gb2312 又解析不了,於是程式就掛了。搞了很長時間也沒搞定,還把我的 Sequel Pro 給搞掛了,f***!
靜下心來一想,題庫只要寫資料庫寫一遍就OK了,但是答題的時候會頻繁地查詢資料庫,用nosql資料庫多好。題目做鍵,答案做值就行了。看過redis,但畢竟是記憶體型資料庫,雖然快,但是還要做持久化,直接用mongodb吧,還沒看過,剛好學習了,邊學邊用。
爬取並存儲題庫部分的程式碼:
tikubhs = [8692, 10988, 10989, 10990, 10991, 10992, 10993, 10994, 10995] # 每一類題庫的編號 pages = [153, 77, 13, 18, 22, 27, 10, 39, 12] # 每一個題庫的題目頁數 from pymongo import MongoClient client = MongoClient() db = client.shitiDB # 資料庫名 shitiDB shiti_table = db.shitis # 表名 shitis for tikubh, page_range in zip(tikubhs, pages): for page in range(1, page_range+1): url = "http://aqjy.qfnu.edu.cn/redir.php?catalog_id=6&cmd=learning&tikubh="+str(tikubh)+"&page="+str(page) resp = session.get(url) resp.encoding = 'gb2312' bsObj = BeautifulSoup(resp.text, "html.parser") shitis = bsObj.find_all('div', class_='shiti') daans = bsObj.find_all('span', style='color:#666666') values = [] for timu_str, daan_str in zip(shitis, daans): shitibh = int(timu_str.h3.text[0:5]) timu = timu_str.h3.text[6:] daan = daan_str.text[daan_str.text.find('標準答案')+5:].strip(' )') d = {'shitibh':shitibh, 'tikubh':tikubh, 'timu':timu, 'daan':daan} values.append(d) new_result = shiti_table.insert_many(values)
-
模擬作答
這一部分是最關鍵的部分,再這上面的耗時比較多,大部分時間都在研究他的資料的提交流程。
題目是分頁的,且選擇頁面或點選上下頁的時候,位址列的地址是不變的,說明分頁是通過js實現的,而不是直接用的連結:
研究得知,上下頁和頁面選擇都是通過post資料標誌到本頁面實現的
搞懂了這些資料的意義和他們之間的關係,用程式碼模擬出來就OK了,當作到最後一頁完成的時候,把tijiao
標誌也設為1,POST到原URL就完成了作答,這部分程式碼就不貼了,文末有GitHub連結。
-
可能的改進 寫好之後許多同學找我給答題,看看如果多的話用flask搭成個web服務。高效還不用擔心我洩露你們的密碼了。爬題庫的時候想的是爬答案的文字,爬成了ABCD的選項,多虧選項和答案文字的對應關係沒變,但也造成個別問題答案會有錯誤,致使我給答題的同學分數基本都在297-299之間(滿分300分),少有300分的同學。本來想改來著,轉念一想都滿分也不太好,有點誤差也好,可能這部分不會改了,感興趣的可以自己改改看。
ps: 本校的可以直接搭好拿來用就可以了,其他同學如有類似需求,這個系統是某公司開發的,好多學校都在用,folk一份改改應該也沒問題。