自動化選課(Python + selenium

​ 前幾天聽到朋友說自己選課事情,突發奇想想要搞這樣一個東西,但是由於各種原因只做到以下的完成度,具體的情況也會在解釋的最後留下。這個只適用於曲師大的教務系統,因為用的這個系統來進行的一個調試,對於其他的系統,思路都是一樣的,程式碼也只適用於學習,請不要用以其他用途!程式碼放在最後。

工具

  • Python3
  • selenium庫(瀏覽器自動化操作)
  • ddddocr庫(OCR圖片文字識別)
  • time庫(定時操作)

思路

​ 對於想要做到的這個需求呢,我選擇的是python + selenium庫進行一個瀏覽器自動化操作,在寫的過程中因為發現了一個驗證碼的問題所以又用到了一個ddddocr庫,如果想要定時操作的話再加上一個time庫。之後就是理清操作的順序就好了,想到於人工操作一遍的過程。

  1. 進入系統登錄介面
  2. 進入用戶介面
  3. 進入選課介面
  4. 開始選課

大的一個方向、思路就是上面這四步,具體的細化之後再談及。

過程

進入系統登錄介面

​ 這一步就很簡單,直接用selenium庫打開chormedriver就好了

        self.driver = webdriver.Chrome() 
        self.driver.maximize_window()  # 最大化 

進入用戶介面

​ 這一步就是登錄,要完成的就是把帳號,密碼輸入對應的框,然後輸入驗證碼,點擊登錄。

​ 首先把帳號密碼輸入到框里很簡單

        self.driver.find_element(By.ID, 'userAccount').send_keys(self.user_account)
        self.driver.find_element(By.ID, 'userPassword').send_keys(self.user_password)

​ 直接調用selenium庫中的函數就可以進行該操作

​ 接下來是這一步驟的一個問題,就是如何過驗證碼,因為該系統的驗證碼圖片並不複雜,我想到的一個解決措施就是把驗證碼截圖,然後識別圖片上的資訊,然後重複上述操作就好了。

​ 如何截圖,我首先想到的是指定位置然後用某個庫截圖或者是打開圖片的鏈接保存圖片,後來發現每次點開鏈接圖片都是不一樣的(應該是JS的原因),最後發現原來selenium庫自帶一個元素截圖的函數。

	    ocr = ddddocr.DdddOcr()
        img = self.driver.find_element(By.ID, 'SafeCodeImg')  # 定位到驗證碼圖片
        img.screenshot('code.png')  # 給驗證碼元素截圖並保存
        with open('code.png', 'rb') as f:
            img_bytes = f.read()  # 讀取圖片編碼
        return ocr.classification(img_bytes)  # 返回圖片中的驗證碼

​ 這樣的話只需要定位到這個圖片元素,然後保存下來,再讀取編碼資訊,使用ddddocr庫來進行一個文字識別,返回圖片的驗證碼模擬登陸就好了

​ 最後登錄也就是找到元素然後調用函數模擬點擊就好了

self.driver.find_element(By.XPATH, "//*[@class='btn btn-primary login_btn']").click()

進入選課介面

​ 這一部分本來難度不算很高,但是因為JS的原因,有一些地方需要注意。

​ 首先就是模擬點擊一些摁扭可以轉到選課介面的地方。因為這個選課系統轉到一個頁面以後會產生一些新的東西(框架),這時候我們要再找上面的元素就要分清楚在哪一個框架上。

    self.driver.switch_to.frame("Frame1")  # !!

​ 通過F12開發者選項可以找到這個元素所在的框架不是初始的框架(frame)而是在JS產生的一個新框架中,那麼就需要用這個函數來轉到新的框架Frame1(通過開發者選項找到的frame id)

​ 只有這一點需要特別注意!!!

開始選課

​ 首先一點,進入這個介面的時候,瀏覽器的介面是切換了的,因此我們也需要將selenium的定位切換到我們所看到的介面上。

        new_window = self.driver.window_handles[-1]
        self.driver.switch_to.window(new_window)

​ 之後通過點擊又轉產生了一個新的框架,因此在進行一個轉框架的操作。

​ 最後就是通過課程id、上課老師名字、上課是星期幾三個來作為鍵確定唯一的課程,進行一個選擇,並重複上述操作直到完成全部選課。

        for my_course in self.user_course:
            self.driver.find_element(By.ID, 'kcxx').send_keys(my_course[1])  # 通過課程資訊查找
            self.driver.find_element(By.ID, 'skls').send_keys(my_course[2])  # 通過上課老師查找
            self.driver.find_element(By.XPATH, f"//*[@id='skxq']/option[{my_course[3]+1}]").click()
            # 選擇星期(一 - 2, 二 - 3, 三 - 4, 四 - 5, 五 - 6)
            self.driver.find_element(By.XPATH, "html/body/div[3]/input[6]").click()
            self.driver.find_element(By.ID, 'kcxx').clear()  # 清空課程查詢框
            self.driver.find_element(By.ID, 'skls').clear()  # 清空上課老師框
            time.sleep(1)
            """
                沒刷新出來加一個等待1秒
            """
            course_remain = self.driver.find_element(By.XPATH, "//*[@class='odd']/td[9]").text  # 查看課餘量
            if int(course_remain) > 0:
                self.driver.find_element(By.XPATH, "//*[@class='odd']/td[11]/div/a").click()
                """
                    這裡缺少了一部分確定的程式碼
                """
            else:
                print(f'{my_course[1]}選課失敗')
            time.sleep(3)  # 3秒的暫停

附加功能

定時功能

​ 使用time庫來進行一個定時開始執行

   run_time_h = 9  # 定時小時
    run_time_m = 0  # 定時分鐘
    while True:
        current_time = time.localtime(time.time())
        print(str(current_time.tm_hour) + '-' + str(current_time.tm_min) + '-' + str(current_time.tm_sec))
        if current_time.tm_hour == run_time_h and current_time.tm_min == run_time_m:
            break

一些操作的解釋

  1. selenium庫的使用可以查一下其他部落客的介紹
  2. 關於元素的定位,一般我習慣於使用通過ID和XPATH來定位(XPATH 就是 “//*[@xx=’abc’]/li[1]/”)這個也可以看看如何去使用。
  3. ddddocr庫是看圖片的編碼來轉換成文本

此程式碼可能出現的問題(完成度不是很高):

​ 因為我本身不是曲師大的校友所以說我沒有進行一個完全的操作,對系統的認識也不是很充分這樣寫出來的程式碼當然也完成度也不能說是很高的,下面我就大概說一下這個程式碼可能產生的問題。

  1. 如果系統崩潰(載入不出頁面……),程式應該是不能執行命令。
  2. 最後的選課部分只進行了一個點擊,並沒有確認,因此實際上這段程式碼是無法完成選課的!
  3. 可能圖片識別有一定的誤差,導致不能登陸成功(ddddocr庫可能出的問題)
  4. 操作過快以至於元素沒有載入出來,這一點我通過time.sleep()函數來進行了一個優化,每次選課間隙也添加一個3秒的一個等待。
  5. 可能通過ID、上課老師、上課星期幾三個限制無法確定唯一的課程
    ……
import time
import ddddocr
from selenium import webdriver
from selenium.webdriver.common.by import By


class CClassSelect:
    def __init__(self, i_account, i_password, i_course):
        self.user_account = i_account
        self.user_password = i_password
        self.user_course = i_course
        self.login_url = '//202.194.188.38/'
        self.account_url = '//202.194.188.38/jsxsd/framework/xsMain.jsp'
        self.driver = webdriver.Chrome()
        self.driver.maximize_window()

    def LoginAccount(self):
        self.driver.get(self.login_url)
        self.driver.find_element(By.ID, 'userAccount').send_keys(self.user_account)
        self.driver.find_element(By.ID, 'userPassword').send_keys(self.user_password)
        identify_code = self.Ocr()
        self.driver.find_element(By.ID, 'RANDOMCODE').send_keys(identify_code)
        self.driver.find_element(By.XPATH, "//*[@class='btn btn-primary login_btn']").click()

    def LoginClassSelect(self):
        time.sleep(1)
        self.driver.find_element(By.XPATH, "//*[@id='onesidebar']/div/ul/li[3]/span").click()
        self.driver.find_element(By.XPATH, "//*[@class='sidebar-menu']/li[7]/a").click()
        time.sleep(1)
        self.driver.find_element(By.XPATH, "//*[@class='treeview-menu menu-open']/li[1]/a").click()
        self.driver.switch_to.frame("Frame1")  # !!
        """
            iframe問題,卡了一段時間這個地方,可以百度到是不在一個frame的原因
            定位不到這個『進入選課』元素,
        """
        self.driver.find_element(By.ID, "jrxk").click()
        self.driver.find_element(By.XPATH, "//*[@class='Nsb_pw']/div/center/input[1]").click()

    def ClassSelect(self):
        new_window = self.driver.window_handles[-1]
        self.driver.switch_to.window(new_window)
        """
            切換到新的窗口
        """
        self.driver.find_element(By.XPATH, "//*[@id='topmenu']/li[4]/a").click()
        self.driver.switch_to.frame("mainFrame")  # 換frame

        """
            選課程式碼 ↓
        """
        for my_course in self.user_course:
            self.driver.find_element(By.ID, 'kcxx').send_keys(my_course[1])  # 通過課程資訊查找
            self.driver.find_element(By.ID, 'skls').send_keys(my_course[2])  # 通過上課老師查找
            self.driver.find_element(By.XPATH, f"//*[@id='skxq']/option[{my_course[3]+1}]").click()
            # 選擇星期(一 - 2, 二 - 3, 三 - 4, 四 - 5, 五 - 6)
            self.driver.find_element(By.XPATH, "html/body/div[3]/input[6]").click()
            self.driver.find_element(By.ID, 'kcxx').clear()  # 清空課程查詢框
            self.driver.find_element(By.ID, 'skls').clear()  # 清空上課老師框
            time.sleep(1)
            """
                沒刷新出來加一個等待1秒
            """
            course_remain = self.driver.find_element(By.XPATH, "//*[@class='odd']/td[9]").text  # 查看課餘量
            if int(course_remain) > 0:
                self.driver.find_element(By.XPATH, "//*[@class='odd']/td[11]/div/a").click()
                """
                    這裡缺少了一部分確定的程式碼
                """
            else:
                print(f'{my_course[1]}選課失敗')
            time.sleep(3)  # 3秒的暫停

    def Ocr(self):
        ocr = ddddocr.DdddOcr()
        img = self.driver.find_element(By.ID, 'SafeCodeImg')  # 定位到驗證碼圖片
        img.screenshot('code.png')  # 給驗證碼元素截圖並保存
        with open('code.png', 'rb') as f:
            img_bytes = f.read()  # 讀取圖片編碼
        return ocr.classification(img_bytes)  # 返回圖片中的驗證碼

    def Run(self):
        self.LoginAccount()
        self.LoginClassSelect()
        self.ClassSelect()
        self.driver.quit()


if __name__ == '__main__':
    # 定時運行
    run_time_h = 9  # 定時小時
    run_time_m = 0  # 定時分鐘
    while True:
        current_time = time.localtime(time.time())
        print(str(current_time.tm_hour) + '-' + str(current_time.tm_min) + '-' + str(current_time.tm_sec))
        if current_time.tm_hour == run_time_h and current_time.tm_min == run_time_m:
            break

    # 主程式
    input_account = "12345"  # 用戶帳號
    input_password = "abcde"  # 用戶密碼
    input_course = [['12345', '張三', 3], ['54321', '李四', 5]]  # 要選的課(ID,上課老師,星期)
    app = CClassSelect(input_account, input_password, input_course)
    app.Run()

Tags: