【免费12306自动抢票】Python实现12306的定时查票、抢票操作和GUI展示(支持选车、选座和多人)

作者声明:

本程序并不鼓励各位读者违反12306平台的使用条款及相关法律规定!
本文章仅作为学习交流和提供思路使用!     
相关重要程序已对部分代码进行模糊处理!
再次声明,请务必遵守相关法律规定,以确保正常的购票秩序!

1、代码运行展示: 

2、需要使用到的外部库:

我们使用selenium来操作页面,用requests来获取数据,用tkinter来构建GUI可视化页面。
import tkinter as tk
from tkinter import ttk, messagebox
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
import json
import requests
from prettytable import PrettyTable
import threading
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.chrome.service import Service
import schedule
from datetime import datetime, time as datetime_time

一、具体的问题分析:

1、根据对url地址的分析可以明确:我们需要知道站点中文名及其英文缩写,所以我们需要一个存储全国站点信息的文件:

下面只有部分内容!这个文件有需要可以访问百度网盘:

提取码: r385icon-default.png?t=N7T8http:// https://pan.baidu.com/s/14rhypDniTVTlz7zj8Rq1bg?pwd=r385

2、通过观察和初步实验,我们很容易发现:当访问下方url地址时,输入相关购票信息后只需登录一次即可提交订单(里面需要拼接站点信息和时间信息):

"https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc&fs={},{}&ts={},{}&date={}&flag=N,N,Y"

 2、获取车次信息我们需要访问下面的url地址(里面仍然需要拼接站点信息和时间信息):

url = f'https://kyfw.12306.cn/otn/leftTicket/queryG?leftTicketDTO.train_date={start_date}&leftTicketDTO.from_station={city_data[start_city]}&leftTicketDTO.to_station={city_data[arrive_city]}&purpose_codes=ADULT'

3、在输入站点、时间信息后进入开发者模式(F12)查看网页源码,不难发现每一趟车次信息里面都存在一个btn72(预定)按钮,但是使用xpath和css定位都会出错,因此我们很难定位:

4、通过观察我们可以发现,每一个车次信息里面都有一个固定的id值,并且只需要在车次信息里面拼接即可得到:

二、代码实现步骤:

1、使用requests发送请求获取车次信息:

def traininformation():
  
    # 获取车次信息
    start_city = input("请输入始发站")
    arrive_city = input("请输入终点站")
    start_date = input("请输入出发日期2024-05-01格式")
    # 拼接url地址:
    url = f'https://kyfw.12306.cn/otn/leftTicket/queryG?leftTicketDTO.train_date={start_date}&leftTicketDTO.from_station={city_data[start_city]}&leftTicketDTO.to_station={city_data[arrive_city]}&purpose_codes=ADULT'
    print(url)
    headers = {
        #这里输入自己的cookie和user-agent即可
        'Cookie': '',
        'User-Agent':'' ,
    }
    try:
        res = requests.get(url, headers=headers)
        # 检查请求是否成功
        res.raise_for_status()
        res_json = res.json()
        result = res_json['data']['result']
    except (requests.RequestException, KeyError):
        messagebox.showerror("错误", "获取车次信息失败")
        print("获取车次信息失败,请检查网络或URL地址!")
        return

执行程序后会得到以下内容:

注意:这里不是乱码!车次信息都在里面,只是被"|"隔开了。

2、对车次信息进行解析并输出重要内容(包括每趟车次里用于定位btn72的元素):

 # 构造车次信息表格
    # 构造储存 “重要信息” 的字典
    car_info_dict = {}
    # ”重要信息“有3个,是夹在车次信息中的用于定位不同车次“预定”按钮的ID元素,需要用重要信息拼接
    header = ["序  号", "车  次", "出发时间 ", "到达时间 ", "历  时", "二等座", "一等座", "无   座", "商务座", "硬卧", "软卧", "硬座"]
    # 用于在控制台输出表格
    table = PrettyTable(header)
    for num, i in enumerate(result):
        # 车次信息被用'|'符号分割,循环提取关键信息
        index = i.split('|')
        # 重要信息1
        car_important_masage1 = index[2]
        # 列车号码
        num_car = index[3]
        # 列车出发时间
        time_start = index[8]
        # 列车到达时间
        time_arrive = index[9]
        # 历时
        time_all = index[10]
        # 重要信息2
        car_important_masage2 = index[16]
        # 重要信息3
        car_important_masage3 = index[17]
        # 二等座
        seat_2 = index[30]
        # 一等座
        seat_1 = index[31]
        # 商务座
        seat_business = index[32]
        # 无座
        seat_standup = index[26]
        # 硬卧
        sleep_hard = index[28]
        # 软卧
        sleep_soft = index[23]
        # 硬座
        seat_hard = index[29]
        # 添加到表格
        table.add_row([num + 1, num_car, time_start, time_arrive, time_all, seat_2, seat_1, seat_standup, seat_business,
                       sleep_hard, sleep_soft, seat_hard])

        # 将重要信息保存到字典
        car_info_dict[num + 1] = [num_car,car_important_masage1, car_important_masage2, car_important_masage3]

    # 输出表格
    print(table)

3、拼接重要信息组成id:

 # 如果找到了对应的列表,则输出值
    if car_info_list:
        print("值1:", car_info_list[0])
        print("值2:", car_info_list[1])
        # 重要信息1
        important_masage1=car_info_list[1]
        print("值3:", car_info_list[2])
        # 重要信息2
        important_masage2 = car_info_list[2]
        print("值4:", car_info_list[3])
        # 重要信息3
        important_masage3 = car_info_list[3]

        # 拼接重要信息,用于后边定位不同车次的预定按钮
        id_value = "ticket_" + important_masage1 + "_" + important_masage2 + "_" + important_masage3
        print(id_value)
    else:
        print("找不到对应的值")
        messagebox.showerror("错误", "匹配不到重要信息!")

