自動化選課(Python + selenium

来源:https://www.cnblogs.com/F0und/archive/2022/08/20/16608360.html
-Advertisement-
Play Games

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


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

工具

  • 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 = 'http://202.194.188.38/'
        self.account_url = 'http://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()


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 原文: Java 斷點下載(下載續傳)服務端及客戶端(Android)代碼 - Stars-One的雜貨小窩 最近在研究斷點下載(下載續傳)的功能,此功能需要服務端和客戶端進行對接編寫,本篇也是記錄一下關於貼上關於實現服務端(Spring Boot)與客戶端(Android)是如何實現下載續傳功能 ...
  • “請你說一下你對Happens-Before的理解” 你聽到這個問題的時候,知道怎麼回答嗎? 大家好,我是Mic,一個工作了14年的Java程式員。 併發編程是面試過程中重點考察的方向,能夠考察的方向有很多 關於這個問題,我把高手回答整理到了15W字的面試文檔裡面大家可以私信我領取 下麵看看高手的回 ...
  • 序列類型的操作 遍歷 從第一個元素到最後一個元素依次訪問(序列類型) for i in 序列: print(i) # i是序列中的值(如果該序列為字典,那麼i為字典的鍵) for i in enumerate(序列): # enumerate(序列):講序列拆分成國歌元組(值,下標) print(i ...
  • 頂層const和底層const 變數自身不能改變的是頂層const,比如const int,int *const的常量指針,變數所指的對象或者所引用的對象是不能改變的,而變數自身是可以改變的是底層const,比如const int *的指向常量對象的非常量指針。 左值和右值 左值是有具體存儲地址的值 ...
  • mamp pro 是最優秀的本地伺服器搭配軟體,也是最好的mysql開發環境和php開發環境,包含了acintosh、Apache、MySQL和PHP四大開發環境,用戶只要輕鬆點選就能對架站、討論區、論壇等必備的元件進行安裝,讓你輕鬆架設自己的web運行環境。 Mac版詳情:MAMP Pro for ...
  • 文末有Gitee鏈接,記得star哦 課程整體內容概述 第一部分:編程語言核心結構 主要知識點:變數、基本語法、分支、迴圈、數組、 第二部分:Java面向對象的核心邏輯 主要知識點:OOP、封裝、繼承、多態、介面、 第三部分:開發JavaSE高級應用程式 主要知識點:異常、集合、|℃、多線程、反射機 ...
  • 目錄 一.簡介 二.效果演示 三.源碼下載 四.猜你喜歡 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 轉場 零基礎 O ...
  • Java集合06 13.Map介面02 13.2Map介面常用方法 put():添加 remove():根據鍵鍵刪除映射關係 get():根據鍵獲取值 size():獲取元素個數 isEnpty():判斷個數是否為0 clear():清除 containsKey():查找鍵是否存在 例子1:Map接 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...