Selenium 自动化测试之纪念币预约

2023-11-21 19:30

本文主要是介绍Selenium 自动化测试之纪念币预约,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

摘要

前段时间,2023 贺岁纪念币的预约火热地进行着,当晚我也凭借惊人的手速抢到了 3 *20 = 60 个,某天偶然打开农行预约纪念币网的站,发现预约端口还未关闭,便想着用 Selenium 自动化测试来实现全自动预约纪念币。
经过测试,预约 10 人的时间在 45 - 55 s 左右,速度还可以,但有些地方还可以再优化,如加载 csv 文件获取个人信息、使用多台手机同时接受短信验证码等,上述功能可能会在以后的更新中添加。

声明:本文只用于技术分享,禁止使用本文代码参与各种不当获利行为

Part I:基本 Selenium 自动化

打开农行纪念币预约网址,进入纪念币预约,可见布局如下:

welcome_page

接下来就是基本的 Selenium 自动化了,F12打开开发者工具,查看 “ 预约 ” 的 Xpath,但通过两次纪念币预约,我发现该元素的 Xpath 是随纪念币更改的,故每次要提前进入该网址获取本次预约的 Xpath。

将所有配置文件放在general_settings.py方便管理

# general_settings.py# 驱动路径
path_chrome = Service_Chrome("../driver/chromedriver.exe")# 预约链接
booking_url = "https://eapply.abchina.com/coin/Coin/CoinIssuesDistribution?typeid=202301"# 预约界面 Xpath
welcome_page_xpath = '/html/body/div[5]/div[2]/table/tbody/tr[5]/td[4]/input[1]'
# main.pybrowser = webdriver.Chrome(service=general_settings.path_chrome)  # 使用 Chrome 驱动
browser.get(general_settings.booking_url)def welcome_page():"""欢迎页面:return: None"""browser.find_element(By.XPATH, general_settings.welcome_page_xpath).click()browser.find_element(By.XPATH, '//*[@id="I128"]/button[1]').click()  # 同意并继续

接下来,进入今天我们的主战场,布局如下:

booking_main

我将此页面分为如下五个部分:

  1. 基本个人信息(姓名、证件号码、手机号码)
  2. 图形验证码
  3. 短信验证码
  4. 兑换网点
  5. 兑换时间

其中,1、4、5 在本 Part 展示,2、3 将在下文展示。

1. 基本个人信息

由于本次自动化是多线程同时进行,且为了个人信息安全和后期再有纪念币预约可以直接使用,故将个人信息放入 MySQL 数据库中,使用 Python 第三方库 pymysql 获取数据库信息并填写。

# general_settings.py# 数据库信息
host = ""  # 主机名(IP)
port = 3306  # 数据库端口,默认为 3306
user = ""  # 数据库用户名
password = ""  # 数据库密码
database = ""  # 信息所在 database(数据库)
table = ""  # 信息所在 table(表)
# main.pydef info_get(host: str, port: int, user: str, password: str, database: str, table: str):"""通过 MySQL 数据库获取信息:param host: 主机名(IP):param port: 数据库端口:param user: 数据库用户名:param password: 数据库密码:param database: 信息所在 database:param table: 信息所在 table:return: 信息的元组"""info_MySQL = Connection(host=host,port=port,user=user,password=password)  # 连接数据库cursor = info_MySQL.cursor() info_MySQL.select_db(database)  cursor.execute(f'SELECT *  FROM {table};')result = cursor.fetchall()  # 获取所有信息info_mysql = result[thread_index]  # 获取对应进程的个人信息cursor.close()info_MySQL.close()return info_mysql
# main.pydef fill_info(info: tuple):"""填写信息函数:param info: 信息元组:return: None"""browser.find_element(By.XPATH, '//*[@id="name"]').send_keys(info[1])  # 姓名browser.find_element(By.XPATH, '//*[@id="identNo"]').send_keys(info[2])  # 身份证号browser.find_element(By.XPATH, '//*[@id="mobile"]').send_keys(info[3])  # 电话号码

2. 兑换网点

