摘要
前段時間,2023 賀歲紀(jì)念幣的預(yù)約火熱地進行著,當(dāng)晚我也憑借驚人的手速搶到了 3 *20 = 60 個,某天偶然打開農(nóng)行預(yù)約紀(jì)念幣網(wǎng)的站,發(fā)現(xiàn)預(yù)約端口還未關(guān)閉,便想著用 Selenium 自動化測試來實現(xiàn)全自動預(yù)約紀(jì)念幣。
經(jīng)過測試,預(yù)約 10 人的時間在 45 - 55 s 左右,速度還可以,但有些地方還可以再優(yōu)化,如加載 csv 文件獲取個人信息、使用多臺手機同時接受短信驗證碼等,上述功能可能會在以后的更新中添加。
聲明:本文只用于技術(shù)分享,禁止使用本文代碼參與各種不當(dāng)獲利行為
Part I:基本 Selenium 自動化
打開農(nóng)行紀(jì)念幣預(yù)約網(wǎng)址,進入紀(jì)念幣預(yù)約,可見布局如下:
接下來就是基本的 Selenium 自動化了,F12
打開開發(fā)者工具,查看 “ 預(yù)約 ” 的 Xpath,但通過兩次紀(jì)念幣預(yù)約,我發(fā)現(xiàn)該元素的 Xpath 是隨紀(jì)念幣更改的,故每次要提前進入該網(wǎng)址獲取本次預(yù)約的 Xpath。
將所有配置文件放在general_settings.py
方便管理
# general_settings.py
# 驅(qū)動路徑
path_chrome = Service_Chrome("../driver/chromedriver.exe")
# 預(yù)約鏈接
booking_url = "https://eapply.abchina.com/coin/Coin/CoinIssuesDistribution?typeid=202301"
# 預(yù)約界面 Xpath
welcome_page_xpath = '/html/body/div[5]/div[2]/table/tbody/tr[5]/td[4]/input[1]'
# main.py
browser = webdriver.Chrome(service=general_settings.path_chrome) # 使用 Chrome 驅(qū)動
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() # 同意并繼續(xù)
接下來,進入今天我們的主戰(zhàn)場,布局如下:
我將此頁面分為如下五個部分:
- 基本個人信息(姓名、證件號碼、手機號碼)
- 圖形驗證碼
- 短信驗證碼
- 兌換網(wǎng)點
- 兌換時間
其中,1、4、5 在本 Part 展示,2、3 將在下文展示。
1. 基本個人信息
由于本次自動化是多線程同時進行,且為了個人信息安全和后期再有紀(jì)念幣預(yù)約可以直接使用,故將個人信息放入 MySQL 數(shù)據(jù)庫中,使用 Python 第三方庫 pymysql 獲取數(shù)據(jù)庫信息并填寫。
# general_settings.py
# 數(shù)據(jù)庫信息
host = "" # 主機名(IP)
port = 3306 # 數(shù)據(jù)庫端口,默認(rèn)為 3306
user = "" # 數(shù)據(jù)庫用戶名
password = "" # 數(shù)據(jù)庫密碼
database = "" # 信息所在 database(數(shù)據(jù)庫)
table = "" # 信息所在 table(表)
# main.py
def info_get(host: str, port: int, user: str, password: str, database: str, table: str):
"""
通過 MySQL 數(shù)據(jù)庫獲取信息
:param host: 主機名(IP)
:param port: 數(shù)據(jù)庫端口
:param user: 數(shù)據(jù)庫用戶名
:param password: 數(shù)據(jù)庫密碼
:param database: 信息所在 database
:param table: 信息所在 table
:return: 信息的元組
"""
info_MySQL = Connection(
host=host,
port=port,
user=user,
password=password
) # 連接數(shù)據(jù)庫
cursor = info_MySQL.cursor()
info_MySQL.select_db(database)
cursor.execute(f'SELECT * FROM {table};')
result = cursor.fetchall() # 獲取所有信息
info_mysql = result[thread_index] # 獲取對應(yīng)進程的個人信息
cursor.close()
info_MySQL.close()
return info_mysql
# main.py
def fill_info(info: tuple):
"""
填寫信息函數(shù)
: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. 兌換網(wǎng)點
兌換網(wǎng)點是一個下拉框?qū)ο?,可以使?Selenium 中 Select 函數(shù)對網(wǎng)點進行選擇。省行、分行、支行都很順利,但營業(yè)處選項遇到了一些問題,營業(yè)處的文本為 “營業(yè)處 + 當(dāng)前剩余紀(jì)念幣數(shù)”,若使用select_by_index
會導(dǎo)致不知道默認(rèn)選擇的營業(yè)處是否還有紀(jì)念幣。
故做以下修改:先選擇默認(rèn)營業(yè)處,若默認(rèn)營業(yè)處剩余紀(jì)念幣數(shù) <= 20,則對營業(yè)處的列表進行遍歷,選擇剩余紀(jì)念幣數(shù) >= 20 的營業(yè)處,若都沒有剩余,則輸出 “ 該營業(yè)處沒有剩余紀(jì)念幣 ”。當(dāng)然,你也可以再對支行、分行甚至省行(只要你能跑)的列表進行遍歷,選擇有剩余的營業(yè)處。
# general_settings.py
# 預(yù)約地址
place_arr = ["", "", "", 4] # 分別為 [省行, 分行, 支行, 默認(rèn)營業(yè)廳序號(從 1 開始為第一個)]
# main.py
def choose_place(province: str, city: str, country: str, default_bank_index: int):
"""
選擇兌換網(wǎng)點
:param province: 省行名稱
:param city: 分行名稱
:param country: 支行名稱
:param default_bank_index: 默認(rèn)營業(yè)處序號(從 1 開始為第一個營業(yè)處)
: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"]') # 選擇營業(yè)處
bank_text = select_bank.text
bank_arr = bank_text.split("\n")
default_coin_number = bank_arr[default_bank_index].split(" ")
# 判斷該營業(yè)處是否有剩余紀(jì)念幣
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)
break
else:
print(f"進程{thread_index} 沒有營業(yè)廳有紀(jì)念幣了...")
break
3. 兌換時間
選擇時間可以通過兩次定位來實現(xiàn),但是速度較慢且 Xpath 路徑不好寫,且有時會涉及到 frame ,此時需要切換 frame,比較麻煩。所以本文使用 js 來處理時間控件,實現(xiàn)原理為刪除 input 的 readonly 屬性,直接輸入日期。
# general_settings.py
# 兌換時間
coindate = "" # 按照'年-月-日'輸入日期,例如:'2023-01-01'
# main.py
def coin_date(coindate: str):
"""
選擇兌換時間
:param coindate: 兌換時間
:return: None
"""
js_date = 'document.getElementById("coindate").removeAttribute("readonly");' # 執(zhí)行 js 代碼去除 readonly 屬性
browser.execute_script(js_date)
browser.find_element(By.ID, 'coindate').clear() # 清除輸入框
browser.find_element(By.ID, 'coindate').send_keys(coindate) # 輸入日期
至此,基本的 Selenium 自動化已經(jīng)完成。接下來,就是本文的核心:圖像驗證碼與短信驗證碼。
Part II:圖形驗證碼
1. 圖形驗證碼數(shù)據(jù)集獲取
既然選擇用深度學(xué)習(xí)識別驗證碼,首先就是獲取驗證碼數(shù)據(jù)集。
在預(yù)約界面查找元素可知驗證碼的 src,刷新后會顯示不同的圖形驗證碼,這樣圖形驗證碼的數(shù)據(jù)源就搞定了。下面就是使用 requests 庫爬取圖形驗證碼,并以二進制方式寫入到本地文件,這里一共爬取 3000 張驗證碼。
# captcha_get.py
import time
import os
import requests
url = 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)
但由于這些驗證碼之后還需要進行標(biāo)注,比較麻煩,特此將我用 2captcha 標(biāo)注好的 3000 張驗證碼貼出來,格式為 " 驗證碼_piccaptcha+hash.png "。(別問我為什么不直接用 2captcha,因為一個驗證碼要 5 s,這速度還不如直接手動輸入)
下載數(shù)據(jù)集 - Kaggle
下載數(shù)據(jù)集 - AliCloud
2. 訓(xùn)練模型
下面介紹本文采用的 CNN 模型 ocr_jasper,基于 mobildenetv2 修改而來,下圖為網(wǎng)絡(luò)結(jié)構(gòu)。
訓(xùn)練代碼在此就不詳細說明了,詳情可看倉庫中 ” ocr_jasper_train “ 內(nèi)的 README.md
。訓(xùn)練完成后,會得到 model.onnx
和 charsets.json
兩個文件,分別為模型文件和字符集文件,這兩個文件需配合 ocr_jasper 庫使用。
3. 獲取頁面中圖形驗證碼
上文爬取驗證碼時提到過,圖形驗證碼的數(shù)據(jù)源是一條鏈接,所以無法直接通過鏈接直接下載圖形驗證碼,故對圖形驗證碼的元素進行截圖并保存,方便 ocr_jasper 調(diào)用。
# main.py
def pic_captcha_save():
"""
定位驗證碼進行截圖
:return: None
"""
captcha_img = browser.find_element(By.XPATH, '//*[@id="piccaptcha"]') # 要截圖的元素
x, y = captcha_img.location.values() # 坐標(biāo)
h, w = captcha_img.size.values() # 寬高
image_data = browser.get_screenshot_as_png() # 把截圖以二進制形式的數(shù)據(jù)返回
screenshot = Image.open(BytesIO(image_data)) # 以新圖片打開返回的數(shù)據(jù)
result = screenshot.crop((x, y, x + w, y + h)) # 對截圖進行裁剪
result.save(f'./Captcha/pic_captcha_thread{thread_index}.png')
4. 使用 ocr_jasper 識別圖形驗證碼
現(xiàn)在,就可以通過調(diào)用 ocr_jasper 來對圖形驗證碼進行識別了,ocr_jasper 可以從本文的倉庫中獲取,在 CMD 或 Anaconda Prompt 中運行:
pip install {ocr_jasper} # 將 {ocr_jasper} 替換為 ocr_jasper 的相對或絕對路徑
接下來就可以在代碼中調(diào)用 ocr_jasper 了,將代碼中import_onnx_path
和charsets_path
修改為訓(xùn)練好的模型和字符集文件的相對或絕對路徑,默認(rèn)放在項目根目錄下的 Models 文件夾中。
# main.py
def 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 會抽風(fēng),無法正確識別圖形驗證碼,在此添加一個函數(shù)來判斷是否識別正確。當(dāng)識別錯誤時,id 為 errorCaptchaNo
的元素會變成 ” 圖形驗證碼錯誤 “;識別正確時,會變?yōu)?” 短信驗證碼已發(fā)送成功 “,所以可以通過該元素文本長度來判斷圖形驗證碼是否識別正確。又因為captcha_success
變量會跨函數(shù)多次調(diào)用,故將其定義為全局變量。
# main.py
def captcha():
"""
判斷圖形驗證碼是否正確
:return: None
"""
global captcha_success
while 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"]').text
if 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 = True
break
短信驗證碼與多線程并發(fā)
短信驗證碼與多線程并發(fā)內(nèi)容可見我的個人博客
- 個人博客:JasperX’s Blog
- 項目倉庫:Github
以上就是本次自動化測試預(yù)約紀(jì)念幣的所有內(nèi)容了,如果你喜歡我,歡迎關(guān)注我的 CSDN、知乎,或者在下方留下你的評論,Bye!文章來源:http://www.zghlxwxcb.cn/news/detail-685807.html
遵守協(xié)議:BY-NC-ND文章來源地址http://www.zghlxwxcb.cn/news/detail-685807.html
到了這里,關(guān)于Selenium 自動化測試之紀(jì)念幣預(yù)約的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!