4、根据输入的信息查询车票:

# 预定车票操作
def booktickets(id_value):
    global error_flag,browser
    # 判断,如果用户输入无误
    if not error_flag:
        # 设置订票网页url模板
        url_template = "https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc&fs={},{}&ts={},{}&date={}&flag=N,N,Y"
        # 根据对该网页url信息的解读,fs={},{}&ts={},{}&date={},{}分别为始发站中文名、始发站缩写代码、终点站中文名、终点站缩写代码、发车当天日期
        # 始发站中文名
        a = start_city_entry.get()
        # 始发站缩写代码
        a1 = city_data[a]
        # 终点站中文名
        b = arrive_city_entry.get()
        # 终点站缩写代码
        b1 = city_data[b]
        # 发车当天日期
        c = start_date_entry.get()
        # 将a、a1、b、b1、c按照顺序对应插入到url中,形成url地址
        order_url = url_template.format(a, a1, b, b1, c)
        # 输出订票的URL地址
        print(order_url)
        service = Service('C:\chromedriver-win64\chromedriver.exe')
        # 利用浏览器驱动打开Chome浏览器
        browser = webdriver.Chrome(service=service)
        # 访问订票网页
        browser.get(order_url)
        # 页面最大化
        browser.maximize_window()
        # 输入出发站信息
        browser.find_element(By.ID, 'fromStationText').clear()
        browser.find_element(By.ID, 'fromStationText').send_keys(a)
        # 输入终点站信息
        browser.find_element(By.ID, 'toStationText').clear()
        browser.find_element(By.ID, 'toStationText').send_keys(b)
        # 输入日期
        browser.find_element(By.ID, 'train_date').clear()
        browser.find_element(By.ID, 'train_date').send_keys(c)

        # 选择是否购买学生票
        if "是" in identity_var.get():
            student_tickt = browser.find_element(By.ID, 'sf2_label')
            student_tickt.click()
            pass

5、定时启动点击预定按钮:

def do_click_btn72(id_value):
    global task_executed

    target_time_str = target_time_str_entry.get()
    print("您选择的启动时间是:")
    task_executed = False
    # 定时任务执行
    schedule.every().day.at(target_time_str).do(lambda: timed_task(id_value))
    # 主循环,等待定时任务触发
    while True:
        schedule.run_pending()
        time.sleep(1)  # 每1秒检查一次

# 定义定时任务
def timed_task(id_value):
    global task_executed,browser
    if not task_executed:
        WebDriverWait(browser, 5, 0.1).until(
            EC.visibility_of_element_located((By.ID, 'query_ticket'))
        )
        browser.find_element(By.ID, 'query_ticket').click()
        time.sleep(2)
        # 通过ID、class定位,获取用户输入的车次序号对应的预定按钮的位置并点击预定按钮
        id_querylefttable = browser.find_element(By.ID, 'queryLeftTable')
        # 此处的id_value就是用重要信息拼接而成的
        try:
            book_btn_id = id_querylefttable.find_element(By.ID, id_value)
            time.sleep(1)
            WebDriverWait(browser, 20,0.1).until(
                EC.visibility_of_element_located((By.CLASS_NAME, 'btn72'))
            )
            book_btn_id.find_element(By.CLASS_NAME, 'btn72').click()
        except:
            messagebox.showinfo("提示", "您输入车次车票已售完,请重试!")

6、选择登录方式:

def select_login_way(browser):
    if login_way_var.get() == "扫码登录":
        # 添加一个独立线程,防止GUI界面无响应
        thread = threading.Thread(target=scan_login, args=(browser,))
        time.sleep(1)
        thread.start()
        # 调用扫码登陆操作的函数
        scan_login(browser)
    elif login_way_var.get() == "密码登录":
        # 调用密码登录操作的函数
        login_with_credentials(browser)

# 扫码登陆操作
def scan_login(browser):
    browser.find_element(By.XPATH, '//*[@id="login"]/div[2]/ul/li[2]/a').click()
    # 最小化窗口方便扫码
    root.iconify()
    # 调用选择乘客的函数
    choose_passengers(browser)

# 密码登录操作
def login_with_credentials(browser):
    wait = WebDriverWait(browser, 10)
    wait.until(EC.visibility_of_element_located((By.ID, 'J-userName')))
    # 输入账号
    browser.find_element(By.ID, 'J-userName').click()
    browser.find_element(By.ID, 'J-userName').send_keys(zhanghao_entry.get())
    time.sleep(1)
    # 输入密码
    browser.find_element(By.ID, 'J-password').click()
    browser.find_element(By.ID, 'J-password').send_keys(password_user_entry.get())
    time.sleep(1)
    # 点击登录按钮
    browser.find_element(By.ID, 'J-login').click()
    time.sleep(1)
    # 输入身份证后四位
    browser.find_element(By.ID, 'id_card').click()
    browser.find_element(By.ID, 'id_card').send_keys(id_user_4_entry.get())
    # 点击获取验证码按钮
    browser.find_element(By.ID, 'verification_code').click()
    # messagebox.showinfo("提示", "请输入短信验证码并提交")
    print("请输入短信验证码并提交")
    # 最小化窗口方便输入验证码
    root.iconify()
    browser.find_element(By.ID, 'code').click()  # 验证码输入框
    #手动输入
    browser.implicitly_wait(20)
    #自动输入
    # browser.find_element(By.ID, 'code').send_keys(yzm_entry.get())
    # browser.find_element(By.ID, 'sureClick').click()

7、选择乘客:

