python selenium模塊實現(xiàn)自動選課

事情緣由還得從那天下午的課說起严卖。當時大家都在認真聽課迎变。突然,旁邊一哥們說他搶到了“高級數(shù)理邏輯”了,what滑黔?脱柱?猿推?“高級數(shù)理邏輯”秘症?就是那門課水易過的神課?可是明明選課系統(tǒng)剛開始1分鐘不到就沒了呀蔑赘。于是狸驳,就問了他是怎么搞到的。他說是運行了幾行JavaScript腳本缩赛,自動刷課的耙箍。我恍然大悟,原來你們都是這么選課的八肘伞辩昆!于是就考慮要不自己也搞個腳本?事不宜遲旨袒,課后就開搞汁针!

具體怎么操作呢?我最開始的想法是調(diào)出Chrome控制臺砚尽,寫好JavaScript代碼施无,然后準備循環(huán)刷新運行。但老是報錯“no such element”必孤,以前沒怎么用過JavaScript猾骡,以為跳轉(zhuǎn)到不同的頁面之后,原頁面上的代碼就不能用了,所以會出現(xiàn)找不到元素的錯誤⌒讼耄現(xiàn)在回過頭來看幢哨,原來是由于該元素在另外一個frame中里面,必須先移動到另外一個frame嫂便,才能找到對應的元素捞镰,所以才會報這個錯。有時間搞個JS版的腳本毙替。

不管怎么樣岸售,直接在控制臺執(zhí)行JavaScript的想法在當時看來是不行了。這時我想到了假期實習時曾用python selenium庫試著爬取微博用戶的頭像蔚龙,這個庫能實現(xiàn)摸擬瀏覽器運行,不需要分析各種表單提交參數(shù)映胁,就能讀到動態(tài)網(wǎng)頁的所有信息木羹,實在是爬動態(tài)網(wǎng)頁的首選,缺點是速度比較慢解孙。后來坑填,因為新浪PC站的反爬蟲相對嚴格,最終還是用了requests庫加上cookie參數(shù)爬微博移動站弛姜。如果要爬取社交網(wǎng)站的數(shù)據(jù)的話脐瑰,其對應的靜態(tài)的移動站是比較靠譜的選擇。

最終決定選擇用python廷臼,結(jié)合selenium庫實現(xiàn)自動選課苍在。
正式進入今天的主題。

環(huán)境配置

  1. 安裝python3荠商,再安裝selenium庫寂恬,直接pip install selenium就行。
  2. 下載chromedriver驅(qū)動莱没,也可以選擇沒有界面的phantomJS瀏覽器初肉,為了方便調(diào)試,也不追求速度饰躲,我選擇了有界面的chrome瀏覽器牙咏。
  3. 引入Chrome瀏覽器
    chromedriver = "E:\LabProjects\crwalChinaZ\chromedriver"
    os.environ['webdriver.chrome.driver'] = chromedriver
    driver = webdriver.Chrome(chromedriver)

用上面這種方式啟動Chrome不用設(shè)置環(huán)境變量,只需要給出chromedriver的本地文件路徑即可嘹裂。然后程序就會打開不帶任何配置的純凈的chrome瀏覽器(可以給webdriver.Chrome()函數(shù)傳入配置參數(shù)妄壶,比如插件,這樣瀏覽器就會帶上相應的插件)寄狼。
執(zhí)行 driver.get('http://yjxt.bupt.edu.cn/') 打開選課網(wǎng)站盯拱,此時運行效果如下

登錄界面

填充表單

現(xiàn)在已經(jīng)成功打開了教務(wù)處的網(wǎng)站,下一步輸入賬戶密碼,實現(xiàn)登錄狡逢。

首先定位賬戶密碼表單的位置宁舰,傳入自己的賬號和密碼。driver.get(url)用于打開一個網(wǎng)頁奢浑,但由于現(xiàn)在的大多數(shù)的Web應用程序使用Ajax技術(shù)蛮艰,當一個頁面被加載到瀏覽器時,該頁面內(nèi)的元素可以在不同的時間點被加載雀彼。而driver.get(url)并不保證web頁面所有元素加載完成后再返回壤蚜。對于這樣的情況,官網(wǎng)給的建議是顯式或隱式地等待一段時間徊哑。用driver.implicitly_wait(seconds)實現(xiàn)隱式等待袜刷,WebDriverWait()(下面會提到)實現(xiàn)隱式等待。根據(jù)函數(shù)單詞意思莺丑,“隱式等待”很容易理解著蟹,就相當于sleep一段時間,那顯式等待WebDriverWait()怎么理解呢梢莽?我們先看該函數(shù)的一個使用示例:

