Python+Selenium自动化抢票实战:从原理到实现
1. 项目概述:为什么选择Python+Selenium来“智取”12306?
又到了一年一度的出行高峰季,抢票大战准时上演。守在电脑前不断刷新,眼睁睁看着票从“有”变“无”,这种体验相信很多人都经历过。作为一名技术从业者,我一直在想,能不能用技术手段把我们从这种重复、枯燥且充满不确定性的体力劳动中解放出来?答案是肯定的,而且实现它的核心工具,就是Python和Selenium。
这个项目,本质上是一个 基于浏览器自动化的模拟操作脚本 。它不涉及破解、攻击或任何违规行为,其原理是模拟一个真实用户的所有操作流程:打开浏览器、登录12306、查询车次、选择乘客、提交订单。Selenium就像一个不知疲倦的“机器人手”,可以精确地执行我们预设的点击、输入、跳转等指令。而Python作为“大脑”,负责逻辑控制、数据处理和异常处理。两者的结合,让我们能够以远超人工的速度和精度,完成抢票流程。
为什么是Selenium,而不是直接调用12306的API?这里有几个核心考量。首先,直接调用官方未公开的API存在法律和技术风险,且接口变动频繁,维护成本极高。其次,12306的登录和下单流程中包含了复杂的动态验证(如滑块验证、点选验证),这些验证机制正是为了防范机器请求。Selenium直接操作浏览器,可以完整地加载和执行这些前端验证逻辑,本质上是在“合规”的框架内进行自动化。最后,浏览器环境能更好地模拟人类行为,降低被风控系统识别为机器的风险。
这个脚本适合谁?如果你是有一定Python基础,对Web自动化感兴趣,并且受够了手动抢票的折磨,那么这个项目将是一个绝佳的实战练手机会。它不仅涵盖了Selenium的核心操作,还涉及网络请求处理、多线程/协程控制、邮件服务集成等实用技能。整个过程,就像在编写一个能帮你干活的数字助手,成就感十足。
2. 核心思路与架构设计:从人工到自动化的思维转变
手动抢票的核心动作可以抽象为几个关键节点:登录、查询、监控、下单。自动化脚本的设计,就是将这些节点串联起来,并用代码逻辑驱动。但直接照搬人工操作是行不通的,我们必须进行“机器友好”的改造。
2.1 整体流程设计
一个健壮的抢票脚本,其核心流程应该是一个包含状态判断和异常处理的循环。我设计的核心流程如下:
- 环境初始化与登录 :启动浏览器,加载12306登录页,处理登录流程(包括账号密码输入和可能的图形验证码)。
- 票务查询与监控 :根据用户配置的日期、车次、坐席等信息,循环查询余票。这里的关键是查询频率的控制,过快容易被封,过慢则可能错过票。
- 有票判定与乘客选择 :当查询到符合条件的余票时,脚本需要立刻锁定,并自动跳转到订单提交页面,选择预设的乘客。
- 提交订单与二次确认 :提交订单后,通常会有一个倒计时的确认页面。脚本需要自动完成确认。
- 结果通知与状态同步 :无论成功与否,都需要通过一个可靠的渠道(如邮箱)将结果通知用户。对于失败情况(如网络异常、验证失败),脚本应能自动重试或记录日志。
这个流程看似线性,但每个环节都可能“卡壳”。比如登录时的验证码识别、查询时的网络波动、提交订单时的排队拥挤。因此,我们的脚本必须在每个环节都内置 重试机制 和 超时处理 。
2.2 技术选型与工具准备
- Python 3.7+ : 语言本体,选择较新的版本以获得更好的异步支持。
- Selenium 4.x : 核心自动化库。请注意,Selenium 4在API上与3.x有部分不兼容,建议直接使用4.x版本。
- WebDriver : 这是Selenium控制浏览器的桥梁。我们必须根据使用的浏览器下载对应的驱动。
- Chrome/Edge : 下载
chromedriver,版本号必须与本地安装的Chrome浏览器 大版本号 完全一致。 - Firefox : 下载
geckodriver。 - 将下载的驱动文件所在目录添加到系统环境变量PATH中,或者直接在代码里指定驱动路径。
- Chrome/Edge : 下载
- 第三方Python库 :
selenium: 主库。requests: 用于辅助的网络请求,例如获取车站编码、调用邮件API。schedule或apscheduler: 用于实现定时循环查询(可选,也可以用简单循环加time.sleep)。smtplib和email: Python内置库,用于发送邮件通知。
- 浏览器建议 :推荐使用Chrome或Edge。它们的WebDriver支持最完善,且无头模式(Headless)运行稳定。Firefox在某些情况下对动态页面的处理略有不同。
注意:驱动版本匹配是新手最容易踩的坑。 你可以在浏览器的“关于”页面查看版本号,然后去对应的驱动官网下载相同大版本号(如Chrome 120.xxx)的驱动。不匹配会导致脚本无法启动浏览器。
3. 核心环节拆解与实战编码
接下来,我们进入具体的代码实现环节。我会分模块讲解关键代码,并附上详细的注释和避坑指南。
3.1 环境搭建与驱动配置
首先,确保你的Python环境已就绪。使用pip安装Selenium:
pip install selenium
然后,编写一个基础的浏览器启动类。这个类将封装浏览器的初始化、元素查找、等待等常用操作,提高代码复用性。
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.common.exceptions import TimeoutException, NoSuchElementException
import time
class TicketGrabber:
def __init__(self, driver_path=None, headless=False):
"""
初始化抢票器
:param driver_path: WebDriver路径,如果已加入PATH则为None
:param headless: 是否使用无头模式(不显示浏览器界面)
"""
options = webdriver.ChromeOptions()
if headless:
options.add_argument('--headless') # 无头模式,后台运行
options.add_argument('--disable-blink-features=AutomationControlled') # 禁用自动化控制特征
options.add_experimental_option('excludeSwitches', ['enable-automation']) # 关闭开发者模式提示
options.add_experimental_option('useAutomationExtension', False)
# 初始化浏览器驱动
if driver_path:
self.driver = webdriver.Chrome(executable_path=driver_path, options=options)
else:
self.driver = webdriver.Chrome(options=options)
# 执行CDP命令,进一步隐藏自动化特征
self.driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
'source': '''
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
'''
})
self.wait = WebDriverWait(self.driver, 10) # 全局显式等待,超时10秒
def find_element(self, by, value, timeout=10):
"""查找单个元素,支持显式等待"""
try:
return WebDriverWait(self.driver, timeout).until(
EC.presence_of_element_located((by, value))
)
except TimeoutException:
print(f"元素定位超时: {by}={value}")
return None
def quit(self):
"""关闭浏览器"""
self.driver.quit()
关键点解析:
- 无头模式 (Headless) :
--headless参数让浏览器在后台运行,不显示GUI界面,节省资源且适合服务器部署。但在调试阶段,建议关闭此选项,以便观察脚本执行过程。 - 反检测策略 : 12306等现代网站会检测浏览器是否被自动化工具控制。我们通过
--disable-blink-features=AutomationControlled和excludeSwitches选项来隐藏部分特征。最核心的是通过execute_cdp_cmd将navigator.webdriver属性设置为undefined,这是目前绕过检测最有效的方法之一。 - 显式等待 (WebDriverWait) : 这是Selenium最佳实践之一。网络有延迟,页面加载需要时间,使用
time.sleep(固定秒数)是低效且不可靠的。显式等待会持续检查某个条件(如元素出现、元素可点击)是否成立,成立则立即继续,最多等待指定时间。这大大提高了脚本的稳定性和执行速度。
3.2 攻克第一关:自动化登录与验证码处理
登录12306最大的挑战是验证码。目前12306主要采用 滑动验证码 和 点选验证码 。
策略:半自动化处理。 完全自动识别验证码(使用OCR或深度学习)对于个人项目来说成本高、稳定性差,且可能违反网站规则。更务实的方法是 脚本负责加载出验证码图片,并暂停,等待用户手动完成验证 。验证通过后,脚本再继续执行后续操作。
def login(self, username, password):
"""登录12306"""
self.driver.get("https://kyfw.12306.cn/otn/resources/login.html")
time.sleep(2) # 等待页面基本加载,此处可用更智能的等待
# 1. 点击“账号登录”标签(如果默认不是)
account_login_tab = self.find_element(By.CLASS_NAME, "login-hd-account")
if account_login_tab:
account_login_tab.click()
# 2. 输入用户名和密码
user_input = self.find_element(By.ID, "J-userName")
pass_input = self.find_element(By.ID, "J-password")
if user_input and pass_input:
user_input.clear()
user_input.send_keys(username)
pass_input.clear()
pass_input.send_keys(password)
print("用户名密码已输入,等待手动完成验证码...")
else:
print("未找到用户名或密码输入框")
return False
# 3. 关键步骤:等待用户手动完成验证码
# 脚本在此处暂停,给出提示。用户需手动在浏览器中完成滑块或点选验证。
input("请在浏览器中完成登录验证码操作,完成后按回车键继续...")
# 4. 验证是否登录成功
# 登录成功后,页面通常会跳转,或者出现用户昵称元素
try:
# 尝试查找代表登录成功的元素,例如“我的12306”链接或用户昵称
WebDriverWait(self.driver, 15).until(
EC.presence_of_element_located((By.LINK_TEXT, "我的12306"))
)
print("登录成功!")
# 获取并保存登录必需的cookies,供后续查询使用(可选,但更稳定)
self.cookies = self.driver.get_cookies()
return True
except TimeoutException:
print("登录可能失败,未检测到成功跳转。")
# 可以在这里截图,方便调试
self.driver.save_screenshot("login_failed.png")
return False
邮箱交互技巧初现: 想象一下,你把这个脚本部署在云服务器上,它如何通知你“该去手动验证了”?这就是我们引入邮箱交互的原因。我们可以在脚本运行到等待验证这一步时, 自动发送一封邮件到你的邮箱 ,邮件内容包含一个提醒,甚至可以直接包含一个远程桌面查看的提示。这样,即使你不在电脑前,也能通过手机邮件获知进度。
import smtplib
from email.mime.text import MIMEText
from email.header import Header
def send_email_notification(subject, content, to_addr):
"""发送邮件通知的简单函数"""
# 配置发件人邮箱信息(以QQ邮箱为例)
mail_host = "smtp.qq.com"
mail_user = "your_email@qq.com" # 发件人邮箱
mail_pass = "your_authorization_code" # 注意:不是邮箱密码,是SMTP授权码
sender = mail_user
receivers = [to_addr]
message = MIMEText(content, 'plain', 'utf-8')
message['From'] = Header("抢票机器人", 'utf-8')
message['To'] = Header("主人", 'utf-8')
message['Subject'] = Header(subject, 'utf-8')
try:
smtp_obj = smtplib.SMTP_SSL(mail_host, 465) # QQ邮箱SSL端口
smtp_obj.login(mail_user, mail_pass)
smtp_obj.sendmail(sender, receivers, message.as_string())
smtp_obj.quit()
print("邮件发送成功")
except smtplib.SMTPException as e:
print(f"邮件发送失败: {e}")
# 在login函数中等待验证前调用
# send_email_notification("12306登录验证提醒", "脚本已到达登录验证步骤,请尽快在浏览器中完成验证!", "your_phone@qq.com")
# input("请在浏览器中完成登录验证码操作,完成后按回车键继续...")
实操心得:关于授权码 :几乎所有邮箱服务商都要求使用授权码而非密码来连接SMTP服务。以QQ邮箱为例,需要在“设置”->“账户”中开启POP3/SMTP服务,并生成一个专属授权码。这个授权码是连接脚本和邮箱的关键。
3.3 票务查询与监控逻辑
登录成功后,就进入了核心的查票循环。12306的查询接口是 https://kyfw.12306.cn/otn/leftTicket/query ,但我们不直接调用,而是通过Selenium操作查询页面,让浏览器去发起这个请求,这样更模拟真人行为。
def query_tickets(self, from_station, to_station, train_date):
"""
查询指定日期、区间的车票
:param from_station: 出发站名,如“北京”
:param to_station: 到达站名,如“上海”
:param train_date: 出发日期,格式“2024-01-01”
:return: 查询结果列表
"""
# 跳转到车票查询页面
self.driver.get("https://kyfw.12306.cn/otn/leftTicket/init")
time.sleep(3) # 等待页面加载
# 1. 填充出发站、到达站(这里需要处理车站编码)
# 12306页面输入车站名时会有下拉列表选择,我们可以用JS直接设置输入框的值并触发事件
from_station_input = self.find_element(By.ID, "fromStationText")
to_station_input = self.find_element(By.ID, "toStationText")
if from_station_input and to_station_input:
# 先点击输入框清空默认内容
from_station_input.click()
from_station_input.clear()
# 通过JS设置值并触发input事件,模拟真实输入
self.driver.execute_script(f'arguments[0].value = "{from_station}"; arguments[0].dispatchEvent(new Event("input"))', from_station_input)
time.sleep(0.5) # 等待下拉列表出现并自动选择(如果车站名唯一)
to_station_input.click()
to_station_input.clear()
self.driver.execute_script(f'arguments[0].value = "{to_station}"; arguments[0].dispatchEvent(new Event("input"))', to_station_input)
time.sleep(0.5)
# 2. 填充出发日期
date_input = self.find_element(By.ID, "train_date")
if date_input:
# 同样使用JS直接设置,因为日期输入框可能有只读属性
self.driver.execute_script(f'arguments[0].value = "{train_date}";', date_input)
# 3. 点击“查询”按钮
query_btn = self.find_element(By.ID, "query_ticket")
if query_btn:
query_btn.click()
print(f"已发起查询:{from_station} -> {to_station}, 日期:{train_date}")
# 4. 等待查询结果表格加载
try:
WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.ID, "queryLeftTable"))
)
print("查询结果加载完成。")
except TimeoutException:
print("查询结果加载超时。")
return []
# 5. 解析查询结果
# 车次信息在 id 为 queryLeftTable 的 tbody 下的 tr 中,class 以 ‘bgc’ 开头或没有class
ticket_list = []
try:
rows = self.driver.find_elements(By.CSS_SELECTOR, "#queryLeftTable tr[class^=bgc], #queryLeftTable tr:not([class])")
for row in rows:
try:
# 提取车次号
train_number = row.find_element(By.CLASS_NAME, "number").text.strip()
# 提取出发/到达时间
start_time = row.find_element(By.CLASS_NAME, "start-t").text.strip()
# 提取历时
duration = row.find_element(By.CLASS_NAME, "ls").text.strip()
# 提取座位信息(这里以二等座为例,class是‘ze’)
second_class_seat = row.find_element(By.CSS_SELECTOR, ".ze .ticket-num").text.strip()
# “有”或数字表示有票,“无”或“--”表示无票
has_ticket = second_class_seat not in ['无', '--', '']
ticket_info = {
'车次': train_number,
'出发时间': start_time,
'历时': duration,
'二等座': second_class_seat,
'有票': has_ticket
}
ticket_list.append(ticket_info)
# 打印有票的车次
if has_ticket:
print(f"发现余票!车次:{train_number}, 时间:{start_time}, 二等座:{second_class_seat}")
except NoSuchElementException:
# 某一行可能缺少某些元素,跳过
continue
except Exception as e:
print(f"解析车次信息时出错:{e}")
return ticket_list
监控循环的设计: 有了单次查询函数,我们需要一个循环来持续监控。这个循环需要控制频率,并能在查到票时立刻中断循环,进入下单流程。
def monitor_tickets(self, from_station, to_station, train_date, target_trains=None, interval=5):
"""
监控指定车次余票
:param target_trains: 目标车次列表,如['G1', 'G2']。为None则监控所有有票车次。
:param interval: 查询间隔(秒),不宜过短,建议5-10秒。
"""
print(f"开始监控 {from_station} -> {to_station},日期:{train_date}")
if target_trains:
print(f"目标车次:{target_trains}")
while True:
try:
tickets = self.query_tickets(from_station, to_station, train_date)
for ticket in tickets:
if ticket['有票']:
train_num = ticket['车次']
# 如果未指定目标车次,或者当前车次在目标列表中
if target_trains is None or train_num in target_trains:
print(f"*** 符合条件!车次 {train_num} 有余票,正在尝试下单... ***")
# 这里应该跳转到下单页面,我们假设有一个book_ticket函数
if self.book_ticket(train_num, from_station, to_station, train_date):
# 下单成功,发送通知并退出监控
send_email_notification("抢票成功!", f"恭喜!已成功抢到 {train_num} 次列车车票,请及时支付。", "your_email@example.com")
return True
else:
print(f"车次 {train_num} 下单失败,继续监控...")
# 本次查询未找到符合条件的票,等待后继续
print(f"本轮查询未找到符合条件的余票,{interval}秒后重新查询...")
time.sleep(interval)
except Exception as e:
print(f"监控过程中发生异常:{e},稍后重试...")
time.sleep(interval)
3.4 自动下单与订单确认
这是最紧张的一步。一旦监控到有票,脚本需要以最快速度完成座位选择和订单提交。
def book_ticket(self, train_number, from_station, to_station, train_date):
"""尝试预订指定车次的车票"""
# 1. 在查询结果页,点击对应车次的“预订”按钮
# 预订按钮的selector比较复杂,通常可以通过车次号来定位
try:
# 构建预订按钮的XPath:找到包含特定车次文本的td,然后找到其同级td下的预订链接
book_btn_xpath = f"//tr[contains(@id, 'ticket_{train_number}')]//a[contains(@class, 'btn72')]"
book_button = self.find_element(By.XPATH, book_btn_xpath, timeout=5)
if book_button and book_button.is_enabled():
book_button.click()
print(f"已点击车次 {train_number} 的预订按钮。")
else:
print(f"车次 {train_number} 的预订按钮不可用或未找到。")
return False
except Exception as e:
print(f"定位预订按钮失败:{e}")
return False
# 2. 等待跳转到乘客选择页面,并自动选择乘客
try:
# 等待页面跳转,通常会出现“乘客信息”字样
WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.XPATH, "//div[contains(text(), '乘客信息')]"))
)
print("已进入乘客选择页面。")
# 3. 选择乘客(假设要选择第一个常用乘客)
# 乘客列表的复选框
passenger_checkbox = self.find_element(By.XPATH, "//input[@type='checkbox' and @name='passenger']", timeout=5)
if passenger_checkbox and not passenger_checkbox.is_selected():
# 使用JS点击,避免元素被遮挡等问题
self.driver.execute_script("arguments[0].click();", passenger_checkbox)
print("已选择第一位乘客。")
else:
print("未找到乘客复选框或已选中。")
# 4. 选择座位类型(如二等座)
seat_type_dropdown = self.find_element(By.ID, "seatType_1") # 示例ID,实际需根据页面调整
if seat_type_dropdown:
from selenium.webdriver.support.select import Select
seat_select = Select(seat_type_dropdown)
seat_select.select_by_visible_text("二等座") # 选择文本为“二等座”的选项
print("已选择座位类型:二等座。")
# 5. 提交订单
submit_order_btn = self.find_element(By.ID, "submitOrder_id")
if submit_order_btn:
submit_order_btn.click()
print("已提交订单,等待系统确认...")
# 6. 处理订单确认对话框(通常会出现一个模态框,需要点击“确认”)
WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.ID, "qr_submit_id"))
)
confirm_btn = self.find_element(By.ID, "qr_submit_id")
if confirm_btn and confirm_btn.is_displayed():
confirm_btn.click()
print("已确认订单!")
# 此时订单已生成,进入支付页面。脚本可以在此暂停,或发送邮件通知用户手动支付。
send_email_notification("订单已生成,等待支付", f"车次 {train_number} 订单已成功生成,请尽快登录12306完成支付!", "your_email@example.com")
return True
else:
print("未找到订单确认按钮。")
return False
except TimeoutException as e:
print(f"下单流程超时:{e}")
self.driver.save_screenshot("order_timeout.png")
return False
except Exception as e:
print(f"下单过程中发生未知错误:{e}")
self.driver.save_screenshot("order_error.png")
return False
4. 进阶技巧与避坑指南
将上述模块组合起来,一个基础的自动化抢票脚本就成型了。但在实际运行中,你会遇到各种各样的问题。下面分享一些进阶技巧和常见坑点。
4.1 邮箱交互的深度应用
前面我们提到了用邮件通知“需要手动验证”。其实,邮箱可以扮演更重要的角色: 远程控制中枢 。
- 状态心跳通知 :让脚本每隔一段时间(如每30分钟)发送一封“我还活着”的状态邮件,报告监控的车次、查询次数等信息。如果收不到心跳邮件,说明脚本可能已经崩溃。
- 指令接收 (高级):你可以搭建一个简单的邮件接收服务(如使用
imaplib库定期检查收件箱),通过发送特定主题或内容的邮件到指定邮箱,来远程控制脚本,例如“暂停监控”、“更换监控车次为G100”、“立即停止”等。 - 结果详情报送 :抢票成功或失败后,不仅发送简单通知,还可以将详细的订单信息、失败原因(截图附件)一并发送,方便复盘。
# 示例:发送带简单HTML格式和附件的通知邮件(需要引入更多email模块组件)
def send_email_with_attachment(subject, content, attachment_path=None):
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
import os
msg = MIMEMultipart()
msg['From'] = sender
msg['To'] = receiver
msg['Subject'] = subject
msg.attach(MIMEText(content, 'html', 'utf-8')) # 使用HTML内容
if attachment_path and os.path.exists(attachment_path):
part = MIMEBase('application', 'octet-stream')
with open(attachment_path, 'rb') as file:
part.set_payload(file.read())
encoders.encode_base64(part)
part.add_header('Content-Disposition', f'attachment; filename="{os.path.basename(attachment_path)}"')
msg.attach(part)
# ... 发送逻辑同上
4.2 稳定性与反反爬策略
12306的反爬机制在不断升级。除了之前提到的隐藏 webdriver 属性,还需要注意:
- 随机化等待时间 :在操作之间使用固定的
time.sleep(2)是明显的机器特征。应该使用随机延迟,例如time.sleep(random.uniform(1, 3))。 - 模拟人类操作轨迹 :Selenium的
ActionChains可以模拟更真实的鼠标移动轨迹,而不是直接从A点跳到B点。 - Cookies管理 :登录成功后,妥善保存
driver.get_cookies(),并在下次启动时使用driver.add_cookie(cookie)恢复登录状态,可以避免频繁登录触发验证。 - User-Agent轮换 :虽然Selenium会使用真实浏览器UA,但在某些场景下可以准备多个UA进行轮换(通过
options.add_argument(f'user-agent={ua}'))。 - 使用更隐蔽的浏览器模式 :可以考虑使用
undetected-chromedriver这类第三方库,它专门为绕过自动化检测而设计。
4.3 常见问题排查实录
-
报错:
Message: ‘chromedriver‘ executable needs to be in PATH- 原因 :系统找不到ChromeDriver。
- 解决 :确保已下载正确版本的ChromeDriver,并将其所在目录添加到系统环境变量PATH中,或者在代码中指定绝对路径:
webdriver.Chrome(executable_path=‘/path/to/chromedriver‘)。
-
元素找不到(NoSuchElementException)
- 原因1 :页面还没加载完。 解决 :务必使用显式等待
WebDriverWait代替硬性等待time.sleep。 - 原因2 :页面结构变了,或者元素在iframe里。 解决 :检查页面HTML结构是否更新;如果元素在iframe内,需要使用
driver.switch_to.frame(frame_element)切换到对应iframe后才能定位。 - 原因3 :元素被遮挡或不可见。 解决 :尝试使用JavaScript直接点击:
driver.execute_script(“arguments[0].click();“, element)。
- 原因1 :页面还没加载完。 解决 :务必使用显式等待
-
脚本被识别,出现验证码或直接失败
- 原因 :浏览器指纹或行为被检测。
- 解决 :启用前面提到的所有反检测选项(
disable-blink-features,excludeSwitches, CDP命令)。考虑使用无头模式时添加更多参数模拟真人,如--window-size=1920,1080。最根本的方法是接受“半自动化”,在关键验证步骤让人工介入。
-
查询或下单速度慢,错过票
- 原因 :网络延迟、代码逻辑不够优化、等待时间设置过长。
- 解决 :优化代码,减少不必要的等待;使用更快的网络环境(如本地运行优于远程服务器);可以考虑使用多线程或异步IO来同时监控多个日期或车次,但要注意同一个账号频繁请求可能被限制。
-
登录成功后,查询页面提示“未登录”
- 原因 :Cookie丢失或域名切换导致会话失效。12306查询页面和登录页面域名可能略有不同。
- 解决 :登录成功后不要关闭浏览器,直接用同一个
driver实例跳转到查询页。如果必须重启脚本,尝试保存和加载Cookies。
这个项目将Python的灵活性与Selenium的自动化能力结合,为你构建了一个私人订制的抢票助手。它不仅仅是一个工具,更是一个深入了解Web自动化、反爬策略和任务编排的绝佳实践。从环境搭建到反检测绕过,从邮件交互到异常处理,每一个环节都充满了值得琢磨的技术细节。记住,技术的目的是提高效率、解决重复劳动,但务必在合规合理的前提下使用。希望这篇详细的指南能帮你顺利搞定回家的车票,更重要的是,在动手实践的过程中,你的编程和问题解决能力一定会再上一个台阶。如果在实际操作中遇到新的问题,不妨多看看浏览器的开发者工具(F12),观察网络请求和元素变化,那才是解决问题的第一现场。
更多推荐
所有评论(0)