# 选择乘客
def choose_passengers(browser):
    global serial_numbers
    #判断用户输入是否出现错误
    if not error_flag:
        time.sleep(1)
        #等待网页加载,3分钟扫码时间
        WebDriverWait(browser, 180,0.1).until(
            EC.visibility_of_element_located((By.ID, 'normalPassenger_0'))
        )
        for num in serial_numbers:
            if num == 1:
                browser.find_element(By.ID, 'normalPassenger_0').click()
            elif num == 2:
                browser.find_element(By.ID, 'normalPassenger_1').click()
            elif num == 3:
                browser.find_element(By.ID, 'normalPassenger_2').click()
            elif num == 4:
                browser.find_element(By.ID, 'normalPassenger_3').click()
            elif num == 5:
                browser.find_element(By.ID, 'normalPassenger_4').click()
            elif num == 6:
                browser.find_element(By.ID, 'normalPassenger_5').click()
            pass
    # 调用提交订单函数
    submit_order(browser)

8、提交订单:

#提交订单函数
def submit_order(browser):
    global seat_number
    # 点击提交订单按钮
    browser.find_element(By.ID, '这里模糊处理了').click()

    # 调用选择座位函数
    show_selected(browser,serial_numbers)

9、选择座位:

# 选择座位函数
def choose_seatclass(browser,serial_numbers):
    global div_element,seat_numbers,div_elements
    # 获取座位类型和乘车人数量
    seat_class = seat_class_var.get()
    serial_numbers = len(serial_numbers)

    # 根据座位类型选择不同的下拉框选项
    if seat_class == "二等座":
        if serial_numbers == 1:
            div_elements = [browser.find_element(By.ID, '这里模糊处理了')]
        elif 1 < serial_numbers <= 5:
            div_elements = [browser.find_element(By.ID, '这里模糊处理了'), browser.find_element(By.ID, '这里模糊处理了')]
        else:
            print("错误: 此座位等级无效!")
    elif seat_class == "一等座":
        select_element = Select(browser.find_element(By.ID, "这里模糊处理了"))
        select_element.select_by_value("M")
        if serial_numbers == 1:
            div_elements = [browser.find_element(By.ID, '这里模糊处理了')]
        elif 1 < serial_numbers <= 5:
            div_elements = [browser.find_element(By.ID, '这里模糊处理了'), browser.find_element(By.ID, '这里模糊处理了')]
        else:
            print("错误: 此座位等级无效!")
    elif seat_class == "商务座":
        select_element = Select(browser.find_element(By.ID, "这里模糊处理了"))
        select_element.select_by_value("P")
        if serial_numbers == 1:
            div_elements = [browser.find_element(By.ID, '这里模糊处理了')]
        elif 1 < serial_numbers <= 5:
            div_elements = [browser.find_element(By.ID, '这里模糊处理了'), browser.find_element(By.ID, '这里模糊处理了')]
        else:
            print("错误: 此座位等级无效!")
    else:
        print("错误: 此座位等级无效! ")

    # 遍历每个座位号,并依次点击
    time.sleep(1)
    for seat_number in seat_numbers:
        # 确保座位号不为空
        if seat_number.strip():
            # 遍历每个 div 元素并尝试点击相应的座位
            for div_element in div_elements:
                try:
                    div_element.find_element(By.ID, seat_number.strip()).click()
                    break  # 点击成功后跳出循环
                except NoSuchElementException:
                    # 如果在当前 div 元素中找不到对应的座位号,则尝试下一个 div 元素
                    continue
        else:
            print("座位号为空!")
    makesure_order(browser)

10、确认订单:

def makesure_order(browser):
    global task_executed
    # # 确认订单
    time.sleep(2)
    # browser.find_element(By.ID, 'qr_submit_id').click()
    print("订票成功,请在手机app【待支付】页面支付")
    # 等待2秒钟,确保页面已加载完毕
    # messagebox.showinfo("--温馨提示--", "订票成功!请在手机app【待支付】页面支付")
    time.sleep(10)
    # 标记任务已执行
    task_executed = True
    # 清除定时任务,确保只执行一次
    schedule.clear()

    browser.quit()

三、完整代码(加入了GUI窗口):

部分内容标明“这里模糊处理了”
import tkinter as tk
from tkinter import ttk, messagebox
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
import json
import requests
from prettytable import PrettyTable
import threading
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.chrome.service import Service
import schedule
from datetime import datetime, time as datetime_time
#声明:此代码仅用于交流学习。

# 设置全局变量,方便函数调用参数
task_executed = False
# 存储城市数据
city_data = {}
# 出行人序号错误标志
error_flag = False
# 验证码
yzm_value = None


# 定义函数来执行用户输入判断
def check_input():
    global city_data, error_flag,serial_numbers
    # 从输入框中获取用户输入的信息
    # 输入起点站
    start_city = start_city_entry.get()
    # 输入终点站
    arrive_city = arrive_city_entry.get()
    # 选择是否为学生身份
    identity_var.get()
    # 输入出行人序号(12306乘车人的排序)
    serial_people_entry.get()
    # 输入出发时间
    start_date = start_date_entry.get()
    # 选择座位等级(二等、一等、商务)
    seat_class_var.get()
    # 输入账号信息
    zhanghao_entry.get()
    # 输入密码信息
    password_user_entry.get()
    # 输入身份证尾号(后四位)信息
    id_user_4_entry.get()
    # 输入短信验证码信息
    # yzm_entry.get()
    # 选择车次信息
    choose_car_num_entry.get()

    # 检查用户输入(城市信息是否在本地json文件中)
    if start_city == "" or arrive_city == "" or start_date == "":
        messagebox.showerror("错误", "请输入站点信息!")
        print("您没有输入站点信息!")
        return
    # 查询城市数据
    try:
        with open('city.json', encoding='utf-8') as f:
            city_data = json.load(f)
    except FileNotFoundError:
        messagebox.showerror("错误", "找不到城市数据文件 'city.json'!")
        print("找不到城市数据文件 'city.json'!")
        return
    # 检查出发城市和目的城市是否在城市数据中
    if start_city not in city_data or arrive_city not in city_data:
        messagebox.showerror("错误", "请输入有效的出发城市和目的城市!")
        print("请输入有效的出发城市和目的城市!")
        return

    # 调用获取车次信息并输出到窗口函数
    traininformation()