try:
    CourseManagement = WebDriverWait(driver, 20).until(
        EC.presence_of_element_located((By.ID, 'menu')))
except Exception as e:
    print(e)

以上代碼表示最多等待瀏覽器20秒萧豆,或直到ID為“menu”的節(jié)點出現(xiàn)為止,如果元素出現(xiàn)昏名,則將這個節(jié)點賦給了CourseManagement涮雷,如果超時了則報錯。結(jié)合函數(shù)的字面意思也很好理解轻局。具體各個參數(shù)的意義詳見selenium中文文檔

待頁面元素都加載完成后洪鸭,需在網(wǎng)頁的源碼中找到賬戶密碼表單元素的位置。注意仑扑,必須通過“更多工具-》開發(fā)者平臺”或直接“右鍵-》檢查”卿嘲,而不能通過“右鍵-》查看網(wǎng)頁源代碼”來獲得查看頁面的源代碼,這兩者的內(nèi)容是不同的夫壁,前者包含了靜態(tài)和動態(tài)加載的源碼拾枣,后者只有靜態(tài)的源碼,沒有我們所需要的表單元素盒让。

selenium提供了很多定位元素的方法梅肤,常用的有find_element_by_idfind_element_by_name邑茄,find_element_by_xpath姨蝴。官網(wǎng)提供了更多定位元素的方法,詳見selenium中文文檔肺缕。如何確定元素的xpath路徑左医,一直是件讓人頭疼的事授帕。有個小技巧很有用,在開發(fā)者平臺上找到要找的頁面元素浮梢,然后“右鍵-》copy-》copy xpath”跛十,這樣該元素的xpath路勁就復制到粘貼板上了,直接粘貼即可秕硝,非常好用芥映!找到表單的代碼如下:

driver.implicitly_wait(3)
# driver.maximize_window()
account = driver.find_element_by_id("username")
passwd = driver.find_element_by_id('password')

確定表單之后,需要填充表單远豺,這里使用send_keys方法奈偏,分別傳入你的賬戶和密碼填充表單。

account.send_keys(config.account)
passwd.send_keys(config.password)

提交表單

表單填好后躯护,當然是提交表單惊来。在selenium中有幾種方法能提交表單。

  1. 在頁面中觀察對應的提交按鈕棺滞,找到這個元素裁蚁,然后執(zhí)行該元素的click()方法,實現(xiàn)表單提交检眯。在這個頁面中厘擂,“提交”按鈕當然是“立即登錄”按鈕了昆淡,找到這個元素再執(zhí)行click()方法即可锰瘸。這種方法雖然通用,但必須找到登錄元素所在的位置昂灵,比較麻煩避凝;
  2. 直接執(zhí)行account.submit()方法,也能提交表單眨补。當調(diào)用元素的submit()方法時管削,selenium會尋找離該元素最近的可提交的元素,具體是有type="submit"屬性的元素撑螺,并提交含思。這里離account最近的滿足該條件的元素當然就是“立即登錄”按鈕啊,所以也能達到提交表單的效果甘晤。當然含潘,按照這個原理,也可以通過密碼框元素的submit()方法即passwd.submit()實現(xiàn)同樣的效果线婚,非常方便遏弱,推薦使用這種方法;
  3. 最后一種方法是模擬鍵盤的操作塞弊。很多網(wǎng)站登錄頁面的實現(xiàn)邏輯都是賬戶和密碼填好后漱逸,直接按回車就可以提交表單泪姨,實現(xiàn)登錄。selenium提供了模擬鍵盤的方法饰抒,如elem.send_keys(Keys.RETURN)肮砾,這相當于“點擊”了回車鍵,實現(xiàn)同樣的效果循集。

