草庐IT

28. 实战:基于selenium实现12306自动购票

Vec_Kun 2024-05-19 原文

目录

前言

目的

思路

代码实现

1. 进入登录界面,输入账号密码

2. 点击登录按钮,完成滑块验证

3. 在个人中心点击购票,跳转

4. 输入出发地、目的地,从控制台输入得到

5. 文本框输入出发日

6. 若是学生票则切换票型

7. 点击查询

8. 定位预定按钮,点击跳转购票页面

9. 选择学生乘客,并在弹窗中确认购买学生票

10. 提交订单,等待付款

完整代码

运行效果

总结


前言

我们已经学会了selenium的基本操作,并且学会了用它处理验证码、跳转网页、处理内联框架等操作,现在可以进行实战:本节选取12306火车购票作为案例,用自动化测试工具selenium实现自动访问网页并下单等待购票。

2023-01-20更新:完善了全部功能并可以完整运行


目的

手动在控制台输入乘车人(新增), 出发地、目的地、出发日、是否购买学生票,确认后自动跳转12306网站购票。


思路

1. 首先获取登陆页面的URL,随后定位账号密码的输入框,用sendkey接口输入个人信息;

2. 获取登录控件的XPATH地址,点击发现弹窗出现滑块验证,使用drag_and_drop_by_offset接口实现拖拽滑块到终点的操作;

3. 登陆以后默认在个人中心,获取购票按钮XPATH地址,点击访问;

4. 分析购票界面,点击文本框以后可以清空当前文本框,所以动作链应当为:点击 -> 输入 -> 按下回车。因为输入以后会弹出选项,所以我们还得点一下回车,直接切换其他文本框会清空;

5. 出发日文本框点击的时候不会清空,所以用clear接口清空文本框,然后输入正确的日期格式yyyy-mm-dd形式;

6. 如果是学生票,切换学生票;

7. 点击查询,拿到可预定车票列表;

8. 用显示等待定位预定按钮,点击跳转购票页面;

9. 用条件等待选择学生乘客,并在弹窗中确认购买学生票 ; 或直接选择一般乘客购票;

10. 提交订单,等待付款。


代码实现

1. 进入登录界面,输入账号密码

opt = Options()
# option.add_experimental_option('excludeSwitches', ['enable-automation'])
opt.add_argument('--disable-blink-features=AutomationControlled')
opt.add_experimental_option('detach', True)
opt.add_argument('--start-maximized')  # 浏览器窗口最大

web = Chrome(options=opt)

web.get("https://kyfw.12306.cn/otn/resources/login.html")

web.find_element(By.XPATH, '//*[@id="toolbar_Div"]/div[2]/div[2]/ul/li[1]/a').click()

time.sleep(1)

# TODO 输入用户名和密码
web.find_element(By.XPATH, '//*[@id="J-userName"]').send_keys("username")
web.find_element(By.XPATH, '//*[@id="J-password"]').send_keys("password")

2. 点击登录按钮,完成滑块验证

# 点击登录
web.find_element(By.XPATH, '//*[@id="J-login"]').click()

time.sleep(3)

# 拖拽
btn = web.find_element(By.XPATH, '//*[@id="nc_1_n1z"]')
ActionChains(web).drag_and_drop_by_offset(btn, 300, 0).perform()
time.sleep(3)

ActionChains里面有许多动作序列,可以帮助我们完成许多仿人类动作,记得在最后加perform,不然动作序列是不会执行的。

3. 在个人中心点击购票,跳转

# 车票预定
web.find_element(By.XPATH, '//*[@id="link_for_ticket"]').click()

4. 输入出发地、目的地,从控制台输入得到

从控制台获得信息:

更新 : 增加了乘车人,便于定位后续购票人

# 初始化购票信息
print("***欢迎使用自动购票系统***")
print("请依次输入购票信息...")
print("=" * 30)
fromStationText = input("请输入出发地(示例:介休东)...\n")
toStationText = input("请输入目的地(示例:成都东)...\n")
train_date = input("请输入出发日(示例:2023-01-19)...\n")
is_student = input("是否购买学生票?(y/n)\n")
print("=" * 30)
print("处理信息中...\n", "处理完毕,请检查您输入的信息...\n", fromStationText, toStationText, train_date)
confirm = input("是否确认上述信息?(y/n)\n")
if confirm == 'y':
    print("=" * 30)
    print("初始化完毕,开始运行系统...")
if confirm == 'n':
    print("=" * 30)
    print("请重新运行程序!")
    exit(1)

