自动化选课(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 = '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()

评论关闭