# 获取车次信息并输出到窗口
def traininformation():
    global car_info_dict
    # 获取车次信息
    start_city = start_city_entry.get()
    arrive_city = arrive_city_entry.get()
    start_date = start_date_entry.get()
    # 拼接url地址:
    url = f'https://kyfw.12306.cn/otn/leftTicket/queryG?leftTicketDTO.train_date={start_date}&leftTicketDTO.from_station={city_data[start_city]}&leftTicketDTO.to_station={city_data[arrive_city]}&purpose_codes=ADULT'
    print(url)
    headers = {
        'Cookie': '',
        'User-Agent': '',
    }
    try:
        res = requests.get(url, headers=headers)
        # 检查请求是否成功
        res.raise_for_status()
        res_json = res.json()
        result = res_json['data']['result']
    except (requests.RequestException, KeyError):
        messagebox.showerror("错误", "获取车次信息失败")
        print("获取车次信息失败,请检查网络或URL地址!")
        return

    # 构造车次信息表格
    # 构造储存 “重要信息” 的字典
    car_info_dict = {}
    # ”重要信息“有3个,是夹在车次信息中的用于定位不同车次“预定”按钮的ID元素,需要用重要信息拼接
    header = ["序  号", "车  次", "出发时间 ", "到达时间 ", "历  时", "二等座", "一等座", "无   座", "商务座", "硬卧", "软卧", "硬座"]
    # 用于在控制台输出表格
    table = PrettyTable(header)
    for num, i in enumerate(result):
        # 车次信息被用'|'符号分割,循环提取关键信息
        index = i.split('|')
        # 重要信息1
        car_important_masage1 = index[2]
        # 列车号码
        num_car = index[3]
        # 列车出发时间
        time_start = index[8]
        # 列车到达时间
        time_arrive = index[9]
        # 历时
        time_all = index[10]
        # 重要信息2
        car_important_masage2 = index[16]
        # 重要信息3
        car_important_masage3 = index[17]
        # 二等座
        seat_2 = index[30]
        # 一等座
        seat_1 = index[31]
        # 商务座
        seat_business = index[32]
        # 无座
        seat_standup = index[26]
        # 硬卧
        sleep_hard = index[28]
        # 软卧
        sleep_soft = index[23]
        # 硬座
        seat_hard = index[29]
        # 添加到表格
        table.add_row([num + 1, num_car, time_start, time_arrive, time_all, seat_2, seat_1, seat_standup, seat_business,
                       sleep_hard, sleep_soft, seat_hard])

        # 将重要信息保存到字典
        car_info_dict[num + 1] = [num_car,car_important_masage1, car_important_masage2, car_important_masage3]

    # 输出表格
    print(table)
    # 清空文本框
    result_text.delete("1.0", tk.END)
    # 插入新的车次信息
    result_text.insert(tk.END, table.get_string())
    # 更新GUI页面主窗口
    root.update()

    # 输出储存重要信息的字典
    # print(car_info_dict)


#根据截取的重要信息拼接后选择车次:
def choose_car():
    global id_value,car_info_dict,serial_numbers,error_flag

    serial_people=serial_people_entry.get()
    # 检查出行人序号是否合法(只能输入1~5之间的任意数字组合,最多支持5人同时订票,数字不能重复)
    serial_numbers = [int(num) for num in serial_people.strip()]
    try:
        if not serial_numbers:
            raise ValueError
        if any(num <= 0 or num > 6 for num in serial_numbers):
            raise ValueError
        if len(serial_numbers) != len(set(serial_numbers)):
            raise ValueError
    except ValueError:
        messagebox.showerror("错误", "请输入不重复的出行人序号,且每个序号应为1到6之间的整数")
        error_flag = True
        return
    # 接收用户输入的数字
    num_input =int(choose_car_num_entry.get())
    # 使用输入的数字作为索引来获取对应的列表
    car_info_list = car_info_dict.get(num_input)

    # 如果找到了对应的列表,则输出值
    if car_info_list:
        print("值1:", car_info_list[0])
        print("值2:", car_info_list[1])
        # 重要信息1
        important_masage1=car_info_list[1]
        print("值3:", car_info_list[2])
        # 重要信息2
        important_masage2 = car_info_list[2]
        print("值4:", car_info_list[3])
        # 重要信息3
        important_masage3 = car_info_list[3]

        # 拼接重要信息,用于后边定位不同车次的预定按钮
        id_value = "ticket_" + important_masage1 + "_" + important_masage2 + "_" + important_masage3
        print(id_value)
    else:
        print("找不到对应的值")
        messagebox.showerror("错误", "匹配不到重要信息!")

    # 调用预定车票函数
    booktickets(id_value)

