Selenium

  • 本代码采用selenium 自动化测试模块,这里用于操作浏览器,就是通过selenium 自动化测试模块让浏览器模拟人来操作浏览器,从而实现购票。

  • 本程序操作的chrom浏览器,需要提前下载浏览器驱动chromedriver.exe,浏览器驱动需与浏览器版本一致,并与抢票程序置于同一目录。

环境搭建

chrome

  1. 下载chromedriver.exe,在chrom浏览器里输入:chrome://version/查看浏览器版本
  2. 前往官网下载浏览器驱动:http://chromedriver.storage.googleapis.com/index.html,
  3. 下载对应用操作系统的版本: image.png
  4. 如果是默认安装的谷歌浏览器的话,把这个路径加入环境变量的path:C:\Program Files\Google\Chrome\Application,接着新建一个txt文件,写入以下内容: chrome.exe --remote-debugging-port=19222 保存之后修改为.bat后缀,直接双击就可以打开浏览器(浏览器留着),打不开就是环境变量配置有问题,请排查

image.png

登录

  1. 12306又又又改登录方式了,之前滑动验证码的可以搞定,现在登录只能扫码和验证码了,我没招了确实... 。但是吼,上有政策,下有对策,你且看我如何实现抢票!
  • 首先输入账号密码登录
  • 接着输入身份证后四位,获取验证码登录
  • 登录成功之后保留在这个页面(关闭其它标签页): image.png

image.png

  • 接下来让我们直接复用已打开的浏览器进行自动化抢票操作!

抢票源码赋上

# -*- 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()

抢票成功

image.png

  • 下面是抢票中使用到的方法,感兴趣的测试小伙伴可以看看

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)