火车票网站自动登录模拟购票

81 阅读3分钟

python+selenium实现

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

options = Options()
options.add_argument("--headless")  # 无界面运行
options.add_argument("--disable-gpu")  # 禁用GPU加速
# 初始化浏览器
wd = webdriver.Chrome()
wd.minimize_window()  # 启动后立即最小化窗口
wait = WebDriverWait(wd, 20)
actions = ActionChains(wd)

def input_with_enter(element, text):
    """带回车确认的输入方法"""
    element.clear()
    time.sleep(0.3)
    for char in text:
        element.send_keys(char)
        time.sleep(0.1)
    element.send_keys(Keys.ENTER)  # 关键回车确认
    time.sleep(0.5)


def set_station_auto(field_id, prompt):
    """自动化设置车站输入(带自动回车确认)"""
    while True:
        try:
            # 获取输入框
            input_field = wait.until(EC.element_to_be_clickable((By.ID, field_id)))

            # 清除原有内容
            input_field.click()
            wd.execute_script(f"document.getElementById('{field_id}').value='';")
            time.sleep(0.5)

            # 获取用户输入
            city_name = input(prompt)
            if not city_name:
                continue

            # 输入并回车确认
            input_with_enter(input_field, city_name)

            # 检查是否成功选择
            if input_field.get_attribute("value"):
                print(f"✅ 已设置: {input_field.get_attribute('value')}")
                return True

        except Exception as e:
            print(f"❌ 设置失败: {str(e)}")
            wd.save_screenshot(f"error_{field_id}.png")


def switch_to_new_window():
    """切换到新打开的窗口"""
    original_window = wd.current_window_handle
    # 等待新窗口出现
    WebDriverWait(wd, 10).until(lambda d: len(d.window_handles) > 1)

    # 切换到新窗口
    for window_handle in wd.window_handles:
        if window_handle != original_window:
            wd.switch_to.window(window_handle)
            break

    print(f"已切换到新窗口,当前URL: {wd.current_url}")
    return True


def print_ticket_info():
    """打印车票信息"""
    print("\n=== 可购车次信息 ===")

    try:
        # 等待结果表格加载
        wait.until(EC.presence_of_element_located((By.ID, "t-list")))

        # 获取所有车次行
        train_list = wait.until(EC.presence_of_all_elements_located(
            (By.CSS_SELECTOR, "#queryLeftTable tr[id^='ticket_']")))

        trains = []
        for i, train in enumerate(train_list, 1):
            try:
                number = train.find_element(By.CLASS_NAME, "number").text
                start_time = train.find_element(By.CLASS_NAME, "start-t").text
                arrive_time = train.find_element(By.CLASS_NAME, "color999").text
                duration = train.find_element(By.CLASS_NAME, "ls").text
                seat_types = train.find_elements(By.CSS_SELECTOR, ".seat[class^='seat_']")

                seat_info = []
                for seat in seat_types:
                    seat_name = seat.get_attribute("textContent").strip()
                    seat_count = seat.find_element(By.CLASS_NAME, "seat_surplus").text
                    seat_info.append(f"{seat_name}:{seat_count}")

                train_info = {
                    "index": i,
                    "number": number,
                    "departure": start_time,
                    "arrival": arrive_time,
                    "duration": duration,
                    "seats": " | ".join(seat_info)
                }
                trains.append(train_info)

                print(
                    f"{i}. 车次: {number} | 出发: {start_time} | 到达: {arrive_time} | 历时: {duration} | 余票: {' | '.join(seat_info)}")
            except Exception as e:
                print(f"解析车次信息时出错: {str(e)}")
                continue

        return trains

    except Exception as e:
        print(f"获取车票信息失败: {str(e)}")
        wd.save_screenshot("ticket_info_error.png")
        return None


def select_and_book(train_list):
    """选择车次并购票"""
    while True:
        choice = input("\n请输入要购买的车次编号(1-{}), 或输入q退出: ".format(len(train_list)))
        if choice.lower() == 'q':
            return False

        try:
            index = int(choice) - 1
            if 0 <= index < len(train_list):
                selected_train = train_list[index]
                print(f"\n您选择了: {selected_train['number']}")

                # 获取当前窗口句柄
                original_window = wd.current_window_handle

                # 点击预订按钮
                book_btn = WebDriverWait(wd, 10).until(
                    EC.element_to_be_clickable((By.XPATH, "//*[@id='ticket_5500000G1401_01_05']/td[13]/a")))

                book_btn.click()
                print("正在跳转到预订页面...")

                # 处理可能的新窗口/新页面
                try:
                    # 尝试切换到新窗口
                    switch_to_new_window()
                except:
                    # 如果没新窗口,则等待当前页面刷新
                    wait.until(EC.url_changes(wd.current_url))
                    print(f"页面已跳转,当前URL: {wd.current_url}")

                return True
            else:
                print("请输入有效的编号!")
        except ValueError:
            print("请输入数字!")


def handle_passenger_selection():
    """处理乘客选择"""
    try:
        # 等待乘客选择页面加载
        wait.until(EC.presence_of_element_located((By.ID, "normal_passenger_id")))

        # 选择第一个乘客
        first_passenger = wait.until(EC.element_to_be_clickable(
            (By.CSS_SELECTOR, "#normal_passenger_id input[name^='normalPassenger']")))
        first_passenger.click()
        print("✅ 已选择乘客")

        # 点击提交订单
        submit_btn = wait.until(EC.element_to_be_clickable((By.ID, "submitOrder_id")))
        submit_btn.click()
        print("✅ 已提交订单")

        # 确认订单
        confirm_btn = wait.until(EC.element_to_be_clickable((By.ID, "qr_submit_id")))
        confirm_btn.click()
        print("🎉 购票成功!请尽快支付!")
        return True
    except Exception as e:
        print(f"购票过程中出错: {str(e)}")
        wd.save_screenshot("book_error.png")
        return False


try:
    # 访问首页
    wd.get("https://www.12306.cn/index/index.html")
    print("✅ 已打开12306首页")

    # 设置出发地
    set_station_auto("fromStationText", "请输入出发地(如'北京')然后按回车: ")

    # 设置目的地
    set_station_auto("toStationText", "请输入目的地(如'上海')然后按回车: ")

    # 设置日期
    date_input = wait.until(EC.element_to_be_clickable((By.ID, "train_date")))
    travel_date = input("请输入日期(YYYY-MM-DD)然后按回车: ")
    input_with_enter(date_input, travel_date)
    print(f"📅 已设置日期: {date_input.get_attribute('value')}")

    # 点击查询
    search_btn = wait.until(EC.element_to_be_clickable((By.ID, "search_one")))
    search_btn.click()
    print("🔍 正在查询车票...")

    # 处理可能的新页面/新窗口
    try:
        switch_to_new_window()
    except:
        print("没有新窗口,继续在当前窗口操作")

    # 等待结果并打印车票信息
    train_list = print_ticket_info()

    # 选择车次并购票
    if train_list and select_and_book(train_list):
        # 处理乘客选择和订单提交
        handle_passenger_selection()

except Exception as e:
    print(f"❗ 发生错误: {str(e)}")
    wd.save_screenshot("main_error.png")
finally:
    input("按回车键退出...")
    wd.quit()