修改:可以精简sendkeys操作,将它们放入一行:将信息输入文本框:

# 输入信息(出发地、目的地、出发日)

# 出发地
web.find_element(By.XPATH, '//*[@id="fromStationText"]').click()
web.find_element(By.XPATH, '//*[@id="fromStationText"]').send_keys(fromStationText, Keys.ENTER)
time.sleep(1)

# 目的地
web.find_element(By.XPATH, '//*[@id="toStationText"]').click()
web.find_element(By.XPATH, '//*[@id="toStationText"]').send_keys(toStationText, Keys.ENTER)
time.sleep(1)

5. 文本框输入出发日

# 出发日
# date = web.find_element(By.XPATH, '//*[@id="train_date"]')
# ActionChains(web).drag_and_drop_by_offset(date, 175, 0).perform()
web.find_element(By.XPATH, '//*[@id="train_date"]').clear()
web.find_element(By.XPATH, '//*[@id="train_date"]').send_keys(train_date)
time.sleep(1)

6. 若是学生票则切换票型

# 点击查询
if is_student == 'y':
    web.find_element(By.XPATH, '//*[@id="sf2_label"]').click()
    time.sleep(1)

7. 点击查询

# 点击查询
web.find_element(By.XPATH, '//*[@id="query_ticket"]').click()
print("=" * 30)
print("查询完毕...")
time.sleep(1)

8. 定位预定按钮,点击跳转购票页面

定位思路有两种,一个是直接找到控件,另一个是相对查找,这里我选用第二种。由于第一种的控件地址隐藏较深,无法直接在源代码定位,藏在js里面,所以我们直接用标头的最后一项相对查找就行了。

# 点击预定
# TODO 待办:定位预订控件
get_ticket = web.find_element(By.XPATH, '//*[@id="float"]/th[16]')
ActionChains(web).move_to_element_with_offset(get_ticket, 55, 70).click().perform()

但是这样很难精准定位,不能适用所有网页,所以还是实践第一种方法:找到控件:

最终确认方法:显示等待

# 点击预定
WebDriverWait(web, 1000).until(EC.presence_of_element_located((By.XPATH, '//*[@id="queryLeftTable"]/tr')))
tr_list = web.find_elements(By.XPATH, '//*[@id="queryLeftTable"]/tr[not(@datatran)]')  # 每一列列车整行信息列表,列车号元素是tr的子元素
if not tr_list:
    print("=" * 30)
    print(f"很抱歉,按您的查询条件,当前未找到从{fromStationText}到{toStationText}的列车。")
    exit(1)
for tr in tr_list:
    train_num = tr.find_element(By.XPATH, './td[1]/div/div[1]/div/a').text  # 取出元素tr里的列车号
    # 动车二等座余票信息
    text_1 = tr.find_element(By.XPATH, "./td[4]").text
    # 火车二等座余票信息
    text_2 = tr.find_element(By.XPATH, "./td[8]").text
    if (text_1 == "有" or text_1.isdigit()) or (text_2 == "有" or text_2.isdigit()):
        # 点击预订按钮
        order_btn = tr.find_element(By.CLASS_NAME, "btn72")
        order_btn.click()
        # 等待订票页面
        WebDriverWait(web, 1000).until(EC.url_to_be('https://kyfw.12306.cn/otn/confirmPassenger/initDc'))
        print("=" * 30)
        print(train_num, "二等座有票!")
        break
    else:
        print("=" * 30)
        print(train_num, "二等座无票!")
        continue

9. 选择学生乘客,并在弹窗中确认购买学生票

修改:用expected condition(EC)选取处理弹窗事件。

# 跳转页面提交订单
# 选定乘车人
web.find_element(By.XPATH, f'//*[@id="normal_passenger_id"]/li/label[contains(text(),"{passenger}")]').click()
# 如果乘客是学生,对提示点击确定
if EC.presence_of_element_located((By.XPATH, '//div[@id="dialog_xsertcj"]')):
    web.find_element(By.ID, 'dialog_xsertcj_ok').click()
    # 提交订单
    web.find_element(By.ID, 'submitOrder_id').click()
    time.sleep(2)
else:
    # 提交订单
    web.find_element(By.ID, 'submitOrder_id').click()
    time.sleep(2)

10. 提交订单,等待付款

修改:更新了座位ID无法找到的问题,向上定位XPATH再往后确认

# 选座
print("=" * 30)
seat = input("请尽快进行选座操作([窗]A/B/C/[过道]/D/F[窗])\n")
if seat == 'A':
    web.find_element(By.XPATH, '//*[@id="erdeng1"]/ul[1]/li[1]/a').click()
