Selenium
-
本代码采用selenium 自动化测试模块,这里用于操作浏览器,就是通过selenium 自动化测试模块让浏览器模拟人来操作浏览器,从而实现购票。
-
本程序操作的chrom浏览器,需要提前下载浏览器驱动chromedriver.exe,浏览器驱动需与浏览器版本一致,并与抢票程序置于同一目录。
环境搭建
chrome
- 下载chromedriver.exe,在chrom浏览器里输入:chrome://version/查看浏览器版本
- 前往官网下载浏览器驱动:http://chromedriver.storage.googleapis.com/index.html,
- 下载对应用操作系统的版本:
- 如果是默认安装的谷歌浏览器的话,把这个路径加入环境变量的path:C:\Program Files\Google\Chrome\Application,接着新建一个txt文件,写入以下内容: chrome.exe --remote-debugging-port=19222 保存之后修改为.bat后缀,直接双击就可以打开浏览器(浏览器留着),打不开就是环境变量配置有问题,请排查
登录
- 12306又又又改登录方式了,之前滑动验证码的可以搞定,现在登录只能扫码和验证码了,我没招了确实... 。但是吼,上有政策,下有对策,你且看我如何实现抢票!
- 首先输入账号密码登录
- 接着输入身份证后四位,获取验证码登录
- 登录成功之后保留在这个页面(关闭其它标签页):
- 接下来让我们直接复用已打开的浏览器进行自动化抢票操作!
抢票源码赋上
# -*- coding:utf-8 -*-
# @Time : 23/04/2023 2:10 pm
# @Author: Azure
# @File: Grab_tickets.py
import os
from selenium import webdriver
import time
import datetime
from selenium.webdriver import ActionChains
from wkkey import WebKeys
def browser():
'''
全局定义浏览器驱动
:return:
'''
# os.popen('d:/chrome.bat')
# sleep(2)
# print('成功打开浏览器')
# 复用已有浏览器,方便调试
options = webdriver.ChromeOptions()
options.add_argument("--disable-blink-features")
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
options.debugger_address = '127.0.0.1:19222'
driver = webdriver.Chrome(options=options)
return driver
class GrabTickets:
def __init__(self):
self.url = 'https://www.12306.cn/index/'
self.driver = browser()
self.wk = WebKeys(self.driver)
# 用户名密码
self.username = ''
self.password = ''
def userinfo(self):
# self.driver.get(self.url)
info = input('此次运行是否需要自定义配置(ture or None): ')
trainnumber = None
if info:
fromstation = input('请输入出发地: ')
destination = input('请输入目的地: ')
date = input('请输入出发日期(例2023-04-23): ')
print('正在查看当天车次,请稍等')
else:
fromstation = '庆盛'
destination = '深圳北'
date = self.get_seventh_day()
trainnumber = 'G6505'
# self.login(self.url)
return fromstation, destination, date, trainnumber
# 现在不用滑动验证码了,芭比Q了
def login(self, url):
# 过网站检测,没加这句的话,账号密码登录时滑动验证码过不了,但二维码登录不受影响
self.driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {"source": """Object.defineProperty(navigator, 'webself.driver', {
get: () => undefined})"""})
self.driver.get(url)
# 获取并点击右上角登录按钮
self.wk.locator_wait("id", 'J-btn-login').click()
# 账号密码登录
self.wk.input("id", 'J-userName', self.username)
self.wk.input("id", 'J-password', self.password)
self.wk.locator_wait("id", 'J-login').click()
time.sleep(2)
# 过滑动验证码
picture_start = self.wk.locator_wait("id", 'nc_1_n1z')
# 移动到相应的位置,并左键鼠标按住往右边拖
ActionChains(self.driver).move_to_element(picture_start).click_and_hold(picture_start).move_by_offset(300,
0).release().perform()
def get_seventh_day(self):
try:
current_date = datetime.date.today() # 获取当前日期
seventh_day = str(current_date + datetime.timedelta(days=14)) # 当天后的第七天日期
return seventh_day
except Exception as e:
raise ValueError('日期计算错误') from e
def isElementExist(self):
ele = self.driver.find_elements("xpath", '//td[@align="center"]/a')
print('可预定车次为{}'.format(len(ele)))
if len(ele) == 0:
return False
else:
return True
def select_Train(self):
fromstation, destination, date, trainnumber = self.userinfo()
# 点击个人中心
self.wk.locator_wait("xpath", '//a[@class = "txt-primary menu-nav-my-hd"]').click()
# 点击车票预订跳转到预订车票页面
self.wk.locator_wait("xpath", '//*[@id="link_for_ticket"]').click()
# 输入出发地和目的地信息
# 出发地
self.wk.locator_wait("xpath", '//*[@id="fromStationText"]').click()
self.wk.input("xpath", '//*[@id="fromStationText"]', fromstation, False, True)
time.sleep(1)
# 目的地
self.wk.locator_wait("xpath", '//*[@id="toStationText"]').click()
self.wk.input("xpath", '//*[@id="toStationText"]', destination, False, True)
time.sleep(1)
# 出发日期
self.wk.locator_wait("xpath", '//*[@id="train_date"]').click()
self.wk.input("xpath", '//*[@id="train_date"]', date, False, True)
time.sleep(1)
# 查询
start = time.time()
while True:
self.driver.implicitly_wait(2)
# 点击查询
self.wk.locator_wait("xpath", "//*[@id='query_ticket']").click()
# 判断页面中有没有“预订”按钮,如果没有预订按钮就不断查询直到车票开售
if not self.isElementExist():
# 车票处于待开售状态
print(f"xx点xx分起售,现在是{time.strftime('%H:%M:%S', time.localtime())},还未开始售票")
# 每隔两分钟刷新一次整体页面,否则3分钟内无购票操作12306系统会自动登出
if time.time() - start >= 120:
self.driver.refresh()
start = time.time()
# 延时1秒防止过于快速地点击导致查询超时,当然偶尔还是会出现超时现象,不过超时也没关系,一般等待6秒之后就会继续自动查询
time.sleep(1)
continue
tickets = self.driver.find_elements("xpath", '//div[@class = "train"]')
all_tickets = len(tickets)
ticket_list = []
for i in range(all_tickets):
car = f'//*[@id="train_num_{i}"]'
ticket = self.driver.find_element("xpath", f'{car}/div[@class = "train"]').get_attribute("id")
head = ticket.index('G')
train = ticket[head:head + 5]
ticket_list.append(train)
train_initime = self.wk.get_el_text('xpath', f'{car}/div[3]/strong[1]')
train_endtime = self.wk.get_el_text('xpath', f'{car}/div[3]/strong[2]')
train_price = self.driver.find_element("xpath",
f'{car}/div[@class = "train"]/../../../td[4]').get_attribute(
"aria-label")
print('\033[94m' + f'车次{train_price}, 出发时间{train_initime}, 到站时间{train_endtime}' + '\033[0m')
trainnumber = input('请输入抢票车次编号(例Z111): ')
print(ticket_list)
for i in range(all_tickets):
car = f'//*[@id="train_num_{i}"]'
ticket = self.driver.find_element("xpath", f'{car}/div[@class = "train"]').get_attribute("id")
head = ticket.index('G')
train = ticket[head:head + 5]
if train == trainnumber:
self.wk.locator_wait("xpath", f'//div[@id = "train_num_{i}"]/../../td[13]/a').click()
break
# reservation = self.wk.get_el_text("xpath", f'//a[@class = "number" and text() = "{trainnumber}" ]')
# print(reservation)
# if reservation == trainnumber:
# 第一个乘车人
self.wk.locator_wait("xpath", '//*[@id="normalPassenger_0"]').click()
# 第二个乘车人
self.wk.locator_wait("xpath", '//*[@id="normalPassenger_1"]').click()
# 选择二等座
try:
self.wk.select_drop_down_box('xpath', '//select[@id = "seatType_1"]', 0)
self.wk.select_drop_down_box('xpath', '//select[@id = "seatType_2"]', 0)
except Exception as e:
print('没得二等座了' + str(e))
# 提交订单
# time.sleep(1)
self.wk.locator_wait("xpath", '//*[@id="submitOrder_id"]').click()
# 选DF靠窗
try:
self.wk.locator_wait("xpath", '//*[@id="erdeng1"]/ul[2]/li[1]/a').click()
self.wk.locator_wait("xpath", '//*[@id="erdeng1"]/ul[2]/li[2]/a').click()
except Exception as e:
print('没得DF靠窗了' + str(e))
# 定位确认订单
self.wk.locator_wait("id", 'qr_submit_id').click()
time.sleep(3)
# self.wk.locator_wait("xpath", '//a[@id = "qr_submit_id"]').click()
# try:
# self.wk.locator_wait("xpath", '//a[@id = "qr_submit_id"]').click()
# except Exception:
# pass
print(f"{trainnumber}次列车抢票成功,请尽快在10分钟内支付!")
return
if __name__ == '__main__':
grab = GrabTickets()
grab.select_Train()
抢票成功
- 下面是抢票中使用到的方法,感兴趣的测试小伙伴可以看看
ui自动化关键字封装
# -*- coding:utf-8 -*-
# @Time : 23/04/2023 4:26 pm
# @Author: Azure
# @File: wkkey.py
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.select import Select
class WebKeys:
# 构造方法,用于接收driver对象
def __init__(self, driver):
self.driver = driver
driver.maximize_window()
# 显示等待最大等待20秒,轮询判断是否成立为默认0.5
self.wait = WebDriverWait(self.driver, 15)
# 用JS显示定位的地方,方便确认定位位置
def locator_station(self, ele):
self.driver.execute_script(
"arguments[0].setAttribute('style',arguments[1]);",
ele,
"border: 2px solid blue" # 边框,green绿色
)
# 显示等待+定位
def locator_wait(self, name, value):
locator = (name, value)
# 显示等待当前元素可见
self.wait.until(ec.visibility_of_element_located(locator))
# 上面不行换下面这个,元素被加到了dom树
# self.wait.until(ec.presence_of_element_located(locator))
# 定位元素并标识
el = self.driver.find_element(name, value)
self.locator_station(el)
return el
# select列表元素选择
def select_drop_down_box(self, name, value, n):
try:
select_element = self.locator_wait(name, value)
select_object = Select(select_element)
# 如果n为str,使用选项value定位方法
if n is str:
select_object.select_by_value(n)
else:
# 如果n不为str,使用选项索引定位方法
select_object.select_by_index(n)
except Exception as e:
print("下拉框选择失败:" + str(e))
raise e
# 获取指定元素的文本
def get_el_text(self, name, value):
return self.locator_wait(name, value).text
# 定位元素并输入,默认清理再输入,可选输入后点击Enter
def input(self, name, value, txt, clear=False, enter=False):
el = self.locator_wait(name, value)
if clear == True:
# 默认不去Enter
if enter == False:
el.send_keys(txt)
else:
el.send_keys(txt + Keys.ENTER)
# 如果clear使用默认值则清理后再输入
else:
el.clear()
if enter == False:
el.send_keys(txt)
else:
el.send_keys(txt + Keys.ENTER)