继上次教程:利用python制作自动填写体温程序最详细教程来了(有后续哦)
{注意:代码已经无法运行,仅做参考}
需要完整代码的同学看目录自取,也可以加强学习,大家一起学习交流呀!
温馨提示:本代码仅供技术交流,还是要同学们自己真实填写体温。配合学校工作。将其当作一个爬虫项目来训练自己,你会得到提升。此博文纯粹为爱发电,无收益。所以希望大家多多点赞关注支持下呀!
注意:此片有双验证码识别,本篇针对验证码有时识别不成功,还设置了反复登录操作直到成功登录,这个操作在csdn上应该是空白的,不过我这里只对数字验证码进行了反复验证操作,但滑块的反复验证操作还需要你们自己照猫画老虎去操作。这样下来填报体温成功率直接100%
本篇代码确实还有不足之处,所以还请自行思考,自行改进,谢谢支持。
此篇是对于上海工程技术大学体温填报而言
文章目录
一、更新通知
-------------2021年5月2日更新
因为学校的wpn网页安全等级升高了,需要ssl证书(一个协议),其中我们可以利用自己各自账号的cookies值作为参数来符合安全请求,但是这样需要多个cookies值,假如给多人填写,这样不太方便,所以我们可以“不安全”地去填写体温,只需要添加一个参数即可。
options = webdriver.ChromeOptions() //选择模式
options.add_argument('--ignore-certificate-errors') //忽略ssl证书引起的错误
driver = webdriver.Chrome(options=options) //将参数传入
--------------5.29再次更新,忘记更新了
由于学校网页的有些更新,以前是在当前网页进行填写(一个窗口),现在填写体温的页面有detail详情页(除了主页),只要我们对体温详情页进行driver驱动操作即可,增加下面几条代码。
windows = driver.window_handles
driver.switch_to.window(windows[1])
--------------9.30再次更新
更新了确认键的xpath的元素,学校的确认键是一定要点的,不要删除了哦,好像不确认就不算填报成功.
---------------11.12更新
简单的元素更新–选择体温填报链接的元素
在最后汇总代码中,学校页面反复横跳,离谱
--------------12.06(新增验证码)
(此时距离考研仅有18天了,很紧张呀),本来不想更新,但是有很多同学私信学校填体温增加了验证码,本着学习交流的心态还是更新一下下。
--------------2022.1.25(双验证码+数字验证码反复登录–直到成功)
感觉用代码填写体温的人很多呀,不然学校不会增加滑块验证码了。其实我是自己手动填写的,因为在双验证码情况下没必要用程序了,主要交流学习经验。我觉得我的新程序亮点是使用while循环和try except来设计了循环登录操作。
二、获取数字验证码的相关操作(新增)
由于时间不容许,所以现在只能简单操作一下了,等考研结束,再来完善代码。
------主要解决两个问题,第一个就是得到验证码图片;第二个就是识别验证码图片,得到验证码数字即可。
1,首先得到验证码图片,方法是:
path = './' + get_dict['classnum'] + '.jpg' # 存放验证码路径
driver.find_element_by_xpath("//*[ @ id = 'fm1'] / div[4] / img").screenshot(path)
这是通过driver中的一个定位验证码元素,然后对该验证码图片截屏的方法,值得注意的是,该截屏方法不同于一般的桌面截屏,这个方法是对该图片元素截屏,所以就是不显示页面也可以截图,为继续多线程提供了保障。
其中的“path"是当前目录下的路径,通过字符串拼接,可以给每一个验证码图片命名为对应学号数。
2,然后就是对验证码进行辨别,这里有许多种方法,例如:通过百度api接口识别,此类识别准确率高,或者通过ocr相关模块识别,我目前用的是concr,这个模块识别率据说有90%,实践证明,确实还可以,不过显然没有百度api识别度高,由于时间有限,大家可以自行百度查找使用方法(评论区有同学也给出了相关代码),下面是我的方法:
from cnocr import CnOcr #导入模块
#创建一个识别函数
def recognize(picture_path): #参数为要识别验证码图片的路径
ocr = CnOcr()
res = ocr.ocr_for_single_line(picture_path) # 识别单行的验证码,这是比较多行验证码来说
#得到的验证码为字典类型,所以下面是将验证码从中提取出来的过程
re = tuple(res)
r = re[0]
k = [str(i) for i in r]
s2 = ''.join(k)
return s2
三、滑块验证码(新增)
解决步骤:
1.得到要滑动距离
首先获取下面两个滑块验证码图片方法:
#取滑块图片标签
img1_labei = driver.find_element_by_xpath("//*[@id='layui-layer100001']/div/div/div/div[1]/img[1]")
stop()
img2_labei = driver.find_element_by_xpath("//*[@id='layui-layer100001']/div/div/div/div[1]/img[2]")
#得到两个图片链接----在src中且为base64编码形式,这种形式可以直接解码为图片,网页中不占内存
img1_url = img1_labei.get_attribute("src")
img2_url = img2_labei.get_attribute("src")
# path1 存放滑块验证码路径
path1 = './' + get_dict['classnum'] + 'a' + '.jpg'
path2 = './' + get_dict['classnum'] + 'b' + '.jpg'
#调用解码函数
decode_base64(img1_url,path1)
decode_base64(img2_url,path2)
下面参数path1大图片背景存放路径,如:
path2小图片缺口存放路径,如:
调用函数获取滑动距离
def distance(path1,path2):
# 读入大验证码图
image = cv2.imread(path1, 0)
# 凸显缺口边缘 threshold1=100, threshold2=200为设定阈值
image0 = cv2.Canny(image, threshold1=100, threshold2=200)
#读入小滑块(可移动)图片
image1 = cv2.imread(path2)
rows, cols, chanals = image1.shape
#裁剪小滑块(可移动)图片空白区域,大概是55*55像素
min_x = 255
min_y = 255
max_x = 0
max_y = 0
for x in range(1, rows):
for y in range(1, cols):
t = set(image1[x, y])
if len(t) >= 2:
if x <= min_x:
min_x = x
elif x >= max_x:
max_x = x
if y <= min_y:
min_y = y
elif y >= max_y:
max_y = y
image2 = image1[min_x:max_x, min_y:max_y]
#同上
can2 = cv2.Canny(image2, threshold1=100, threshold2=200)
#匹配缺口位置
res = cv2.matchTemplate(image0, can2, cv2.TM_CCOEFF_NORMED)
# 寻找最优匹配 min_val, max_val, min_loc, max_loc =0.09221141040325165 0.3617416322231293 (285, 77) (254, 146)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
#max_loc为匹配最优的缺口左上角二维坐标 例如max_loc = (254,146)
x = max_loc[0]
Distance = x-6
return Distance
2.selenium方法拖动滑块
#调用函数获取距离
x = distance(path1,path2)
#获取滑块元素
slide = driver.find_element_by_xpath("//div[@class='ap-bar-ctr']")
#下面方法是托slide元素到x距离后并松开 0代表高度,这里不用上下拖动,所以为0
ActionChains(driver).drag_and_drop_by_offset(slide,x,0).perform()
四、构思过程
此篇博文主要针对具体的python代码,此次是升级版:多线程(电脑配置好的,网速ok的一次应该可以填10个以上,本人电脑不太行,不过一次6个左右没问题)填写体温,且体温取自己设定范围内的随机值(35.4,36.6)。
(完整代码在文章末尾,各位记得点点赞鼓励下孩子)
确定好目标功能后,下面就是具体实现方法步骤:
五、需要准备的模块
1,首先导入程序实现上述功能所需要的模块:
import random,time
from multiprocessing import Pool #多线程
from cnocr import CnOcr # 数字识别
from selenium import webdriver
import cv2 #验证码识别
from selenium.webdriver import ActionChains #拖动滑块
import base64 #解码
from selenium.common.exceptions import NoSuchElementException #导入selenium中的找不到定位点错误类型
2,简单介绍部分模块功能:使用time模块实现填写体温时的延时,主要是为了等待浏览器响应,否则填写时会报错,用一个函数封装。
def stop(number):
time.sleep(number)
3,利用random模块设置一个体温随机数,如下图范围为35.4,~36.6,也可以自行设置。(注意不要忘记下面的格式化format,这个主要是为了保留一位小数,uniform会返回一大串小数位)
**
def rands():
b = '{:.1f}'.format(random.uniform(35.4, 36.6))
return b
六、用selenium与体温网页交互
**
1,封装一个函数,利用selenium与体温填写网页交互:
(1)交互过程第一步首先得到填写体温的登陆首页:(注意函数的参数)
def write_tem(get_dict): #传入get_dict字典,与多线程有关,先不管
print(get_dict['classnum']+"正在自动填写体温") #get_dict['classnum']表示得到字典中的classnum:学号
driver = webdriver.Chrome() #驱动谷歌浏览器
login_url="https://webvpn.sues.edu.cn" #登陆页面网址
driver.get(login_url) #驱动器驱动登陆页面网址,进入网址
(2)进入网址后,调用前面的演示函数stop(),可以传入参数2~4秒都可以,看自己网速。
stop(4)
driver.find_element_by_id("username").click() #通过查找id的方式找到网页中**”填写用户名“**的元素
driver.find_element_by_id("username").send_keys(get_dict['classnum']) #自动填写自己的classnum学号(get_dict['classnum'])
stop(1)
driver.find_element_by_id("password").click() #再找到密码填写的元素
driver.find_element_by_id("password").send_keys(get_dict['passage']) #自动填写自己的passage:密码(get_dict['passage'])
程序填写好登陆密码和账户后,再点击登陆按钮:
stop(1)
driver.find_element_by_class_name("login_btn").click()
(3)用程序登陆账号后,再进一步的得到体温填写的页面网址,
write_url="https://webvpn.sues.edu.cn/webvpn/LjIwNC4yMTUuMjE2LjIwOS4xNzA=/LjIxOS4yMTAuMjE0LjIwNC4xNTcuMTYzLjIwOC4xNzAuMTQ4LjE2Ny4yMTQuMTUxLjE2Ny45NS4xNTEuMTQ5LjE3NC4xMDMuMTUxLjE2NA==/default/work/shgcd/jkxxcj/jkxxcj.jsp"
driver.get(write_url) #进入具体的填写页面
(4)下面即是填写温度和提交的代码,其实就两个操作(1,定位到要填写的那个网页元素,2,在定位的这个元素上**click()**点击,要填写的元素用send_keys()在元素中写入内容。)
(driver.find_element_by_xpath/find_element_by_xpath/find_element_by_id等等几种其他的方法都可以))
stop(2)
driver.find_element_by_xpath("//div[@class='input-group']/input").click()
stop(2)
driver.find_element_by_xpath("//div[@class='input-group']/input").clear()
stop(2)
driver.find_element_by_xpath("//div[@class='input-group']/input").send_keys(rands())
stop(2)
driver.find_element_by_xpath("//div[@class='form-item text-center form-actions']/button[@class='btn btn-primary obtn']").click()
stop(2)
driver.close() #填写完毕后,自动关闭浏览器页面
print(get_dict['classnum']+"体温填写完毕")
`
七、简单多线程操作
people = []
classnums = ['学号1','学号2','学号3','学号4'] #多个输入学号
passages = ['密码1','密码2','密码3','密码4',] #输入对应密码,位置不能乱放
for i in range(0,len(classnums),1):
classnum = classnums[i]
passage = passages[i]
get_dict = {
'classnum': classnum,
'passage': passage
}
people.append(get_dict) #将封装的账号信息加入到列表中
finally:调用多线程模块:
if __name__ == '__main__':
pool = Pool(processes=4)
#processes=4表示一次完成四次pool操作(一次填四个同学),可以自行设置线程个数
pool.map(write_tem, people)
#使用map方法,括号内有填写函数write_tem(实质为调用函数)
#以及people列表中的get_dict用户信息字典(作为参数传入write_tem()函数中)
pool.close()
pool.join()
八、心得:
代码就是这样,将一系列的小功能结合起来就能实现某一个比较实用的大功能,学习的时候,可以先简单了解部分,再在做大功能的时候,找到目的需求,一个个的用小的知识点构成,和建房子一样。而难的地方就是建房子的过程,怎么建才能达到目标?怎么才能利用好每一片瓦,每一块砖?…
九、更新完整代码如下(持续更新—加入双验证码识别):
抛出问题:(确实如果有模块识别错误的验证码,这时可以动动你们的大脑思考如何才能检测到验证码填写错误后继续重新登录,直到验证码识别正确,可以在评论区留下你们的想法。
解决方法:while循环+try–excpet方法成功解决)
import random,time
from multiprocessing import Pool #多线程
from cnocr import CnOcr # 数字识别
from selenium import webdriver
import cv2 #验证码识别
from selenium.webdriver import ActionChains #拖动滑块
import base64 #解码
from selenium.common.exceptions import NoSuchElementException #导入selenium中的找不到定位点错误类型
people = []
classnums = ['学号1','学号2']
passages = ['密码1','密码2']
for i in range(0, len(classnums), 1):
classnum = classnums[i]
passage = passages[i]
get_dict = {
'classnum': classnum,
'passage': passage
}
people.append(get_dict)
#滑块移动距离
def distance(path1,path2):
# 读入大验证码图
image = cv2.imread(path1, 0)
# 凸显缺口边缘 threshold1=100, threshold2=200为设定阈值
image0 = cv2.Canny(image, threshold1=100, threshold2=200)
#读入小滑块(可移动)图片
image1 = cv2.imread(path2)
rows, cols, chanals = image1.shape
#裁剪小滑块(可移动)图片空白区域,大概是55*55像素
min_x = 255
min_y = 255
max_x = 0
max_y = 0
for x in range(1, rows):
for y in range(1, cols):
t = set(image1[x, y])
if len(t) >= 2:
if x <= min_x:
min_x = x
elif x >= max_x:
max_x = x
if y <= min_y:
min_y = y
elif y >= max_y:
max_y = y
image2 = image1[min_x:max_x, min_y:max_y]
#同上
can2 = cv2.Canny(image2, threshold1=100, threshold2=200)
#匹配缺口位置
res = cv2.matchTemplate(image0, can2, cv2.TM_CCOEFF_NORMED)
# 寻找最优匹配 min_val, max_val, min_loc, max_loc =0.09221141040325165 0.3617416322231293 (285, 77) (254, 146)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
#max_loc为匹配最优的缺口左上角二维坐标 例如max_loc = (254,146)
x = max_loc[0]
Distance = x-6
return Distance
#解码
def decode_base64(src,path):
# 切割字符串,获取后面图片数据部分
image_data = src.split(',')[1]
# 解码-->二进制数据
image = base64.b64decode(image_data)
#保持图片
with open(path, 'wb') as f:
f.write(image)
# 识别
def recognize(picture_path):
ocr = CnOcr()
res = ocr.ocr_for_single_line(picture_path) # 单行即可
re = tuple(res)
r = re[0]
k = [str(i) for i in r]
s2 = ''.join(k)
return s2
#等待时间
def stop():
time.sleep(round(random.uniform(1,3)))
#正常体温
def rands():
b = '{:.1f}'.format(random.uniform(35.4, 36.6))
return b
#填写体温
def write_tem(get_dict):
options = webdriver.ChromeOptions()
options.add_argument('--ignore-certificate-errors')
driver = webdriver.Chrome(options=options)
#进入网址
login_url = "https://webvpn.sues.edu.cn/rump_frontend/nav/"
driver.get(login_url)
#下面重复登录,直到登录成功,while循环yyds
bool_flag = True
while bool_flag == True:
try:
# 填写用户名
stop()
driver.find_element_by_id("username").click()
driver.find_element_by_id("username").send_keys(get_dict['classnum'])
# 填写密码
stop()
driver.find_element_by_id("password").click()
driver.find_element_by_id("password").send_keys(get_dict['passage'])
# 设置存放验证码路径 './'表示保持为当前目录 get_dict['classnum'] 表示当前填写人学号
path = './' + get_dict['classnum'] + '.jpg' # 字符串拼接
# 截取验证码图片
driver.find_element_by_xpath("//*[@id='fm1']/div[4]/img").screenshot(path)
# 调用前面识别函数,得到验证码数字
verify_word = recognize(path)
# 填写进验证码
driver.find_element_by_id("authcode").click()
driver.find_element_by_id("authcode").send_keys(verify_word)
# 登录
stop()
driver.find_element_by_class_name("login_btn").click()
stop()
#先寻找健康信息填报链接元素
temporary_element=driver.find_element_by_xpath("//*[@id='group-4']/div[2]//p[1]")
except NoSuchElementException:
#没找到就意味者没有成功登录进去
print(get_dict['classnum']+"验证码识别错误------重新登录")
#若执行下面则继续执行while循环
bool_flag = True
continue #进行下一次while循环
else:
#执行下面就停止while循环
temporary_element.click()
bool_flag = False
#转到健康信息填报页面
stop()
windows = driver.window_handles
driver.switch_to.window(windows[1])
#点击体温一栏
stop()
driver.find_element_by_xpath("//div[@class='input-group']/input").click()
#清空填报信息,(可以省略)
stop()
driver.find_element_by_xpath("//div[@class='input-group']/input").clear()
#填写体温
stop()
driver.find_element_by_xpath("//div[@class='input-group']/input").send_keys(rands())
#提交
stop()
driver.find_element_by_id("post").click()
stop()
#取滑块图片标签
img1_labei = driver.find_element_by_xpath("//*[@id='layui-layer100001']/div/div/div/div[1]/img[1]")
stop()
img2_labei = driver.find_element_by_xpath("//*[@id='layui-layer100001']/div/div/div/div[1]/img[2]")
#得到两个图片链接----在src中且为base64编码形式,这种形式可以直接解码为图片,网页中不占内存
img1_url = img1_labei.get_attribute("src")
img2_url = img2_labei.get_attribute("src")
# path1 存放滑块验证码路径
path1 = './' + get_dict['classnum'] + 'a' + '.jpg'
path2 = './' + get_dict['classnum'] + 'b' + '.jpg'
#调用解码函数
decode_base64(img1_url,path1)
decode_base64(img2_url,path2)
#调用函数获取距离
x = distance(path1,path2)
#获取滑块元素
slide = driver.find_element_by_xpath("//div[@class='ap-bar-ctr']")
#下面方法是托slide元素到x距离后并松开 0代表高度,这里不用上下拖动,所以为0
ActionChains(driver).drag_and_drop_by_offset(slide,x,0).perform()
#确认
stop()
driver.find_element_by_xpath("//*[@id='layui-layer100003']/div[3]/a").click()
#关闭浏览器
driver.quit()
print(get_dict['classnum'] + "体温填写完")
if __name__ == '__main__':
#下面参数processes可以修改,processes=2意思是2线程
pool = Pool(processes=2)
#调用函数write_tem
pool.map(write_tem, people)
#上面函数多线程结束后关闭并加入新线程,直到参数people跑完
pool.close()
pool.join()