if seat == 'B':
    web.find_element(By.XPATH, '//*[@id="erdeng1"]/ul[1]/li[2]/a').click()
if seat == 'C':
    web.find_element(By.XPATH, '//*[@id="erdeng1"]/ul[1]/li[3]/a').click()
if seat == 'D':
    web.find_element(By.XPATH, '//*[@id="erdeng1"]/ul[2]/li[1]/a').click()
if seat == 'F':
    web.find_element(By.XPATH, '//*[@id="erdeng1"]/ul[2]/li[2]/a').click()

# 最终确认
web.find_element(By.XPATH, '//*[@id="qr_submit_id"]').click()

print("=" * 30)
print("*"*40, "\n---***<|订单创建完成,请于10分钟内付款|>***---")
print("*"*40)


完整代码

from selenium.webdriver import Chrome
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

import time

# 初始化购票信息
print("***欢迎使用自动购票系统***")
print("请依次输入购票信息...")
print("=" * 30)
passenger = input("请输入乘车人(示例:蔡徐坤)...\n")
fromStationText = input("请输入出发地(示例:介休东)...\n")
toStationText = input("请输入目的地(示例:成都东)...\n")
train_date = input("请输入出发日(示例:2023-01-19)...\n")
is_student = input("是否购买学生票?(y/n)\n")
print("=" * 30)
print("处理信息中...\n", "处理完毕,请检查您输入的信息...\n", fromStationText, toStationText, train_date)
confirm = input("是否确认上述信息?(y/n)\n")
if confirm == 'y':
    print("=" * 30)
    print("初始化完毕,开始运行系统...")
if confirm == 'n':
    print("=" * 30)
    print("请重新运行程序!")
    exit(1)

# 2.chrome的版本大于等于88
opt = Options()
# option.add_experimental_option('excludeSwitches', ['enable-automation'])
opt.add_argument('--disable-blink-features=AutomationControlled')
opt.add_experimental_option('detach', True)
opt.add_argument('--start-maximized')  # 浏览器窗口最大

web = Chrome(options=opt)

web.get("https://kyfw.12306.cn/otn/resources/login.html")

web.find_element(By.XPATH, '//*[@id="toolbar_Div"]/div[2]/div[2]/ul/li[1]/a').click()

time.sleep(1)

# TODO 输入用户名和密码
web.find_element(By.XPATH, '//*[@id="J-userName"]').send_keys("18306825490")
web.find_element(By.XPATH, '//*[@id="J-password"]').send_keys("lk020511")

# 点击登录
web.find_element(By.XPATH, '//*[@id="J-login"]').click()

time.sleep(3)

# 拖拽
btn = web.find_element(By.XPATH, '//*[@id="nc_1_n1z"]')
ActionChains(web).drag_and_drop_by_offset(btn, 300, 0).perform()
time.sleep(3)

# 车票预定
web.find_element(By.XPATH, '//*[@id="link_for_ticket"]').click()

# 输入信息(出发地、目的地、出发日)

# 出发地
web.find_element(By.XPATH, '//*[@id="fromStationText"]').click()
web.find_element(By.XPATH, '//*[@id="fromStationText"]').send_keys(fromStationText, Keys.ENTER)
time.sleep(1)

# 目的地
web.find_element(By.XPATH, '//*[@id="toStationText"]').click()
web.find_element(By.XPATH, '//*[@id="toStationText"]').send_keys(toStationText, Keys.ENTER)
time.sleep(1)

# 出发日
web.find_element(By.XPATH, '//*[@id="train_date"]').clear()
web.find_element(By.XPATH, '//*[@id="train_date"]').send_keys(train_date)
time.sleep(1)

# 点击查询
if is_student == 'y':
    web.find_element(By.XPATH, '//*[@id="sf2_label"]').click()
    time.sleep(1)
web.find_element(By.XPATH, '//*[@id="query_ticket"]').click()
print("=" * 30)
print("查询完毕...")
time.sleep(1)

# 点击预定
WebDriverWait(web, 1000).until(EC.presence_of_element_located((By.XPATH, '//*[@id="queryLeftTable"]/tr')))
tr_list = web.find_elements(By.XPATH, '//*[@id="queryLeftTable"]/tr[not(@datatran)]')  # 每一列列车整行信息列表,列车号元素是tr的子元素
if not tr_list:
    print("=" * 30)
    print(f"很抱歉,按您的查询条件,当前未找到从{fromStationText}到{toStationText}的列车。")
    exit(1)