# 预定车票操作
def booktickets(id_value):
    global error_flag,browser
    # 判断,如果用户输入无误
    if not error_flag:
        # 设置订票网页url模板
        url_template = "https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc&fs={},{}&ts={},{}&date={}&flag=N,N,Y"
        # 根据对该网页url信息的解读,fs={},{}&ts={},{}&date={},{}分别为始发站中文名、始发站缩写代码、终点站中文名、终点站缩写代码、发车当天日期
        # 始发站中文名
        a = start_city_entry.get()
        # 始发站缩写代码
        a1 = city_data[a]
        # 终点站中文名
        b = arrive_city_entry.get()
        # 终点站缩写代码
        b1 = city_data[b]
        # 发车当天日期
        c = start_date_entry.get()
        # 将a、a1、b、b1、c按照顺序对应插入到url中,形成url地址
        order_url = url_template.format(a, a1, b, b1, c)
        # 输出订票的URL地址
        print(order_url)
        service = Service('C:\chromedriver-win64\chromedriver.exe')
        # 利用浏览器驱动打开Chome浏览器
        browser = webdriver.Chrome(service=service)
        # 访问订票网页
        browser.get(order_url)
        # 页面最大化
        browser.maximize_window()
        # 输入出发站信息
        browser.find_element(By.ID, 'fromStationText').clear()
        browser.find_element(By.ID, 'fromStationText').send_keys(a)
        # 输入终点站信息
        browser.find_element(By.ID, 'toStationText').clear()
        browser.find_element(By.ID, 'toStationText').send_keys(b)
        # 输入日期
        browser.find_element(By.ID, 'train_date').clear()
        browser.find_element(By.ID, 'train_date').send_keys(c)

        # 选择是否购买学生票
        if "是" in identity_var.get():
            student_tickt = browser.find_element(By.ID, 'sf2_label')
            student_tickt.click()
            pass

        do_click_btn72(id_value)



def do_click_btn72(id_value):
    global task_executed

    target_time_str = target_time_str_entry.get()
    print("您选择的启动时间是:")
    task_executed = False
    # 定时任务执行
    schedule.every().day.at(target_time_str).do(lambda: timed_task(id_value))
    # 主循环,等待定时任务触发
    while True:
        schedule.run_pending()
        time.sleep(1)  # 每1秒检查一次

# 定义定时任务
def timed_task(id_value):
    global task_executed,browser
    if not task_executed:
        WebDriverWait(browser, 5, 0.1).until(
            EC.visibility_of_element_located((By.ID, 'query_ticket'))
        )
        browser.find_element(By.ID, 'query_ticket').click()
        time.sleep(2)
        # 通过ID、class定位,获取用户输入的车次序号对应的预定按钮的位置并点击预定按钮
        id_querylefttable = browser.find_element(By.ID, 'queryLeftTable')
        # 此处的id_value就是用重要信息拼接而成的
        try:
            book_btn_id = id_querylefttable.find_element(By.ID, id_value)
            time.sleep(1)
            WebDriverWait(browser, 20,0.1).until(
                EC.visibility_of_element_located((By.CLASS_NAME, 'btn72'))
            )
            book_btn_id.find_element(By.CLASS_NAME, 'btn72').click()
        except:
            messagebox.showinfo("提示", "您输入车次车票已售完,请重试!")
        # 调用选择登录方式函数
        select_login_way(browser)


def select_login_way(browser):
    if login_way_var.get() == "扫码登录":
        # 添加一个独立线程,防止GUI界面无响应
        thread = threading.Thread(target=scan_login, args=(browser,))
        time.sleep(1)
        thread.start()
        # 调用扫码登陆操作的函数
        scan_login(browser)
    elif login_way_var.get() == "密码登录":
        # 调用密码登录操作的函数
        login_with_credentials(browser)

# 扫码登陆操作
def scan_login(browser):
    browser.find_element(By.XPATH, '//*[@id="login"]/div[2]/ul/li[2]/a').click()
    # 最小化窗口方便扫码
    root.iconify()
    # 调用选择乘客的函数
    choose_passengers(browser)

# 密码登录操作
def login_with_credentials(browser):
    wait = WebDriverWait(browser, 10)
    wait.until(EC.visibility_of_element_located((By.ID, 'J-userName')))
    # 输入账号
    browser.find_element(By.ID, 'J-userName').click()
    browser.find_element(By.ID, 'J-userName').send_keys(zhanghao_entry.get())
    time.sleep(1)
    # 输入密码
    browser.find_element(By.ID, 'J-password').click()
    browser.find_element(By.ID, 'J-password').send_keys(password_user_entry.get())
    time.sleep(1)
    # 点击登录按钮
    browser.find_element(By.ID, 'J-login').click()
    time.sleep(1)
    # 输入身份证后四位
    browser.find_element(By.ID, 'id_card').click()
    browser.find_element(By.ID, 'id_card').send_keys(id_user_4_entry.get())
    # 点击获取验证码按钮
    browser.find_element(By.ID, 'verification_code').click()
    # messagebox.showinfo("提示", "请输入短信验证码并提交")
    print("请输入短信验证码并提交")
    # 最小化窗口方便输入验证码
    root.iconify()
    browser.find_element(By.ID, 'code').click()  # 验证码输入框
    #手动输入
    browser.implicitly_wait(20)
    #自动输入
    # browser.find_element(By.ID, '这里模糊处理了').send_keys(yzm_entry.get())
    # browser.find_element(By.ID, '这里模糊处理了').click()

    # 调用选择乘客的函数
    choose_passengers(browser)
# 选择乘客
def choose_passengers(browser):
    global serial_numbers
    #判断用户输入是否出现错误
    if not error_flag:
        time.sleep(1)
        #等待网页加载,3分钟扫码时间
        WebDriverWait(browser, 180,0.1).until(
            EC.visibility_of_element_located((By.ID, '这里模糊处理了'))
        )
        for num in serial_numbers:
            if num == 1:
                browser.find_element(By.ID, 'normalPassenger_0').click()
            elif num == 2:
                browser.find_element(By.ID, 'normalPassenger_1').click()
            elif num == 3:
                browser.find_element(By.ID, 'normalPassenger_2').click()
            elif num == 4:
                browser.find_element(By.ID, 'normalPassenger_3').click()
            elif num == 5:
                browser.find_element(By.ID, 'normalPassenger_4').click()
            elif num == 6:
                browser.find_element(By.ID, 'normalPassenger_5').click()
            pass
    # 调用提交订单函数
    submit_order(browser)