綜合來說唇敞,個人覺得第二種方法更加直觀好用,第三種模擬鍵盤的方法需要考慮網(wǎng)站的鍵位順序咒彤,可能會出現(xiàn)一些問題疆柔。所以直接執(zhí)行account.submit(),進入選課系統(tǒng)镶柱,現(xiàn)在頁面如下:

![選課系統(tǒng)界面1]
](http://upload-images.jianshu.io/upload_images/3029393-2a0d92550e561530.JPG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

選課系統(tǒng)界面2

進入選課頁面

現(xiàn)已成功登錄系統(tǒng)旷档,按照選課流程,需要先點擊左下角“課務(wù)管理”歇拆,然后再點擊“課務(wù)管理”下面的“課程網(wǎng)上選課管理”鞋屈,此時右邊彈出的界面即為選課頁面。所以故觅,現(xiàn)階段的任務(wù)是找到“課務(wù)管理”和“課程網(wǎng)上選課管理”兩個元素厂庇,分別執(zhí)行click事件,進入選課界面输吏。

首先找到“課程管理”位置权旷,執(zhí)行click事件

CourseManagement = driver.find_element_by_xpath('//div[@id="menu"]/div[2]')
CourseManagement.click()

可運行時卻提示“no such element”錯,這令人很郁悶贯溅,代碼中明明有menu這個id的拄氯,為什么會報錯呢?這個問題糾結(jié)了好久它浅,selenium文檔上也沒有這個問題译柏,最后費了好大的力氣,終于在stackoverflow上找到了答案姐霍。有的頁面由幾個frame組成鄙麦,如果要訪問的元素不在當前的frame中,那么必須先切換到該元素所在的frame镊折,才能進一步選定元素胯府。那frame又是什么呢?我查了下腌乡,找到了下面這段簡要描述:

框架是網(wǎng)頁中常用的技術(shù)盟劫,可以讓多個URL的內(nèi)容顯示在一個頁面中。常用標簽FRAMESET与纽,F(xiàn)RAME實現(xiàn)侣签。FRAMESET是用以劃分框窗塘装,每一框窗由一個FRAME標記所標示,F(xiàn)RAME必須在FRAMESET范圍中使用影所。iframe在frame的基礎(chǔ)上提供了更多好用的特性蹦肴。

仔細一看,左邊導航欄果然在一個在一個id為MenuFrame的iframe中猴娩,而剛才相當于在默認的frame中阴幌,當然找不到這個元素,所以現(xiàn)在的任務(wù)是轉(zhuǎn)到相應的frame卷中,再執(zhí)行操作矛双。【4.jpg】

頁面源代碼

了解原因后蟆豫,查了文檔议忽,發(fā)現(xiàn)switch_to_frame()可以轉(zhuǎn)到指定的frame,代碼段如下:

frame = driver.find_element_by_id("MenuFrame")
driver.switch_to_frame(frame)

進入正確的frame之后十减,下面的代碼就能正確執(zhí)行了

CourseManagement = driver.find_element_by_xpath('//div[@id="menu"]/div[2]')
CourseManagement.click()

下一步是點擊“課程網(wǎng)上選課管理”栈幸。于是,按上面的套路帮辟,我寫了類似的代碼

driver.find_element_by_id('tree1_2_a').click()

代碼執(zhí)行后速址,點擊事件能觸發(fā),但是右邊彈出的頁面卻并不是預想的選課頁面由驹。仔細一看芍锚,原來是錯誤地“點擊”了“學期課表信息查詢”按鈕,導致右邊界面不對荔棉。再次確認元素的id沒問題后闹炉,接著又執(zhí)行了幾次蒿赢,每次結(jié)果都不太一樣润樱,有時候“點擊”上面的按鈕,有的時候“點擊”下面的按鈕羡棵。程序員的都知道壹若,這種不按套路跑的程序是最讓人頭疼的,代碼明明是對的皂冰,但為什么每次結(jié)果都不一樣呢店展?難道還是代碼的問題?代碼肯定沒錯秃流,應該是環(huán)境的問題......

ActionChains類

這一通無意義的想法下來赂蕴,我還是乖乖谷歌吧。用中文搜了好久也找不到對應的問題舶胀,最后還是用了英文關(guān)鍵字才找到了問題的所在概说。這種問題主要是由于模擬瀏覽器的指針定位錯誤引起的碧注,就相當于鼠標的坐標計算錯了,所以導致點擊了錯誤的位置糖赔。有人提出了可以用ActionChains類來實現(xiàn)點擊事件萍丐,以下是ActionChains實現(xiàn)示例:

menu = driver.find_element_by_css_selector(".nav")
hidden_submenu = driver.find_element_by_css_selector(".nav #submenu1")
ActionChains(driver).move_to_element(menu).click(hidden_submenu).perform()

最后一行是一個動作鏈的實現(xiàn),首先移動到menu元素放典,然后點擊hidden_submenu元素逝变,最后的perform()表示立即執(zhí)行該動作鏈。ActionChains實現(xiàn)機制類似于真實的鼠標操作奋构,容易理解壳影。但代碼改用ActionChains實現(xiàn)鼠標點擊事件后,錯誤仍然存在弥臼,真是讓人奇怪态贤,難不成確實是環(huán)境的問題?看來還得找另外的方法醋火。

嵌入JavaScript代碼

stackoverflow上有人提到悠汽,selenium有直接執(zhí)行JavaScript代碼的接口。selenium本身就是一個JS模擬器芥驳,用原生的JavaScript實現(xiàn)點擊事件肯定沒問題柿冲。貌似有點道理,先試一試再說兆旬。于是我嵌入了一行簡單的JavaScript代碼

driver.execute_script('document.getElementById("tree1_2_a").click()')

再次運行假抄,bug解決!

一路隨著bug狂奔之后丽猬,最終的選課界面終于出現(xiàn)了宿饱,下一步就是就是找到要選的課的位置,循環(huán)判斷能否選課脚祟,再傳遞click事件谬以,完成選課!

返回默認frame

然而由桌,還是太年輕为黎,高興得太早了。接著行您,先找到課的位置铭乾,再執(zhí)行簡單的點擊事件(PS. 下面的xpath路勁是直接在控制臺復制的,方法見上娃循,簡單快速?婚荨)

driver.find_element_by_xpath('//*[@id="contentParent_dgData"]/tbody/tr[44]/td[8]')

但是又提示“no such element”錯誤。又是這個錯誤捌斧!仔細一想笛质,難道選課頁面在另外一個frame里吹泡?仔細一看,還真是经瓷。所以必須先轉(zhuǎn)到選課頁面所在的frame爆哑,然后才能進行操作。于是又有了下面代碼

Courseframe = driver.find_element_by_id("PageFrame")
driver.switch_to_frame(Courseframe)

又是“no such element”錯誤舆吮!為什么呢揭朝?原來兩個frame間的關(guān)系是平行的,在其中一個frame是看不到另一個frame的元素的色冀,必須先進入主frame潭袱,即相當于這兩個frame的父frame,然后才能進入另外一個frame锋恬。查看官方文檔后屯换,發(fā)現(xiàn)switch_to_default_content()函數(shù)能切換到默認的frame。執(zhí)行這個函數(shù)后与学,上面的代碼就能正確地執(zhí)行了彤悔。

到此,下面的邏輯就很簡單了索守。先循環(huán)判斷要選的課是否處于可選狀態(tài)晕窑,可以的話直接執(zhí)行click事件。
由于網(wǎng)站的frame用得比較多卵佛,需要特別注意frame間的轉(zhuǎn)換杨赤。

多說一句

最近阿里月餅事件鬧得沸沸揚揚,我也不是受這件事的啟發(fā)才寫腳本的截汪,純粹是感興趣疾牲。
任務(wù)自動化本來就是程序員的一大樂趣,無關(guān)價值觀衙解。

附. 完整代碼:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait 
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
import os
import io
import sys
import config
import time

# 將wanted_course_num改為想選的課的順序
# 有效溝通技巧是0阳柔,寬帶通信網(wǎng)是1,以此類推
wanted_course_num = 42
wanted_course_string = '//*[@id="contentParent_dgData_hykFull_'
wanted_course = wanted_course_string + str(wanted_course_num) + '"]'
wanted_course2 = 'contentParent_dgData_hykSelkc_' + str(wanted_course_num)
print(wanted_course)
# 下載chromedriver丢郊,我這里是放在了
# E:\LabProjects\crwalChinaZ\chromedriver
# 更改為你放置的位置
chromedriver = "E:\LabProjects\crwalChinaZ\chromedriver"
os.environ['webdriver.chrome.driver'] = chromedriver
driver = webdriver.Chrome(chromedriver)
# driver = webdriver.PhantomJS()
driver.get('http://yjxt.bupt.edu.cn/')
driver.implicitly_wait(3)
# driver.maximize_window()
account = driver.find_element_by_id("username")
passwd = driver.find_element_by_id('password')
account.send_keys(config.account)
passwd.send_keys(config.password)
account.submit()
# try:
#     CourseManagement = WebDriverWait(driver, 20).until(
#         EC.presence_of_element_located((By.ID, 'menu')))
# except Exception as e:
#     print(e)
driver.implicitly_wait(10)
while 1:
    frame = driver.find_element_by_id("MenuFrame")
    driver.switch_to_frame(frame)
    CourseManagement = driver.find_element_by_xpath('//div[@id="menu"]/div[2]')
    CourseManagement.click()
    driver.execute_script('document.getElementById("tree1_2_a").click()')
    # driver.find_element_by_id('tree1_2_a').click()
    driver.implicitly_wait(5)
    driver.switch_to_default_content()
    Courseframe = driver.find_element_by_id("PageFrame")
    driver.switch_to_frame(Courseframe)


    logic_button = driver.find_element_by_xpath(wanted_course).text
    if u'班級已全選滿' in logic_button:
        print('wait 10 seconds!') 
    else:
        # button = driver.find_element_by_xpath('//*[@id="contentParent_dgData"]/tbody/tr[44]/td[8]')
        # button.click()
        string = 'document.getElementById("{}").click()'.format(wanted_course2)
        # print(string)
        # driver.execute_script('document.getElementById(%s).click()' %(wanted_course2))
        driver.execute_script(string)
        driver.implicitly_wait(2)
        driver.switch_to_default_content()
        driver.switch_to_frame(driver.find_element_by_xpath("http://iframe[@name='selClass']"))
        driver.execute_script('document.getElementById("contentParent_dgData_ImageButton1_0").click()')
        break;
    driver.switch_to_default_content()
    # driver.refresh()
    time.sleep(10)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末盔沫,一起剝皮案震驚了整個濱河市医咨,隨后出現(xiàn)的幾起案子枫匾,更是在濱河造成了極大的恐慌,老刑警劉巖拟淮,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件干茉,死亡現(xiàn)場離奇詭異,居然都是意外死亡很泊,警方通過查閱死者的電腦和手機角虫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門沾谓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人戳鹅,你說我怎么就攤上這事均驶。” “怎么了枫虏?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵妇穴,是天一觀的道長。 經(jīng)常有香客問我隶债,道長腾它,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任死讹,我火速辦了婚禮瞒滴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘赞警。我一直安慰自己妓忍,他們只是感情好,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布愧旦。 她就那樣靜靜地躺著单默,像睡著了一般。 火紅的嫁衣襯著肌膚如雪忘瓦。 梳的紋絲不亂的頭發(fā)上搁廓,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天,我揣著相機與錄音耕皮,去河邊找鬼境蜕。 笑死,一個胖子當著我的面吹牛凌停,可吹牛的內(nèi)容都是我干的粱年。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼罚拟,長吁一口氣:“原來是場噩夢啊……” “哼台诗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起赐俗,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤拉队,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后阻逮,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體粱快,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了事哭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片漫雷。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖鳍咱,靈堂內(nèi)的尸體忽然破棺而出降盹,到底是詐尸還是另有隱情,我是刑警寧澤谤辜,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布澎现,位于F島的核電站,受9級特大地震影響每辟,放射性物質(zhì)發(fā)生泄漏剑辫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一渠欺、第九天 我趴在偏房一處隱蔽的房頂上張望妹蔽。 院中可真熱鬧,春花似錦挠将、人聲如沸胳岂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乳丰。三九已至,卻和暖如春内贮,著一層夾襖步出監(jiān)牢的瞬間产园,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工夜郁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留什燕,地道東北人。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓竞端,卻偏偏與公主長得像屎即,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子事富,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

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