for tr in tr_list:
    train_num = tr.find_element(By.XPATH, './td[1]/div/div[1]/div/a').text  # 取出元素tr里的列车号
    # 动车二等座余票信息
    text_1 = tr.find_element(By.XPATH, "./td[4]").text
    # 火车二等座余票信息
    text_2 = tr.find_element(By.XPATH, "./td[8]").text
    if (text_1 == "有" or text_1.isdigit()) or (text_2 == "有" or text_2.isdigit()):
        # 点击预订按钮
        order_btn = tr.find_element(By.CLASS_NAME, "btn72")
        order_btn.click()
        # 等待订票页面
        WebDriverWait(web, 1000).until(EC.url_to_be('https://kyfw.12306.cn/otn/confirmPassenger/initDc'))
        print("=" * 30)
        print(train_num, "二等座有票!")
        break
    else:
        print("=" * 30)
        print(train_num, "二等座无票!")
        continue


# 跳转页面提交订单
# 选定乘车人
web.find_element(By.XPATH, f'//*[@id="normal_passenger_id"]/li/label[contains(text(),"{passenger}")]').click()
# 如果乘客是学生,对提示点击确定
if EC.presence_of_element_located((By.XPATH, '//div[@id="dialog_xsertcj"]')):
    web.find_element(By.ID, 'dialog_xsertcj_ok').click()
    # 提交订单
    web.find_element(By.ID, 'submitOrder_id').click()
    time.sleep(2)
else:
    # 提交订单
    web.find_element(By.ID, 'submitOrder_id').click()
    time.sleep(2)

# 选座
print("=" * 30)
seat = input("请尽快进行选座操作([窗]A/B/C/[过道]/D/F[窗])\n")
if seat == 'A':
    web.find_element(By.XPATH, '//*[@id="erdeng1"]/ul[1]/li[1]/a').click()
if seat == 'B':
    web.find_element(By.XPATH, '//*[@id="erdeng1"]/ul[1]/li[2]/a').click()
if seat == 'C':
    web.find_element(By.XPATH, '//*[@id="erdeng1"]/ul[1]/li[3]/a').click()
if seat == 'D':
    web.find_element(By.XPATH, '//*[@id="erdeng1"]/ul[2]/li[1]/a').click()
if seat == 'F':
    web.find_element(By.XPATH, '//*[@id="erdeng1"]/ul[2]/li[2]/a').click()

# 最终确认
web.find_element(By.XPATH, '//*[@id="qr_submit_id"]').click()

print("=" * 30)
print("*"*40, "\n---***<|订单创建完成,请于10分钟内付款|>***---")
print("*"*40)


运行效果

 

 

 

 

 


总结

本节是基于selenium的浏览器自动化操作的实例,较为综合,涉及的知识点也比较多,仅供小伙伴们参考学习,请勿用于其他用途!

不太明白的小伙伴可以移步我之前发布过的selenium基础简单的验证码实战

有关28. 实战:基于selenium实现12306自动购票的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  3. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  4. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  5. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  6. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  7. ruby-on-rails - 从应用程序中自定义文件夹内的命名空间自动加载 - 2

    我们目前正在为ROR3.2开发自定义cms引擎。在这个过程中,我们希望成为我们的rails应用程序中的一等公民的几个类类型起源,这意味着它们应该驻留在应用程序的app文件夹下,它是插件。目前我们有以下类型:数据源数据类型查看我在app文件夹下创建了多个目录来保存这些:应用/数据源应用/数据类型应用/View更多类型将随之而来,我有点担心应用程序文件夹被这么多目录污染。因此,我想将它们移动到一个子目录/模块中,该子目录/模块包含cms定义的所有类型。所有类都应位于MyCms命名空间内,目录布局应如下所示:应用程序/my_cms/data_source应用程序/my_cms/data_ty

  8. 「Python|Selenium|场景案例」如何定位iframe中的元素? - 2

    本文主要介绍在使用Selenium进行自动化测试或者任务时,对于使用了iframe的页面,如何定位iframe中的元素文章目录场景描述解决方案具体代码场景描述当我们在使用Selenium进行自动化测试的时候,可能会遇到一些界面或者窗体是使用HTML的iframe标签进行承载的。对于iframe中的标签,如果直接查找是无法找到的,会抛出没有找到元素的异常。比如近在咫尺的例子就是,CSDN的登录窗体就是使用的iframe,大家可以尝试通过F12开发者模式查看到的tag_name,class_name,id或者xpath来定位中的页面元素,会抛出NoSuchElementException异常。解决

  9. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

  10. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

随机推荐