#提交订单函数
def submit_order(browser):
    global seat_number
    # 点击提交订单按钮
    browser.find_element(By.ID, '这里模糊处理了').click()

    # 调用选择座位函数
    show_selected(browser,serial_numbers)

# 定义一个函数 show_selected,用于显示选中的选项
def show_selected(browser,serial_numbers):
    global seat_numbers
    if len(serial_numbers)==1:
        # 创建一个列表,其中包含被选中的选项的标签文本
        selected_options = [seat_options_1[i] for i, var in enumerate(checkbox_vars_1) if var.get()]
        print("选择的第一排座位是:", selected_options)  # 打印被选中的选项
        seat_numbers=selected_options
        if len(seat_numbers)==1:

            choose_seatclass(browser,serial_numbers)
        else:
            print("错误,乘车人数与选择的座位数不一致!")
            messagebox.showerror("错误", "请选择与乘车人数相等的座位")


    elif 1<len(serial_numbers)<=5:
        selected_options_1 = [seat_options_1[i] for i, var in enumerate(checkbox_vars_1) if var.get()]  # 创建一个列表,其中包含被选中的选项的标签文本
        print("选择的第一排座位是:", selected_options_1)
        selected_options_2 = [seat_options_2[i] for i, var in enumerate(checkbox_vars_2) if var.get()]  # 创建一个列表,其中包含被选中的选项的标签文本
        print("选择的第二排座位是:", selected_options_2)
        seat_numbers = selected_options_1 +selected_options_2
        print("选择的所有座位是:",seat_numbers)
        if len(seat_numbers)==len(serial_numbers):
            choose_seatclass(browser,serial_numbers)
        else:
            print("错误,乘车人数与选择的座位数不一致!")
            messagebox.showerror("错误", "请选择与乘车人数相等的座位")

    elif len(serial_numbers)>5:
        print("乘车人数过多,无法选座,系统将随机安排座位")
        makesure_order(browser)

# 选择座位函数
def choose_seatclass(browser,serial_numbers):
    global div_element,seat_numbers,div_elements
    # 获取座位类型和乘车人数量
    seat_class = seat_class_var.get()
    serial_numbers = len(serial_numbers)

    # 根据座位类型选择不同的下拉框选项
    if seat_class == "二等座":
        if serial_numbers == 1:
            div_elements = [browser.find_element(By.ID, '这里模糊处理了')]
        elif 1 < serial_numbers <= 5:
            div_elements = [browser.find_element(By.ID, '这里模糊处理了'), browser.find_element(By.ID, '这里模糊处理了')]
        else:
            print("错误: 此座位等级无效!")
    elif seat_class == "一等座":
        select_element = Select(browser.find_element(By.ID, "这里模糊处理了"))
        select_element.select_by_value("M")
        if serial_numbers == 1:
            div_elements = [browser.find_element(By.ID, '这里模糊处理了')]
        elif 1 < serial_numbers <= 5:
            div_elements = [browser.find_element(By.ID, '这里模糊处理了'), browser.find_element(By.ID, '这里模糊处理了')]
        else:
            print("错误: 此座位等级无效!")
    elif seat_class == "商务座":
        select_element = Select(browser.find_element(By.ID, "这里模糊处理了"))
        select_element.select_by_value("P")
        if serial_numbers == 1:
            div_elements = [browser.find_element(By.ID, '这里模糊处理了')]
        elif 1 < serial_numbers <= 5:
            div_elements = [browser.find_element(By.ID, '这里模糊处理了'), browser.find_element(By.ID, '这里模糊处理了')]
        else:
            print("错误: 此座位等级无效!")
    else:
        print("错误: 此座位等级无效! ")

    # 遍历每个座位号,并依次点击
    time.sleep(1)
    for seat_number in seat_numbers:
        # 确保座位号不为空
        if seat_number.strip():
            # 遍历每个 div 元素并尝试点击相应的座位
            for div_element in div_elements:
                try:
                    div_element.find_element(By.ID, seat_number.strip()).click()
                    break  # 点击成功后跳出循环
                except NoSuchElementException:
                    # 如果在当前 div 元素中找不到对应的座位号,则尝试下一个 div 元素
                    continue
        else:
            print("座位号为空!")
    makesure_order(browser)

def makesure_order(browser):
    global task_executed
    # # 确认订单
    time.sleep(2)
    # browser.find_element(By.ID, '这里模糊处理了').click()
    print("订票成功,请在手机app【待支付】页面支付")
    # 等待2秒钟,确保页面已加载完毕
    # messagebox.showinfo("--温馨提示--", "订票成功!请在手机app【待支付】页面支付")
    time.sleep(10)
    # 标记任务已执行
    task_executed = True
    # 清除定时任务,确保只执行一次
    schedule.clear()

    browser.quit()


# 创建主窗口
root = tk.Tk()
root.title("Train Ticket Booking")
root.geometry("1500x650")
# root.geometry("1000x400")
root.attributes("-topmost", True)


# 创建左侧框架
left_frame = tk.Frame(root)
left_frame.grid(row=0, column=0, sticky="nsew")