兑换网点是一个下拉框对象,可以使用 Selenium 中 Select 函数对网点进行选择。省行、分行、支行都很顺利,但营业处选项遇到了一些问题,营业处的文本为 “营业处 + 当前剩余纪念币数”,若使用select_by_index会导致不知道默认选择的营业处是否还有纪念币。

problem_place

故做以下修改:先选择默认营业处,若默认营业处剩余纪念币数 <= 20,则对营业处的列表进行遍历,选择剩余纪念币数 >= 20 的营业处,若都没有剩余,则输出 “ 该营业处没有剩余纪念币 ”。当然,你也可以再对支行、分行甚至省行(只要你能跑)的列表进行遍历,选择有剩余的营业处。

# general_settings.py# 预约地址
place_arr = ["", "", "", 4]  # 分别为 [省行, 分行, 支行, 默认营业厅序号(从 1 开始为第一个)]
# main.pydef choose_place(province: str, city: str, country: str, default_bank_index: int):"""选择兑换网点:param province: 省行名称:param city: 分行名称:param country: 支行名称:param default_bank_index: 默认营业处序号(从 1 开始为第一个营业处):return: None"""select_province = browser.find_element(By.XPATH, '//*[@id="orglevel1"]')  # 选择省行Select(select_province).select_by_visible_text(province)select_city = browser.find_element(By.XPATH, '//*[@id="orglevel2"]')  # 选择分行Select(select_city).select_by_visible_text(city)select_country = browser.find_element(By.XPATH, '//*[@id="orglevel3"]')  # 选择支行Select(select_country).select_by_visible_text(country)select_bank = browser.find_element(By.XPATH, '//*[@id="orglevel4"]')  # 选择营业处bank_text = select_bank.textbank_arr = bank_text.split("\n")default_coin_number = bank_arr[default_bank_index].split(" ")# 判断该营业处是否有剩余纪念币if int(default_coin_number[1]) >= 20:Select(select_bank).select_by_index(default_bank_index)else:for bank_index in range(1, len(bank_arr)):coin_number = bank_arr[bank_index].split(" ")if int(coin_number[1]) >= 20:Select(select_bank).select_by_index(bank_index)breakelse:print(f"进程{thread_index} 没有营业厅有纪念币了...")break

3. 兑换时间

选择时间可以通过两次定位来实现,但是速度较慢且 Xpath 路径不好写,且有时会涉及到 frame ,此时需要切换 frame,比较麻烦。所以本文使用 js 来处理时间控件,实现原理为删除 input 的 readonly 属性,直接输入日期。

# general_settings.py# 兑换时间
coindate = ""  # 按照'年-月-日'输入日期,例如:'2023-01-01'
# main.pydef coin_date(coindate: str):"""选择兑换时间:param coindate: 兑换时间:return: None"""js_date = 'document.getElementById("coindate").removeAttribute("readonly");'  # 执行 js 代码去除 readonly 属性browser.execute_script(js_date)browser.find_element(By.ID, 'coindate').clear()  # 清除输入框browser.find_element(By.ID, 'coindate').send_keys(coindate)  # 输入日期

至此,基本的 Selenium 自动化已经完成。接下来,就是本文的核心:图像验证码与短信验证码。

Part II:图形验证码

1. 图形验证码数据集获取

既然选择用深度学习识别验证码,首先就是获取验证码数据集。

pic_captcha_url

在预约界面查找元素可知验证码的 src,刷新后会显示不同的图形验证码,这样图形验证码的数据源就搞定了。下面就是使用 requests 库爬取图形验证码,并以二进制方式写入到本地文件,这里一共爬取 3000 张验证码。

# captcha_get.pyimport time
import os
import requestsurl = f'https://eapply.abchina.com/coin/Helper/ValidCode.ashx'if not os.path.exists('./pic_captcha'):os.makedirs('./pic_captcha')for index in range(3000):file = f'./pic_captcha/captcha_{index}.png're = requests.get(url)with open(file, 'wb') as f:f.write(re.content)print(f'captcha_{index} finished...')time.sleep(0.1)

但由于这些验证码之后还需要进行标注,比较麻烦,特此将我用 2captcha 标注好的 3000 张验证码贴出来,格式为 " 验证码_piccaptcha+hash.png "。(别问我为什么不直接用 2captcha,因为一个验证码要 5 s,这速度还不如直接手动输入)

