使用selenium实现学校时政在线测试自动答题
-
在mysql中建表
由于要将题库存到数据库中,要事先在数据库中建好数据表。我这里用了mysql数据库,也可以使用其他数据库。
建表语句参考如下CREATE TABLE `data` ( `pro` varchar(255) NOT NULL DEFAULT '', `res` varchar(10) DEFAULT NULL, PRIMARY KEY (`pro`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
主函数
主函数中就实现代码框架,大致需要实现的功能有登录、获取题目、解答题目、提交答案并扩充题库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() # 关闭浏览器
-
登录函数
用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
-
破解验证码
登录的难点在于破解验证码。一般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官网给出了的
-
获取答题页信息
登录之后就可以一路点击到答题页了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()')) # 显示考试成绩
最终代码
# -*- 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