# 左侧声明文本框
shengming1_text = tk.Text(left_frame, height=1, width=10, font=("隶书", 18,"bold"))
shengming1_text.grid(row=1, column=0, columnspan=2, padx=10, pady=10)
shengming1_text.insert(tk.END, " 使用须知")
shengming1_text.tag_configure("foreground_color", foreground="red")
# 应用标签到文本
shengming1_text.tag_add("foreground_color", "1.0", "end")
# 左侧声明文本框
shengming2_text = tk.Text(left_frame, height=2, width=40, font=("华文楷体", 12,"bold"))
shengming2_text.grid(row=2, column=0, columnspan=2, padx=10, pady=10)
shengming2_text.insert(tk.END, "1、请确保【始发站】和【终点站】名称和缩写代码在本地json文件内!")
# 左侧声明文本框
shengming3_text = tk.Text(left_frame, height=2, width=40, font=("华文楷体", 12,"bold"))
shengming3_text.grid(row=3, column=0, columnspan=2, padx=10, pady=10)
shengming3_text.insert(tk.END, "2、如果您没有【学生票】资格,请不要尝试,否则可能会运行出错!")
# 左侧声明文本框
shengming4_text = tk.Text(left_frame, height=2, width=40, font=("华文楷体", 12,"bold"))
shengming4_text.grid(row=4, column=0, columnspan=2, padx=10, pady=10)
shengming4_text.insert(tk.END, "3、【出发日期】务必确保是“xxxx-xx-xx”的格式,否则将不能正常运行!")
# 左侧声明文本框
shengming5_text = tk.Text(left_frame, height=2, width=40, font=("华文楷体", 12,"bold"))
shengming5_text.grid(row=5, column=0, columnspan=2, padx=10, pady=10)
shengming5_text.insert(tk.END, "3、【车次序号】即为右侧【车次信息】栏里面的序号,不同的序号对应不同的车次!")
# 左侧声明文本框
shengming6_text = tk.Text(left_frame, height=2, width=40, font=("华文楷体", 12,"bold"))
shengming6_text.grid(row=6, column=0, columnspan=2, padx=10, pady=10)
shengming6_text.insert(tk.END, "4、【乘车人序号】是12306手机APP里【乘车人】列表排序,不同的序号对应不同的乘车人!")
# 左侧声明文本框
shengming7_text = tk.Text(left_frame, height=2, width=40, font=("华文楷体", 12,"bold"))
shengming7_text.grid(row=7, column=0, columnspan=2, padx=10, pady=10)
shengming7_text.insert(tk.END, "5、不建议使用【密码登录】,速度较慢,使用【扫码登录】则不需要点击下方按钮!")
# 账号输入框
zhanghao_label = tk.Label(left_frame, text="请输入您的账号:", font=("华文楷体", 11,"bold"))
zhanghao_label.grid(row=12, column=0, padx=10, pady=10)
zhanghao_entry = tk.Entry(left_frame,  font=("华文楷体", 12,"bold"))
zhanghao_entry.insert(0, "AAAAAAAAA")  # 默认账号
zhanghao_entry.grid(row=12, column=1, padx=10, pady=10)
# 密码输入框
password_user_label = tk.Label(left_frame, text="请输入您的密码:", font=("华文楷体", 11,"bold"))
password_user_label.grid(row=13, column=0, padx=10, pady=10)
password_user_entry = tk.Entry(left_frame, show="*")
password_user_entry.insert(0, "12345465")  # 默认密码
password_user_entry.grid(row=13, column=1, padx=10, pady=10)
# 身份证尾号输入框
id_user_4_label = tk.Label(left_frame, text="身份证尾号(4位):", font=("华文楷体", 11,"bold"))
id_user_4_label.grid(row=14, column=0, padx=10, pady=10)
id_user_4_entry = tk.Entry(left_frame, font=("华文楷体", 12,"bold"))
id_user_4_entry.insert(0, "0000")  # 默认身份证尾号
id_user_4_entry.grid(row=14, column=1, padx=10, pady=10)
# # 验证码输入框
# yzm_label = ttk.Label(left_frame, text="请输入短信验证码:", font=("华文楷体", 11,"bold"))
# yzm_label.grid(row=15, column=0, padx=10, pady=10)
# yzm_entry = ttk.Entry(left_frame, font=("华文楷体", 12,"bold"))
# yzm_entry.grid(row=15, column=1, padx=10, pady=10)
# # 提交验证码按钮
# submit_button = ttk.Button(left_frame, text="提交验证码", command=login_with_credentials)
# submit_button.grid(row=16, column=0, columnspan=2, pady=10)
# 本程序仅作为学习使用文本框
disclaimer_text = tk.Text(left_frame, height=1, width=20, font=("隶书", 15,"bold"))
disclaimer_text.grid(row=18, column=0, columnspan=2, padx=10, pady=10)
disclaimer_text.insert(tk.END, "本程序仅作为学习使用")