pic_captcha.png

下载数据集 - Kaggle

下载数据集 - AliCloud

2. 训练模型

下面介绍本文采用的 CNN 模型 ocr_jasper,基于 mobildenetv2 修改而来,下图为网络结构。

ocr_jasper_network

训练代码在此就不详细说明了,详情可看仓库中 ” ocr_jasper_train “ 内的 README.md 。训练完成后,会得到 model.onnxcharsets.json 两个文件,分别为模型文件和字符集文件,这两个文件需配合 ocr_jasper 库使用。

3. 获取页面中图形验证码

上文爬取验证码时提到过,图形验证码的数据源是一条链接,所以无法直接通过链接直接下载图形验证码,故对图形验证码的元素进行截图并保存,方便 ocr_jasper 调用。

# main.pydef pic_captcha_save():"""定位验证码进行截图:return: None"""captcha_img = browser.find_element(By.XPATH, '//*[@id="piccaptcha"]')  # 要截图的元素x, y = captcha_img.location.values()  # 坐标h, w = captcha_img.size.values()  # 宽高image_data = browser.get_screenshot_as_png()  # 把截图以二进制形式的数据返回screenshot = Image.open(BytesIO(image_data))  # 以新图片打开返回的数据result = screenshot.crop((x, y, x + w, y + h))  # 对截图进行裁剪result.save(f'./Captcha/pic_captcha_thread{thread_index}.png')

4. 使用 ocr_jasper 识别图形验证码

现在,就可以通过调用 ocr_jasper 来对图形验证码进行识别了,ocr_jasper 可以从本文的仓库中获取,在 CMD 或 Anaconda Prompt 中运行:

pip install {ocr_jasper} # 将 {ocr_jasper} 替换为 ocr_jasper 的相对或绝对路径 

接下来就可以在代码中调用 ocr_jasper 了,将代码中import_onnx_pathcharsets_path修改为训练好的模型和字符集文件的相对或绝对路径,默认放在项目根目录下的 Models 文件夹中。

# main.pydef pic_captcha_recognition():"""使用 ocr_jasper 识别图形验证码:return: None"""ocr_pic = ocr_jasper.OCR(import_onnx_path='./Models/model.onnx',charsets_path="./Models/charsets.json")with open(f'./Captcha/pic_captcha_thread{thread_index}.png', 'rb') as f:image = f.read()captcha_recognized = ocr_pic.classification(image)browser.find_element(By.XPATH, '//*[@id="piccode"]').send_keys(captcha_recognized)  # 验证码输入框def get_text_captcha():"""获取短信验证码:return: None"""browser.find_element(By.XPATH, '//*[@id="sendValidate"]').click() 

5. 判断图形验证码是否识别正确

有时 ocr 会抽风,无法正确识别图形验证码,在此添加一个函数来判断是否识别正确。当识别错误时,id 为 errorCaptchaNo的元素会变成 ” 图形验证码错误 “;识别正确时,会变为 ” 短信验证码已发送成功 “,所以可以通过该元素文本长度来判断图形验证码是否识别正确。又因为captcha_success变量会跨函数多次调用,故将其定义为全局变量。

# main.pydef captcha():"""判断图形验证码是否正确:return: None"""global captcha_successwhile True:pic_captcha_save()time.sleep(1)pic_captcha_recognition()get_text_captcha()time.sleep(0.5)is_captcha_error = browser.find_element(By.XPATH, '//*[@id="errorCaptchaNo"]').textif len(is_captcha_error) == 7:browser.find_element(By.XPATH, '//*[@id="piccaptcha"]').click()  # 重新获取验证码browser.find_element(By.XPATH, '//*[@id="piccode"]').clear()elif len(is_captcha_error) == 10:captcha_success = Truebreak

短信验证码与多线程并发

短信验证码与多线程并发内容可见我的个人博客

  • 个人博客:JasperX’s Blog
  • 项目仓库:Github

以上就是本次自动化测试预约纪念币的所有内容了,如果你喜欢我,欢迎关注我的 CSDN、知乎,或者在下方留下你的评论,Bye!

遵守协议:BY-NC-SA

