zoukankan      html  css  js  c++  java
  • 使用selenium实现学校时政在线测试自动答题 Jathon

    1. 在mysql中建表
      由于要将题库存到数据库中,要事先在数据库中建好数据表。我这里用了mysql数据库,也可以使用其他数据库。
      建表语句参考如下

      CREATE TABLE `data` (
          `pro` varchar(255) NOT NULL DEFAULT '',
          `res` varchar(10) DEFAULT NULL,
          PRIMARY KEY (`pro`)
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
      
    2. 主函数
      主函数中就实现代码框架,大致需要实现的功能有登录、获取题目、解答题目、提交答案并扩充题库

      if __name__ == "__main__":
          driver = webdriver.Chrome() # 打开Chrome浏览器
          driver.maximize_window() # 浏览器窗口最大化
      
          suc = login() # 自动登录
          if suc == False:
              print("登录失败")
              driver.close()  # 关闭浏览器
              exit(0)
      
          pro = get_pro() # 获取题目页
          if pro == None:
              print("试题获取失败")
              driver.close()  # 关闭浏览器
              exit(0)
      
          slove_pro(pro) # 解析并解答数据库中记录过的题目
          res = submit() # 提交答案并获取所有题目答案
          show_and_expand(res) # 显示成绩并且扩充题库
          driver.close()  # 关闭浏览器
      
    3. 登录函数
      用selenium获取输入框并向输入框发送文本信息,最后点击登录。如果加载超时或者用户名密码验证码输入错误都将返回False。

      def login():
          global driver
          driver.get("http://xszc.zptc.edu.cn/default.asp") # 打开网站登录页
          user = '【自己的学号】' # 设置用户名
          passwd = '【自己的密码】' # 设置密码
          captcha = get_captcha() # 识别验证码
          if captcha == None:
              return False
      
          wait = WebDriverWait(driver, 100) # 设置最长等待时间
          try:
              wait.until(EC.presence_of_element_located((By.ID, "username"))) # 等待用户名输入框加载完成
              sleep(2) # 睡两秒钟
              driver.find_element_by_id("username").send_keys(user) # 向用户名输入框发送用户名
      
              wait.until(EC.presence_of_element_located((By.ID, "Password"))) # 等待密码输入框加载完成
              sleep(2) # 睡两秒钟
              driver.find_element_by_id("Password").send_keys(passwd) # 向密码输入框发送密码
      
              wait.until(EC.presence_of_element_located((By.ID, "CheckCode")))  # 等待验证码输入框加载完成
              sleep(2)  # 睡两秒钟
              driver.find_element_by_id("CheckCode").send_keys(captcha)  # 向验证码输入框发送验证码
      
              wait.until(EC.presence_of_element_located((By.CLASS_NAME, "buttonlogin1")))  # 等待登录按钮加载完成
              sleep(2)  # 睡两秒钟
              driver.find_element_by_class_name("buttonlogin1").click()  # 点击登录按钮
              sleep(1)
              if driver.current_url == "http://xszc.zptc.edu.cn/user_ChkLogin.asp":
                  return False
          except:
              return False
      
    4. 破解验证码
      登录的难点在于破解验证码。一般jpg格式或者png格式的图片可以直接调用百度AI接口,但是我们学校网站的验证码格式很特殊,所以使用selenium对验证码进行截图来转化格式,截图的格式都是png。而且验证码的尺寸还很小,百度AI接口无法识别过小的图片。所以还需要使用PIL库将图片放大20倍之后再调用百度AI接口识别。

      def get_captcha():
          wait = WebDriverWait(driver, 100)  # 设置最长等待时间
          captcha_xpath = "//*[@id='CheckCode']/../img" # 验证码的xpath
          captcha_path = "captcha.png" # 设置验证码本地保存路径
      
          try:
              wait.until(EC.presence_of_element_located((By.XPATH, captcha_xpath)))  # 等待验证码加载完成
          except:
              return None
      
          sleep(2)  # 睡两秒钟
          captcha = driver.find_element_by_xpath(captcha_xpath).screenshot_as_png # 获取png格式的验证码
          with open(captcha_path, "wb") as file:
              file.write(captcha) # 将验证码保存到本地
      
          image = Image.open(captcha_path) # 打开保存到本地的验证码
          image.resize((800, 200), Image.ANTIALIAS).save(captcha_path) # 将验证码大小放大20倍,验证码太小百度AI接口无法识别
          with open(captcha_path, "rb") as file:
              captcha = file.read() # 读取放大后的验证码
          os.remove(captcha_path) # 删除本地保存的验证码
      
          # 调用百度AI接口破验证码
          request_url = "https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic"
          img = base64.b64encode(captcha)
          params = {"image": img}
          access_token = get_token()
          request_url = request_url + "?access_token=" + access_token
          headers = {'content-type': 'application/x-www-form-urlencoded'}
          response = requests.post(request_url, data=params, headers=headers)
          if response:
              print(response.json())
      
          # 从百度AI返回的response里提取信息并返回或者判断为识别失败
          try:
              words = response.json()["words_result"][0]['words']
              return words
          except:
              return None
      

      用到的get_token()方法也是百度AI官网给出了的

    5. 获取答题页信息
      登录之后就可以一路点击到答题页了

      def get_pro():
          global driver
          wait = WebDriverWait(driver, 40) # 设置等待
          try:
              wait.until(EC.presence_of_element_located((By.XPATH, "/html/body/table[2]/tbody/tr/td/table/tbody/tr[1]/td[10]/a"))) # 等待练习测试区按钮加载完成
              sleep(1) # 睡一秒钟
              driver.find_element_by_xpath("/html/body/table[2]/tbody/tr/td/table/tbody/tr[1]/td[10]/a").click() # 点击进入练习测试区
      
              wait.until(EC.presence_of_element_located((By.XPATH, "/html/body/table[3]/tbody/tr/td/table[2]/tbody/tr[2]/td/a"))) # 等待时政在线测试按钮加载完成
              sleep(2) # 睡两秒钟
              driver.find_element_by_xpath("/html/body/table[3]/tbody/tr/td/table[2]/tbody/tr[2]/td/a").click() # 点击时政在线测试按钮
      
              wait.until(EC.presence_of_element_located((By.ID, "button"))) # 等待开始考试按钮加载完成
              sleep(2) # 睡两秒钟
              driver.find_element_by_id("button").click() # 点击开始考试按钮
      
              wait.until(EC.presence_of_element_located((By.XPATH, "/html/body/div/form/table/tbody/tr/td/table[2]/tbody/tr[5]/td[2]")))  # 等待试题加载完成
              body = driver.page_source # 获取页面源码
              sleep(1) # 睡一秒钟
              return body
          except:
              return None
      
    6. 答题
      然后就是最关键的答题了,获取题目从数据库中找到题目答案,然后选择正确答案。

      def slove_pro(data):
          global driver
          res = etree.HTML(data) # 解析网页源码
          conn = MySQLdb.connect(db='【数据库名】', host='【端口号】', user='【数据库用户名】', passwd='【数据库密码】', charset='utf8') # 连接数据库
          cur = conn.cursor() # 获取数据库游标
      
          list_01 = [i.strip(" \n") for i in res.xpath('//*[@id="submenu0"]/div[1]/table/tbody/tr/td/text()')] # 获取判断题
          list_02 = [i.strip(" \n") for i in res.xpath('//*[@id="submenu1"]/div[1]/table/tbody/tr/td/text()')] # 获取单选题
          list_03 = [i.strip(" \n") for i in res.xpath('//*[@id="submenu2"]/div[1]/table/tbody/tr/td/text()')] # 获取多选题
      
          index = 0 # 记录当前正在解答的题目题号
          for item in list_01:
              if len(item) <= 1 or item[1] == '.': # 当前条目是选项而非题目,例如:  "A. 对"
                  continue
              index = index + 1
              cur.execute("select * from data where pro = '%s'" % item) # 查找数据库中是否存在本题答案
              for pro, res in cur.fetchall():
                  print(pro, res[5:])
                  for val in res[5:]: # 单选和判断不需要此循环,但是多选题答案不一,需要勾选所以答案
                      sleep(0.5) # 睡半秒钟
                      driver.find_element_by_xpath('//*[@name="cjpd%s" and @value="%s"]'%(index, val)).click() # 勾选正确选项
      
          index = 0 # 记录当前正在解答的题目题号
          for item in list_02:
              if len(item) <= 1 or item[1] == '.': # 当前条目是选项而非题目"
                  continue
              index = index + 1
              cur.execute("select * from data where pro = '%s'" % item) # 查找数据库中是否存在本题答案
              for pro, res in cur.fetchall():
                  print(pro, res[5:])
                  for val in res[5:]: # 单选和判断不需要此循环,但是多选题答案不一,需要勾选所以答案
                      sleep(0.5)  # 睡半秒钟
                      driver.find_element_by_xpath('//*[@name="cjxz%s" and @value="%s"]' % (index, val)).click() # 勾选正确选项
      
          index = 0 # 记录当前正在解答的题目题号
          for item in list_03:
              if len(item) <= 1 or item[1] == '.': # 当前条目是选项而非题目"
                  continue
              index = index + 1
              cur.execute("select * from data where pro = '%s'" % item) # 查找数据库中是否存在本题答案
              for pro, res in cur.fetchall():
                  print(pro, res[5:])
                  for val in res[5:]: # 单选和判断不需要此循环,但是多选题答案不一,需要勾选所以答案
                      sleep(0.5)  # 睡半秒钟
                      driver.find_element_by_xpath('//*[@name="cjdx%s" and @value="%s"]' % (index, val)).click() # 勾选正确选项
      
    7. 提交答案

      def submit():
          global driver
          sleep(2) # 睡两秒钟
          driver.find_element_by_id("button").click() # 点击交卷按钮
          sleep(1) # 睡一秒钟
          driver.switch_to.alert.accept() # 点击弹框中的确定按钮
          sleep(2) # 睡两秒钟
          driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") # 将页面拖至底部显示成绩
          body = driver.page_source # 获取页面源码
          sleep(5) # 睡五秒钟
          return body
      
    8. 扩充题库
      数据库中的题库可能不一定全,而且题目或许会更新,遇到数据库中没有的题,那就保存下来,下一次运行的时候题库就更大了。而且提交之后顺便输出一下测试结果。

      def show_and_expand(data):
          res = etree.HTML(data)  # 解析网页源码
          conn = MySQLdb.connect(db='【数据库名】', host='【端口号】', user='【数据库用户名】', passwd='【数据库密码】', charset='utf8')  # 连接数据库
          cur = conn.cursor()  # 获取数据库游标
      
          list_01 = [i.strip(" \n") for i in res.xpath('//*[@id="submenu0"]/div[1]/table/tbody/tr/td/text()')] # 获取判断题和答案
          list_02 = [i.strip(" \n") for i in res.xpath('//*[@id="submenu1"]/div[1]/table/tbody/tr/td/text()')] # 获取单选题和答案
          list_03 = [i.strip(" \n") for i in res.xpath('//*[@id="submenu2"]/div[1]/table/tbody/tr/td/text()')] # 获取多选题和答案
      
          sql = "insert into data(pro, res) values(%s, %s)"
          val = []
      
          for i in list_01:
              if val == []: # 当前条目是题目
                  val.append(i)
              if i[:5] == "正确答案:": # 当前条目是答案
                  val.append(i)
                  print(val)
                  try:
                      cur.execute(sql, tuple(val))
                  except:
                      print("本题已存在于数据库中")
                  val.clear()
      
          for i in list_02:
              if val == []: # 当前条目是题目
                  val.append(i)
              if i[:5] == "正确答案:": # 当前条目是答案
                  val.append(i)
                  print(val)
                  try:
                      cur.execute(sql, tuple(val))
                  except:
                      print("本题已存在于数据库中")
                  val.clear()
      
          for i in list_03:
              if val == []: # 当前条目是题目
                  val.append(i)
              if i[:5] == "正确答案:": # 当前条目是答案
                  val.append(i)
                  print(val)
                  try:
                      cur.execute(sql, tuple(val))
                  except:
                      print("本题已存在于数据库中")
                  val.clear()
      
          conn.commit() # 提交数据至数据库
          cur.close() # 关闭游标
          conn.close() # 关闭连接
      
          print(res.xpath('//*[@bgcolor="#E3E3E3" and @align="center"]/text()')) # 显示考试成绩
      

    最终代码

    # -*- coding: utf-8 -*-
    from selenium import webdriver
    from selenium.webdriver.support.wait import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.common.by import By
    from time import sleep
    from lxml import etree
    import requests
    import urllib
    import base64
    import json
    import MySQLdb
    from PIL import Image
    import os
    
    driver = None
    
    def get_token():
        client_id = "【官网获取的AK】"
        client_secret = "【官网获取的SK】"
        host = 'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=' + client_id + '&client_secret=' + client_secret
        request = urllib.request.Request(host)
        request.add_header('Content-Type', 'application/json; charset=UTF-8')
        response = urllib.request.urlopen(request)
        token_content = response.read()
        if token_content:
            token_info = json.loads(token_content)
            token_key = token_info['access_token']
        return token_key
    
    def get_captcha():
        wait = WebDriverWait(driver, 100)  # 设置最长等待时间
        captcha_xpath = "//*[@id='CheckCode']/../img" # 验证码的xpath
        captcha_path = "captcha.png" # 设置验证码本地保存路径
    
        try:
            wait.until(EC.presence_of_element_located((By.XPATH, captcha_xpath)))  # 等待验证码加载完成
        except:
            return None
    
        sleep(2)  # 睡两秒钟
        captcha = driver.find_element_by_xpath(captcha_xpath).screenshot_as_png # 获取png格式的验证码
        with open(captcha_path, "wb") as file:
            file.write(captcha) # 将验证码保存到本地
    
        image = Image.open(captcha_path) # 打开保存到本地的验证码
        image.resize((800, 200), Image.ANTIALIAS).save(captcha_path) # 将验证码大小放大20倍,验证码太小百度AI接口无法识别
        with open(captcha_path, "rb") as file:
            captcha = file.read() # 读取放大后的验证码
        os.remove(captcha_path) # 删除本地保存的验证码
    
        # 调用百度AI接口破验证码
        request_url = "https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic"
        img = base64.b64encode(captcha)
        params = {"image": img}
        access_token = get_token()
        request_url = request_url + "?access_token=" + access_token
        headers = {'content-type': 'application/x-www-form-urlencoded'}
        response = requests.post(request_url, data=params, headers=headers)
        if response:
            print(response.json())
    
        # 从百度AI返回的response里提取信息并返回或者判断为识别失败
        try:
            words = response.json()["words_result"][0]['words']
            return words
        except:
            return None
    
    def login():
        global driver
        driver.get("http://xszc.zptc.edu.cn/default.asp") # 打开网站登录页
        user = '【自己的学号】' # 设置用户名
        passwd = '【自己的密码】' # 设置密码
        captcha = get_captcha() # 识别验证码
        if captcha == None:
            return False
    
        wait = WebDriverWait(driver, 100) # 设置最长等待时间
        try:
            wait.until(EC.presence_of_element_located((By.ID, "username"))) # 等待用户名输入框加载完成
            sleep(2) # 睡两秒钟
            driver.find_element_by_id("username").send_keys(user) # 向用户名输入框发送用户名
    
            wait.until(EC.presence_of_element_located((By.ID, "Password"))) # 等待密码输入框加载完成
            sleep(2) # 睡两秒钟
            driver.find_element_by_id("Password").send_keys(passwd) # 向密码输入框发送密码
    
            wait.until(EC.presence_of_element_located((By.ID, "CheckCode")))  # 等待验证码输入框加载完成
            sleep(2)  # 睡两秒钟
            driver.find_element_by_id("CheckCode").send_keys(captcha)  # 向验证码输入框发送验证码
    
            wait.until(EC.presence_of_element_located((By.CLASS_NAME, "buttonlogin1")))  # 等待登录按钮加载完成
            sleep(2)  # 睡两秒钟
            driver.find_element_by_class_name("buttonlogin1").click()  # 点击登录按钮
            sleep(1)
            if driver.current_url == "http://xszc.zptc.edu.cn/user_ChkLogin.asp":
                return False
        except:
            return False
    
    def get_pro():
        global driver
        wait = WebDriverWait(driver, 40) # 设置等待
        try:
            wait.until(EC.presence_of_element_located((By.XPATH, "/html/body/table[2]/tbody/tr/td/table/tbody/tr[1]/td[10]/a"))) # 等待练习测试区按钮加载完成
            sleep(1) # 睡一秒钟
            driver.find_element_by_xpath("/html/body/table[2]/tbody/tr/td/table/tbody/tr[1]/td[10]/a").click() # 点击进入练习测试区
    
            wait.until(EC.presence_of_element_located((By.XPATH, "/html/body/table[3]/tbody/tr/td/table[2]/tbody/tr[2]/td/a"))) # 等待时政在线测试按钮加载完成
            sleep(2) # 睡两秒钟
            driver.find_element_by_xpath("/html/body/table[3]/tbody/tr/td/table[2]/tbody/tr[2]/td/a").click() # 点击时政在线测试按钮
    
            wait.until(EC.presence_of_element_located((By.ID, "button"))) # 等待开始考试按钮加载完成
            sleep(2) # 睡两秒钟
            driver.find_element_by_id("button").click() # 点击开始考试按钮
    
            wait.until(EC.presence_of_element_located((By.XPATH, "/html/body/div/form/table/tbody/tr/td/table[2]/tbody/tr[5]/td[2]")))  # 等待试题加载完成
            body = driver.page_source # 获取页面源码
            sleep(1) # 睡一秒钟
            return body
        except:
            return None
    
    def slove_pro(data):
        global driver
        res = etree.HTML(data) # 解析网页源码
        conn = MySQLdb.connect(db='【数据库名】', host='【端口号】', user='【数据库用户名】', passwd='【数据库密码】', charset='utf8') # 连接数据库
        cur = conn.cursor() # 获取数据库游标
    
        list_01 = [i.strip(" \n") for i in res.xpath('//*[@id="submenu0"]/div[1]/table/tbody/tr/td/text()')] # 获取判断题
        list_02 = [i.strip(" \n") for i in res.xpath('//*[@id="submenu1"]/div[1]/table/tbody/tr/td/text()')] # 获取单选题
        list_03 = [i.strip(" \n") for i in res.xpath('//*[@id="submenu2"]/div[1]/table/tbody/tr/td/text()')] # 获取多选题
    
        index = 0 # 记录当前正在解答的题目题号
        for item in list_01:
            if len(item) <= 1 or item[1] == '.': # 当前条目是选项而非题目,例如:  "A. 对"
                continue
            index = index + 1
            cur.execute("select * from data where pro = '%s'" % item) # 查找数据库中是否存在本题答案
            for pro, res in cur.fetchall():
                print(pro, res[5:])
                for val in res[5:]: # 单选和判断不需要此循环,但是多选题答案不一,需要勾选所以答案
                    sleep(0.5) # 睡半秒钟
                    driver.find_element_by_xpath('//*[@name="cjpd%s" and @value="%s"]'%(index, val)).click() # 勾选正确选项
    
    
        index = 0 # 记录当前正在解答的题目题号
        for item in list_02:
            if len(item) <= 1 or item[1] == '.': # 当前条目是选项而非题目"
                continue
            index = index + 1
            cur.execute("select * from data where pro = '%s'" % item) # 查找数据库中是否存在本题答案
            for pro, res in cur.fetchall():
                print(pro, res[5:])
                for val in res[5:]: # 单选和判断不需要此循环,但是多选题答案不一,需要勾选所以答案
                    sleep(0.5)  # 睡半秒钟
                    driver.find_element_by_xpath('//*[@name="cjxz%s" and @value="%s"]' % (index, val)).click() # 勾选正确选项
    
    
        index = 0 # 记录当前正在解答的题目题号
        for item in list_03:
            if len(item) <= 1 or item[1] == '.': # 当前条目是选项而非题目"
                continue
            index = index + 1
            cur.execute("select * from data where pro = '%s'" % item) # 查找数据库中是否存在本题答案
            for pro, res in cur.fetchall():
                print(pro, res[5:])
                for val in res[5:]: # 单选和判断不需要此循环,但是多选题答案不一,需要勾选所以答案
                    sleep(0.5)  # 睡半秒钟
                    driver.find_element_by_xpath('//*[@name="cjdx%s" and @value="%s"]' % (index, val)).click() # 勾选正确选项
    
    def submit():
        global driver
        sleep(2) # 睡两秒钟
        driver.find_element_by_id("button").click() # 点击交卷按钮
        sleep(1) # 睡一秒钟
        driver.switch_to.alert.accept() # 点击弹框中的确定按钮
        sleep(2) # 睡两秒钟
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") # 将页面拖至底部显示成绩
        body = driver.page_source # 获取页面源码
        sleep(5) # 睡五秒钟
        return body
    
    def show_and_expand(data):
        res = etree.HTML(data)  # 解析网页源码
        conn = MySQLdb.connect(db='【数据库名】', host='【端口号】', user='【数据库用户名】', passwd='【数据库密码】', charset='utf8')  # 连接数据库
        cur = conn.cursor()  # 获取数据库游标
    
        list_01 = [i.strip(" \n") for i in res.xpath('//*[@id="submenu0"]/div[1]/table/tbody/tr/td/text()')] # 获取判断题和答案
        list_02 = [i.strip(" \n") for i in res.xpath('//*[@id="submenu1"]/div[1]/table/tbody/tr/td/text()')] # 获取单选题和答案
        list_03 = [i.strip(" \n") for i in res.xpath('//*[@id="submenu2"]/div[1]/table/tbody/tr/td/text()')] # 获取多选题和答案
    
        sql = "insert into data(pro, res) values(%s, %s)"
        val = []
    
        for i in list_01:
            if val == []: # 当前条目是题目
                val.append(i)
            if i[:5] == "正确答案:": # 当前条目是答案
                val.append(i)
                print(val)
                try:
                    cur.execute(sql, tuple(val))
                except:
                    print("本题已存在于数据库中")
                val.clear()
    
        for i in list_02:
            if val == []: # 当前条目是题目
                val.append(i)
            if i[:5] == "正确答案:": # 当前条目是答案
                val.append(i)
                print(val)
                try:
                    cur.execute(sql, tuple(val))
                except:
                    print("本题已存在于数据库中")
                val.clear()
    
        for i in list_03:
            if val == []: # 当前条目是题目
                val.append(i)
            if i[:5] == "正确答案:": # 当前条目是答案
                val.append(i)
                print(val)
                try:
                    cur.execute(sql, tuple(val))
                except:
                    print("本题已存在于数据库中")
                val.clear()
    
        conn.commit() # 提交数据至数据库
        cur.close() # 关闭游标
        conn.close() # 关闭连接
    
        print(res.xpath('//*[@bgcolor="#E3E3E3" and @align="center"]/text()')) # 显示考试成绩
    
    if __name__ == "__main__":
        driver = webdriver.Chrome()
        driver.maximize_window() # 浏览器窗口最大化
    
        suc = login() # 自动登录
        if suc == False:
            print("登录失败")
            driver.close()  # 关闭浏览器
            exit(0)
    
        pro = get_pro() # 获取题目页
        if pro == None:
            print("试题获取失败")
            driver.close()  # 关闭浏览器
            exit(0)
    
        slove_pro(pro) # 解析并解答数据库中记录过的题目
        res = submit() # 提交答案并获取所有题目答案
        show_and_expand(res) # 显示成绩并且扩充题库
        driver.close()  # 关闭浏览器
    

    运行效果
    链接: https://pan.baidu.com/s/1Byt04iVHKmwoPsM4UXNxjw 提取码: brk4

  • 相关阅读:
    react路由,路由配置写在哪里?
    fastadmin中自建留言板的总结(一)TP5.0的数据流程
    精通JS的调试之控制台api大全,抓住数据的本质,本质!本质!!
    react中的,invoke,evoke,dispatch,assign都是什么意思,有什么区别
    leetcode——215.数组中的第k个最大的数
    leetcode——53.最大子序和
    leetcode——441.排列硬币
    leetcode——1137.第N个斐波那契数
    leetcode——70.爬楼梯
    leetcode——509.斐波那契数
  • 原文地址:https://www.cnblogs.com/Jathon-cnblogs/p/13214506.html
Copyright © 2011-2022 走看看