# 创建中间框架
middle_frame = tk.Frame(root,bg="lightblue")
middle_frame.grid(row=0, column=2, sticky="nsew")
# 出发城市输入框
start_city_label = tk.Label(middle_frame, text="请输入您的始发站:", font=("华文楷体", 11,"bold"))
start_city_label.grid(row=0, column=0, padx=10, pady=10)
start_city_entry = tk.Entry(middle_frame, font=("华文楷体", 12,"bold"))
start_city_entry.insert(0, "北京")  # 默认出发城市
start_city_entry.grid(row=0, column=1, padx=10, pady=10)
# 目的城市输入框
arrive_city_label = tk.Label(middle_frame, text="请输入您的终点站:", font=("华文楷体", 11,"bold"))
arrive_city_label.grid(row=1, column=0, padx=10, pady=10)
arrive_city_entry = tk.Entry(middle_frame, font=("华文楷体", 12,"bold"))
arrive_city_entry.insert(0, "成都东")  # 默认目的城市
arrive_city_entry.grid(row=1, column=1, padx=10, pady=10)
# 是否购买学生票下拉菜单
identity_label = tk.Label(middle_frame, text="请选择是否购买学生票:", font=("华文楷体", 11,"bold"))
identity_label.grid(row=2, column=0, padx=10, pady=10)
identity_var = tk.StringVar()
identity_var.set("否")  # 默认选择"否"
identity_option = ttk.OptionMenu(middle_frame, identity_var, "否", "是")
identity_option.grid(row=2, column=1, padx=10, pady=10)
# 出发日期输入框
start_date_label = tk.Label(middle_frame, text="请输入您的出发日期:", font=("华文楷体", 11,"bold"))
start_date_label.grid(row=3, column=0, padx=10, pady=10)
start_date_entry = tk.Entry(middle_frame, font=("华文楷体", 12,"bold"))
start_date_entry.insert(0, "2024-05-01")  # 默认出发日期
start_date_entry.grid(row=3, column=1, padx=10, pady=10)
# 确认按钮
submit_button = ttk.Button(middle_frame, text="确认", command=check_input)
submit_button.grid(row=5, column=0, columnspan=2, pady=10)
# 车次选择输入框
choose_car_num_label = tk.Label(middle_frame, text="请输入您的车次序号:", font=("华文楷体", 11,"bold"))
choose_car_num_label.grid(row=6, column=0, padx=10, pady=10)
choose_car_num_entry = tk.Entry(middle_frame, font=("华文楷体", 12,"bold"))
choose_car_num_entry.grid(row=6, column=1, padx=10, pady=10)
# 出行人序号输入框
serial_people_label = tk.Label(middle_frame, text="请输入您的出行人序号:", font=("华文楷体", 11,"bold"))
serial_people_label.grid(row=8, column=0, padx=10, pady=10)
serial_people_entry = tk.Entry(middle_frame, font=("华文楷体", 12,"bold"))
serial_people_entry.grid(row=8, column=1, padx=10, pady=10)
#选择座位等级
seat_class_label=tk.Label(middle_frame, text="请选择您的座位等级:", font=("华文楷体", 11,"bold"))
seat_class_label.grid(row=9, column=0, padx=10, pady=10)
seat_class_var = tk.StringVar()
seat_class_var.set("二等座")
seat_class_option = ttk.OptionMenu(middle_frame, seat_class_var, "二等座", "一等座","商务座")
seat_class_option.grid(row=9, column=1, padx=10, pady=10)


select_set_text = tk.Text(middle_frame, height=1, width=15, font=("华文楷体", 11,"bold"))
select_set_text.grid(row=10, column=0, columnspan=1, padx=10, pady=10)
select_set_text.insert(tk.END, "请选择您的座位:")
# 定义选项列表1
seat_options_1 = ["1A", "1B", "1C", "1D", "1F"]

# 创建 BooleanVar 对象列表1
checkbox_vars_1 = [tk.BooleanVar() for _ in seat_options_1]

# 创建框架1
frame_1 = tk.Frame(middle_frame)
frame_1.grid(row=11, column=0, padx=10, pady=10, sticky="w")

# 创建复选框1
for i, option in enumerate(seat_options_1):
    checkbox = tk.Checkbutton(frame_1, text=option, variable=checkbox_vars_1[i])
    checkbox.pack(side="left", padx=5)

# 定义选项列表2
seat_options_2 = ["2A", "2B", "2C", "2D", "2F"]
# 创建 BooleanVar 对象列表2
checkbox_vars_2 = [tk.BooleanVar() for _ in seat_options_2]
# 创建框架2
frame_2 = tk.Frame(middle_frame)
frame_2.grid(row=12, column=0, padx=10, pady=10, sticky="w")
# 创建复选框2
for i, option in enumerate(seat_options_2):
    checkbox = tk.Checkbutton(frame_2, text=option, variable=checkbox_vars_2[i])
    checkbox.pack(side="left", padx=5)

# 抢票时间输入框
target_time_str_label = tk.Label(middle_frame, text="请输入您需要点击预定按钮的时间:", font=("华文楷体", 11,"bold"))
target_time_str_label.grid(row=13, column=0, padx=10, pady=10)
target_time_str_entry = tk.Entry(middle_frame, font=("华文楷体", 12,"bold"))
target_time_str_entry.insert(13, "16:00")  # 默认出发日期
target_time_str_entry.grid(row=13, column=1, padx=10, pady=10)

# 登录方式输入框
login_way_label = ttk.Label(middle_frame, text="请选择您的登录方式:", font=("华文楷体", 11,"bold"))
login_way_label.grid(row=15, column=0, padx=10, pady=10)
login_way_var = tk.StringVar()
login_way_var.set("密码登录")  # 默认选择"密码登录"
login_way_option = ttk.OptionMenu(middle_frame, login_way_var, "扫码登录", "密码登录")
login_way_option.grid(row=15, column=1, padx=10, pady=10)
# 提交按钮
submit_button = ttk.Button(middle_frame, text="提交", command=choose_car)
submit_button.grid(row=16, column=0, columnspan=2, pady=10)


# 创建右侧框架
right_frame = tk.Frame(root, bg="lightblue")
right_frame.grid(row=0, column=3, sticky="nsew")

# 车次信息标签
result_label = tk.Label(right_frame, text="车次信息:", font=("华文楷体", 14, "bold"))
result_label.grid(row=0, column=0, padx=5, pady=5)

# 车次信息滚动文本框
result_text = tk.Text(right_frame, wrap="none")
result_text.grid(row=1, column=0, padx=10, pady=10, sticky="nsew")

# 垂直滚动条
vertical_scrollbar = tk.Scrollbar(right_frame, orient="vertical", command=result_text.yview)
vertical_scrollbar.grid(row=1, column=1, sticky="ns")
result_text.config(yscrollcommand=vertical_scrollbar.set)

# 水平滚动条
horizontal_scrollbar = tk.Scrollbar(right_frame, orient="horizontal", command=result_text.xview)
horizontal_scrollbar.grid(row=2, column=0, sticky="ew")
result_text.config(xscrollcommand=horizontal_scrollbar.set)




# 运行窗口
root.mainloop()

再次声明:本程序只用作学习交流使用,部分代码已经模糊处理,请遵守相关法律规定!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值