这篇关于Selenium 自动化测试之纪念币预约的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/404578

相关文章

Python+FFmpeg实现视频自动化处理的完整指南

《Python+FFmpeg实现视频自动化处理的完整指南》本文总结了一套在Python中使用subprocess.run调用FFmpeg进行视频自动化处理的解决方案,涵盖了跨平台硬件加速、中间素材处理... 目录一、 跨平台硬件加速:统一接口设计1. 核心映射逻辑2. python 实现代码二、 中间素材处

Java使用Spire.Doc for Java实现Word自动化插入图片

《Java使用Spire.DocforJava实现Word自动化插入图片》在日常工作中,Word文档是不可或缺的工具,而图片作为信息传达的重要载体,其在文档中的插入与布局显得尤为关键,下面我们就来... 目录1. Spire.Doc for Java库介绍与安装2. 使用特定的环绕方式插入图片3. 在指定位

CPython与PyPy解释器架构的性能测试结果对比

《CPython与PyPy解释器架构的性能测试结果对比》Python解释器的选择对应用程序性能有着决定性影响,CPython以其稳定性和丰富的生态系统著称;而PyPy作为基于JIT(即时编译)技术的替... 目录引言python解释器架构概述CPython架构解析PyPy架构解析架构对比可视化性能基准测试测

C#借助Spire.XLS for .NET实现Excel工作表自动化样式设置

《C#借助Spire.XLSfor.NET实现Excel工作表自动化样式设置》作为C#开发者,我们经常需要处理Excel文件,本文将深入探讨如何利用C#代码,借助强大的Spire.XLSfor.N... 目录为什么需要自动化工作表样式使用 Spire.XLS for .NET 实现工作表整体样式设置样式配置

C#自动化生成PowerPoint(PPT)演示文稿

《C#自动化生成PowerPoint(PPT)演示文稿》在当今快节奏的商业环境中,演示文稿是信息传递和沟通的关键工具,下面我们就深入探讨如何利用C#和Spire.Presentationfor.NET... 目录环境准备与Spire.Presentation安装核心操作:添加与编辑幻灯片元素添加幻灯片文本操

Python实现Word文档自动化的操作大全(批量生成、模板填充与内容修改)

《Python实现Word文档自动化的操作大全(批量生成、模板填充与内容修改)》在职场中,Word文档是公认的好伙伴,但你有没有被它折磨过?批量生成合同、制作报告以及发放证书/通知等等,这些重复、低效... 目录重复性文档制作,手动填充模板,效率低下还易错1.python-docx入门:Word文档的“瑞士

5 种使用Python自动化处理PDF的实用方法介绍

《5种使用Python自动化处理PDF的实用方法介绍》自动化处理PDF文件已成为减少重复工作、提升工作效率的重要手段,本文将介绍五种实用方法,从内置工具到专业库,帮助你在Python中实现PDF任务... 目录使用内置库(os、subprocess)调用外部工具使用 PyPDF2 进行基本 PDF 操作使用

C#自动化实现检测并删除PDF文件中的空白页面

《C#自动化实现检测并删除PDF文件中的空白页面》PDF文档在日常工作和生活中扮演着重要的角色,本文将深入探讨如何使用C#编程语言,结合强大的PDF处理库,自动化地检测并删除PDF文件中的空白页面,感... 目录理解PDF空白页的定义与挑战引入Spire.PDF for .NET库核心实现:检测并删除空白页

Python实现自动化删除Word文档超链接的实用技巧

《Python实现自动化删除Word文档超链接的实用技巧》在日常工作中,我们经常需要处理各种Word文档,本文将深入探讨如何利用Python,特别是借助一个功能强大的库,高效移除Word文档中的超链接... 目录为什么需要移除Word文档超链接准备工作:环境搭建与库安装核心实现:使用python移除超链接的

使用Python实现Word文档的自动化对比方案

《使用Python实现Word文档的自动化对比方案》我们经常需要比较两个Word文档的版本差异,无论是合同修订、论文修改还是代码文档更新,人工比对不仅效率低下,还容易遗漏关键改动,下面通过一个实际案例... 目录引言一、使用python-docx库解析文档结构二、使用difflib进行差异比对三、高级对比方