1. 前言
这个项目源自坛友的求助:“自动生成核酸检测采集码”;
其需求为读取Excel里面的个人信息,输入到核酸检测预约系统里去提交,再保存生成的核酸检测采集码。
网站链接:202.102.252.250:8010/index.html
我也不是专业的,程序设计难免存在遗漏之处,有问题欢迎大家批评指正、回复讨论,希望这篇文章能给Python初学者,特别是selenium初学者提供一点帮助。
成品动图展示:
2. 设计思路
利用 pandas 库解析Excel内容;
利用 selenium 库提交数据及保存二维码。
所有用到的头文件
import pandas as pd #用于Excel的读取和解析
from selenium import webdriver #浏览器自动化
from time import sleep #延时
import sys,win32api,win32con #用于系统弹窗
3. pandas
表格样式:
a. 使用read_excel读取文件
data = pd.read_excel(excel_file) #excel_file为文件路径
b. 通过len获取Excel的行数,为后期程序循环的结束提供判断条件
Excel_len = len(data)
c. 使用data.iloc[a,b]
读取表格的指定行列(a为行数,b为列数),这儿读取的数据不包含标题行,以下面表格为例,读取“张三”使用的是print(data.iloc[0,0])
,为确保读取的数据类型准确,我在这里还使用强制类型转换将其转换成字符串,print(str(data.iloc[0,0]))
姓名 |
电话 |
身份证号 |
张三 |
10086 |
1001001000 |
在后期测试中遇到的读取手机号出现了末尾加.0的情况,但Excel中看似一切正常,我使用了[0:11]
处理数据,例如:
tel = "13813813815.0"
print(tel) #此时输出为13813813815.0
tel = tel[0:11]
'''截取字符串的第1至第11位,这个是“前闭后开”的原则,
和数组类似,字符串的第一个元素的下标为0,tel[0:11]可以
用数学中的区间[0,11)表示,包含了第0至第10这十一个元素
嗯,大概就是这样'''
print(tel) #此时输出为13813813815
通过以上的数据处理,就能一定程度上保证获取的数据就是我们想要的数据,以避免出现错误的情况。
4. selenium
我使用的是火狐浏览器,火狐与chrome相比的优势在于驱动不需要随着浏览器的升级而升级,最开始玩selenium的时候因浏览器版本问题走过很多弯路,真是一言难尽啊。
browser = webdriver.Firefox()
定义并启动浏览器,驱动文件放在python安装路径下或当前编程文件夹就能直接找到,否则需要在括号中指定驱动路径。运行完这条命令后浏览器将自动打开。
browser.set_window_size(312,1015)
设置浏览器窗口大小。那个网站为手机版网站,不能自适应电脑的分辨率,因为后期需要截图保存二维码,故需要指定窗口大小,这样看起来才和手机相似。
以上我放在程序的前面,因为启动浏览器是比较费时间了,为了提高工作效率,在程序运行前启动一次就好,后续就直接打开链接操作即可,因为是批量填写、提交和截图,那我定义一个函数,通过for或者while反复调用即可。
使用def 函数名(形参):
定义函数,以下是我定义的函数:
def Tijiao(Name,Tel,Id,Xian,Zhen,Cun,Zu,Addr):
我将所有需要的信息都定义了进来,虽然多但缺一不可,后续只需要调用这个函数就能完成一次的填写、提交和截图。
使用browser.get(Url)
打开网页,初次接触python的同学可能会在子函数中打开网页这一步遇到问题——NameError: name 'browser' is not defined
,因为前面的 browser定义是在程序中,虽然作用域是全局,但在子函数中直接去调用还是找不到的。需要在调用前,需要使用global browser
申明"browser"这个变量是全局变量,这样才能够直接使用。
打开了网站接下来就是selenium元素定位的重头戏了,这儿我不挨个详细讲了,大家可以看看这个博客的介绍 https://www.cnblogs.com/eastonliu/p/9088301.html 。
我习惯使用xpath定位,在我的编程经验中只有xpath定位能够兼容各种网站、各类元素,browser.find_element_by_xpath()
括号中填写从浏览器中复制的xpath地址,前后需要加引号,因为参数类型是字符串,以需要填写的第一项姓名为例:
inputN = browser.find_element_by_xpath("/html/body/uni-app/uni-page/uni-page-wrapper/uni-page-body/uni-view/uni-view/uni-form/span/uni-view[1]/uni-input/div/input")#这个xpath地址是直接从网页中复制的
inputN.clear()#清除网页输入框中原有的值
inputN.send_keys(Name)#将名字填写进去
Xpath复制展示:
姓名、身份证号、手机号、现居住地 这四项都能通过这个方法直接定位找到填写,问题难点在于地区选择,系统中的选项有上千种组合方式,再使用上面这中xpath定位肯定不行,通过审查元素不难发现,选项都是一个一个的文字,那是不是可以直接搜索文字,已达到找到元素的目的,在xpath定位中有个contains方法,它与text组合就能实现模糊定位文字,以选择县为例,完整参数如下:
Click = browser.find_element_by_xpath("//*[contains(text(),'" + Xian + "')]") #"Xian"是形参,通过这样的组合才能将变量带入进去
Click.click() #点击元素
同理后面的镇、村、小组选择这样写,现在就剩须知和保存提交两个按钮需要点击,通过前面的xpath定位再.click()
就能完成,以须知为例:
Iknow = browser.find_element_by_xpath("//uni-view[6]/uni-radio-group/uni-label/uni-radio/div/div")
Iknow.click()
提交完成后使用get_screenshot_as_file
保存截图,完整如下:
browser.get_screenshot_as_file('./img/'+Name+"_"+Id+".png")#括号中为路径和文件名
看似妥了,一切完美,简直不要太好......
然而这个网站会保存cookie和缓存,批量提交时问题就暴露了出来:
a.第二次打开或强制刷新网页仍位于上一次提交后的二维码页面上;
b. 地区选择仍保留了上一次的选择结果,比如这次选择的县和上次相同,那么contains(text()
将搜索到两个及以上的元素,导致不能提交。
如图:
一开始我从清除cookie和缓存出发,不过测试+查了许久的资料都不能解决。那要么就直接关闭浏览器,第二次提交再打开,但是启动浏览器非常缓慢,如果每次提交都关闭再打开显然效率就降低了,于是我从网站本身出发,提交后的网页有“返回”这个按钮,那我使用contains(text()
找到这个按钮再点击就好了,这样就解决了问题a。
对于问题b,在xpath定位中有一个针对多个元素定位的方法,即find_elements_by_xpath
,就是在"element"后面加一个"s",这样就能将所有的元素都查找出来,通过分析网页的结构,尽管有多个元素,但我需要的都是排在最后那个元素,所以以县为例,完整如下:
Click = browser.find_elements_by_xpath("//*[contains(text(),'" + Xian + "')]")
Click[len(Click)-1].click() #len方法是获取长度(数组、列表、元组等),长度-1即为最后一个元素的下标
这样我大致完成了第一版程序,我万万没想到他们那儿村民小组居然可以多达二十多组,那时候我们村也才几组,contains
方法是模糊定位,也就是只要包含就能找到,那假如想要选择的是“二组”,就能将“十二组”、“二十二组”等都匹配上,而我前面处理又多个元素又是直接取的最后一个元素,bug就这样产生了......
再一次打开网站进行分析,无意中发现选择按钮的文字都位于div
标签以内,而这也是与其他非按钮元素的区别,也就是说,只要通过定位div
标签内的文字不就能排除掉因cookie和缓存导致的干扰吗,因为“二组”和“十二组”的相似性,模糊查找也不能选用了,以选择小组为例,完整如下:
Click = browser.find_elements_by_xpath("//div[text()='"+Zu+"']")
Click[len(Click)-1].click()
至此,selenium的核心程序设计完成了。
5. 逻辑设计
程序执行过程中由于环境、路径、配置文件、网络等原因,不可避免的会出现异常,如果不做处理就会导致程序终止,并且还不知道原因,python中使用try—except
处理异常,详细了解可参考这篇文章https://www.runoob.com/python/python-exceptions.html ,我将所有程序都嵌套在try:
下,用except Exception:
处理抛出的异常,使用print(sys.exc_info()[1]))
将原始的异常提示打印出来,这样就能知道原因,以便能对症下药,整体结构如下:
try:
'''放入需要执行和捕获的程序'''
except Exception:
print('错误代码:'+str(sys.exc_info()[1]))
win32api.MessageBox(0, str(sys.exc_info()[1], '出错!',win32con.MB_OK)#弹出一个警示框
警示框详解可以参考这篇文章 https://www.cnblogs.com/microtiger/p/14812480.html
利用pands读取到文件后,我使用了while循环来执行批量提交,定义了一个变量a,用于while循环的计数,通过a的计数与3.b中的Excel_len
对比,就能知道何时终止循环结束程序。
计数变量a正好等同于表格的行数,那么str(data.iloc[a,列数])
就能循环读取,并将其赋值到实参上,完整如下:
Name = str(data.iloc[a,0])
Tel = str(data.iloc[a,1])
Tel = Tel[0:11]
Id = str(data.iloc[a,2])
Xian = str(data.iloc[a,3])
Zhen = str(data.iloc[a,4])
Cun = str(data.iloc[a,5])
Zu = str(data.iloc[a,6])
Addr = str(data.iloc[a,7])
然后将上述实参传递至selenium核心子函数进行调用,就能实现一次的提交。
在selenium自动化处理时,我的设计逻辑为:1.打开链接;2.判断是否位于提交后的界面→若是则点击返回;3.判断是否位于首页→若是则往下填写;针对于以上否的情况,则采用刷新、重新打开链接或延时一定时间再判断进行的处理。
中间为填写、选择和提交。在地区选择的部分,我也使用了一个异常处理,如果Excel中的地区没有严格按照网页中的选项填写,选择时就会导致程序异常,后面的就无法执行了,通过异常处理就能跳过这一个的填写,以便不影响大局。
根据该系统的特性,在点击提交保存按钮后立马截了一张图,如果是身份证号码有误、电话有误、已经提交保存过等原因导致无法正常提交,在点击提交保存按钮后能立马显示出来,这时候截一张图在后续查看的时候就能明确知道原因以便修改。
从提交成功再到二维码加载出来需要一定的时间,可以使用sleep()
方法进行延时等待,其单位为秒,sleep(1)
延时一秒。
延时一定时间后,通过模糊查找网页中是否包含“返回”二字,来判断是的提交成功,若成功,则再次截一张图保存(以相同名字保存截图会覆盖上一张图片)。
if (len(browser.find_elements_by_xpath("//*[contains(text(),'返回')]")) == 1 ):#判断是否添加成功
6.结语
深思熟虑之后还是不将完整源码附上了,因为该系统是当地正式运行的核酸预约系统,大家批量的去提交、测试必定会一定程度上影响系统的正常运行,核心部分已经贴出,剩下的无非就是逻辑的完善和细节的处理,相信以上方案能给初学者提供一定解决问题的思路,学习过程中遇到问题可以私信我。
c语言是我大学学习的第一个语言,然后是Java,第一次接触Python也是在大学课堂,还只有半学期课程,由于众所周知的原因课程结束我都没能明白Python是什么,课程结束后的一次偶然因素再次与Python相识,经过反复的测试+查资料完成了人生的一个Python实践程序,有了第一次的成功之后,我对Python也有了浓厚兴趣,兴趣是最好的老师,由此我展开了一系列的Python学习之旅,毕业后误打误撞来到工控领域工作,Python虽然用不上,但自学Python过程中积累的各类经验还是蛮有意义的。