Python+Selenium自动化抢票脚本实战:从环境搭建到踩坑指南

发布时间:2026/9/9 14:44:41
Python+Selenium自动化抢票脚本实战:从环境搭建到踩坑指南 春节抢票这事儿每年都像一场没有硝烟的战争。手动刷新页面、盯着余票数、反复提交订单手指和眼睛都累得不行。用Python写一个自动化脚本让Selenium替代你完成打开页面、查询余票、填写乘客信息这些重复动作把精力留在最后一步“确认提交”上这是绝大多数人能做到、也值得一试的方案。这篇文章我不讲虚的直接按我做自动化测试和爬虫项目时沉淀下来的经验带你从环境搭建一路写到能跑起来的抢票辅助脚本中间会把踩过的坑、排查过的报错一并交代清楚。1. 抢票脚本到底要做什么先拆解需求再动手写代码1.1 抢票场景里的真实痛点余票监控与购票节奏很多人以为抢票脚本是高深的黑科技其实拆开来看就三件事查余票、填信息、交订单。手动操作的痛处在哪起售时间一到网站瞬间进入高并发状态你刷新一次页面可能要三五秒等页面完全加载出来又要几秒等你看清哪趟车有余票座位可能已经被抢光了。脚本的价值不是“保证你一定抢到票”而是把“查余票”这个动作的间隔从几秒压缩到几百毫秒同时把“填乘客信息”这种最费时间的操作从一分钟压缩到一秒内完成。也别指望脚本能替代平台方开发的官方购票通道。在大多数情况下脚本扮演的角色是“手脚麻利的助手”帮你把机械动作做完把最关键的判断和确认留给你。我习惯把这个思路总结成一句话自动化负责速度和效率人工负责最终决策与合规操作。这样既降低了被封风险和处理异常的成本也让你对每一张订单都有绝对的控制权。1.2 技术方案对比为什么是Selenium而不是Requests写网络自动化脚本技术选型往往卡在 Requests、Selenium、Playwright 之间。我直接给结论想快速落地、资料最全、遇到坑时能搜到答案选Selenium。我用过一段时间的 Requests纯接口模拟的确轻量CPU和内存占用比Selenium小一个量级。但问题是现在的购票类网站基本不再做纯服务端渲染了余票、车次信息都是页面加载后通过JavaScript动态刷出来的。你用Requests拿到的HTML可能里面什么都没有还得额外分析接口、维护Cookies和Headers折腾一圈下来复杂度反而比Selenium高。再加上登录验证、滑块验证这类交互纯Requests处理起来非常痛苦。Playwright 这几年也热语法更现代还能自动等待元素。但说实话它的中文资料量、第三方封装和社区问题积累还是比Selenium少。Selenium存在了这么多年大到电商平台、小到个人脚本你能想到的坑几乎都有人踩过并写了解决方案。对于一个要在春节前快速交付上线的脚本来说稳定和可排查比“更先进”重要得多。2. 环境准备从零搭好Selenium自动化工作台2.1 Python安装与依赖安装的正确姿势不管你是Windows还是macOS第一步都是先装Python。Windows用户去官方网站下载安装包时记得在安装向导第一步勾选Add Python to PATH这一步漏掉的话后面命令行里敲python会提示找不到命令。装完之后打开命令行工具输入python --version能输出版本号就说明成功了。依赖安装用pip一行命令搞定pip install selenium国内网络环境下载慢的话可以在pip命令后面加上国内镜像源具体方法网上很多这里不展开。建议你顺手建一个虚拟环境别图省事直接装在系统Python里。我见过太多人装了某个依赖之后把系统环境搞乱回头排查问题时完全无法复现。虚拟环境命令也很简单python -m venv ticket_envWindows下进入虚拟环境执行ticket_env\Scripts\activatemacOS和Linux下执行source ticket_env/bin/activate。后续所有依赖都装在这个环境里脚本跑挂了随时重建不影响其他项目。2.2 浏览器驱动版本匹配是最容易翻车的点Selenium本身不操作浏览器它通过“浏览器驱动”来指挥浏览器干活。Chrome浏览器对应的是ChromeDriver这两者版本必须严格匹配否则一启动就报SessionNotCreatedException这是新手最容易翻车的点。我建议的匹配步骤是这样的先在Chrome地址栏输入chrome://version查看当前浏览器版本号比如 120.x.x.x然后去ChromeDriver的镜像或官方存储库下载对应大版本的驱动包。下载完成后解压得到一个可执行文件Windows下是chromedriver.exe。驱动文件放哪里也有讲究。最简单的做法是放在你项目根目录然后在代码里用Service类指定路径。我自己习惯把驱动放项目根目录这样换电脑或者换项目时驱动跟着代码走不会因为系统PATH路径问题莫名报错。2.3 开发工具配置VSCode和PyCharm怎么选编辑器这块VSCode和PyCharm我都重度用过。VSCode胜在轻量装一个Python扩展后按CtrlShiftP打开命令面板选择解释器时切换到虚拟环境的Python路径就能实现代码补全和调试。PyCharm社区版则是开箱即用新建项目时可以直接指定虚拟环境不用额外配置。有一类报错很典型你在终端里运行脚本一切正常但在编辑器里点运行却报ModuleNotFoundError: No module named selenium。这通常是编辑器的解释器没有指向虚拟环境导致的。解决办法简单粗暴在VSCode右下角或PyCharm设置里把项目解释器切换成你创建的ticket_env下的Python。这个坑我每带一个新人都要解释一遍因为报错提示非常唬人容易被带偏。3. 核心细节Selenium操作网页的五个关键能力3.1 元素定位选对选择器是一切的基础脚本能不能准确操作页面核心在于能不能定位到元素。Selenium提供了find_element系列方法支持ID、Name、Class Name、CSS选择器、XPath等方式。我的经验是优先用ID其次用CSS选择器最后才考虑XPath。为什么ID在页面里通常唯一且稳定定位速度最快。CSS选择器简洁、可读性好比如#username表示id为username的元素.btn-primary表示class包含btn-primary的元素。XPath虽然功能强大但表达式一长就难维护比如/html/body/div[2]/div[3]/form/div[1]/input这种页面结构一调整就失效排查起来头大。定位元素的代码看起来都是这样的from selenium.webdriver.common.by import By username_input driver.find_element(By.ID, username) submit_button driver.find_element(By.CSS_SELECTOR, button.submit)如果元素定位不到不要怀疑是Selenium坏了先用浏览器开发者工具的Elements面板确认页面里到底有没有这个元素再检查是不是元素在一个iframe里面。iframe问题后面专门讲。3.2 等待机制让脚本学会等人网页加载最令人头疼的就是“时机”。元素还没渲染出来你就去点报NoSuchElementException页面还在转圈你就去输入报ElementNotInteractableException。很多人图省事上来就time.sleep(3)但这其实是拿时间换稳定性的笨办法。网络快的时候白白等3秒网络慢的时候3秒还不够。正确做法是用Selenium的显式等待让脚本轮询某个条件满足条件才继续执行from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC wait WebDriverWait(driver, 10) login_button wait.until(EC.element_to_be_clickable((By.ID, login_button))) login_button.click()上面这段代码的意思是最多等10秒每隔一小段时间检查一次登录按钮是否可点击一旦可点击就立即执行点击超时则抛出异常。这比sleep高效得多也让脚本更“聪明”。3.3 输入、点击与下拉选择基本操作也有坑输入和点击是最常用的操作但有几个细节很容易忽略。输入前建议先clear()清空已有内容尤其是那些默认值可能影响搜索结果的输入框。点击之前可以通过is_enabled()判断按钮是否可用避免页面处于禁用状态时强行点击。下拉框的处理也是一个常考点。原生select标签可以用Select类from selenium.webdriver.support.ui import Select Select(driver.find_element(By.ID, passenger_type)).select_by_visible_text(成人)但很多购票网站的“选择乘客”其实是自定义列表不是原生下拉框这时就得点击之后等待列表项出现再点击对应的元素。处理这类动态列表时显式等待就派上用场了。还有一种场景容易被忽略页面很长目标按钮在可视区域之外。直接点击可能报“不可点击”这时候先让元素滚动到视野内from selenium.webdriver.common.action_chains import ActionChains element driver.find_element(By.ID, submit_order) ActionChains(driver).move_to_element(element).perform() element.click()如果说抢票脚本也有“卖油翁式基本功”那输入、点击、滚动这三样就是必须练到肌肉记忆的部分。3.4 窗口、标签页与iframe跳转一个都不能少自动化过程中点击某些按钮会打开新标签页或者弹窗此时Selenium默认还在原来的页面上操作如果不切换上下文元素定位就会失败。处理逻辑是拿到所有窗口句柄逐个匹配并切换handles driver.window_handles driver.switch_to.window(handles[-1])iframe是另一个更容易被忽略的坑。很多登录弹窗其实内嵌在一个iframe里页面结构看似存在但你直接find_element就是找不到。解决办法是先切换进iframedriver.switch_to.frame(driver.find_element(By.CSS_SELECTOR, iframe[src*login]))操作完再切回主文档driver.switch_to.default_content()这个问题在登录环节几乎必现如果你启动脚本后发现“明明页面上有这个元素但代码死活找不到”优先检查是不是iframe在作祟。3.5 文件下载与页面状态控制顺带说说等待下载完成虽然不是抢票核心环节但我发现很多人在自动化里处理“下载文件后继续执行”时总会卡住。抢票场景不涉及下载但类似思路会在爬虫项目里用到。核心是不要只在代码层面点击下载按钮就完事还要检查目标文件是否已经落到磁盘上、大小是否不再变化。可以用一个循环配合文件大小判断import os import time file_path /path/to/download/file.zip while not os.path.exists(file_path): time.sleep(1) size -1 while os.path.getsize(file_path) ! size: size os.path.getsize(file_path) time.sleep(1)这段代码的意思先等文件出现再等文件大小稳定稳定之后才认为下载完成。类似的“检查前置条件再继续”的思路放在抢票脚本里同样适用——比如确认订单信息已经加载出来再执行下一步避免误点。4. 完整实战从登录到下单的抢票核心流程4.1 搭建浏览器对象与基础配置先写一个初始化函数创建浏览器对象并配置相关参数。这里有个比较重要的选择要不要开启无头模式。无头模式不显示浏览器界面资源占用低适合部署在服务器上但排错时看不见页面状态而且某些网站能识别无头浏览器的特征。本地个人用我建议不启用无头模式保持可见窗口把--start-maximized参数加上让浏览器启动即最大化方便你随时观察脚本执行到哪一步。from selenium import webdriver from selenium.webdriver.chrome.service import Service options webdriver.ChromeOptions() options.add_argument(--start-maximized) # options.add_argument(--headless) # 无头模式部署服务器时再打开 service Service(./chromedriver) driver webdriver.Chrome(serviceservice, optionsoptions) driver.get(https://example.com)执行完这段代码浏览器应该能自动打开并跳转到目标站点。如果弹出“Chrome正在受到自动测试软件的控制”提示这是正常现象说明驱动生效了。4.2 登录环节普通输入与验证码的人工预留登录是第一个坎。常规流程是打开登录页、输入账号密码、处理验证码、点击登录。账号密码输入用send_keys即可耗时几乎可以忽略。真正的变量是验证码环节。滑块验证是购票平台最常见的安全验证方式。很多教程会让你去研究滑块轨迹、缺口识别但这既涉及复杂图像算法又容易触发更严格的风控。我的建议是脚本运行到这个环节时暂停下来等人工处理。具体做法很简单在代码里插入一个input()等待username_input driver.find_element(By.ID, username) username_input.send_keys(你的账号) password_input driver.find_element(By.ID, password) password_input.send_keys(你的密码) input(请手动完成滑块验证完成后按回车键继续...) login_button driver.find_element(By.ID, login_button) login_button.click()这样的半自动方式可能看起来“不够酷”但胜在稳定可靠。你手动拖一下滑块只要几秒钟却能让整个脚本避开图像识别和轨迹模拟这个深坑。脚本的核心价值本来就是帮你节省重复操作时间而不是让你去和风控系统硬碰硬。4.3 余票查询日期切换与车次筛选登录成功之后进入查询页面。购票网站的出发地、目的地通常是一个支持自动补全的输入框。输入城市名后需要等待下拉候选列表出现再点击目标选项。直接输入完就查询很可能会查到空数据。有代表性的写法是这样from selenium.webdriver.common.keys import Keys from_input driver.find_element(By.ID, fromStation) from_input.clear() from_input.send_keys(北京) from_input.send_keys(Keys.ENTER) to_input driver.find_element(By.ID, toStation) to_input.clear() to_input.send_keys(上海) to_input.send_keys(Keys.ENTER)日期选择看起来复杂核心仍然是“先点击日期控件再选择具体日期”。要注意的是有些日期控件会动态渲染直接find_element未必能找到想要的那天建议先等待日期面板出现再通过文本定位。定位到之后先判断该日期是否可选有的网站会禁用已过日期。查询按钮点击之后余票列表一般也是异步加载的。这时候用显式等待等待车次行出现是最稳的wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, tr.ticket-row)))接下来遍历每一行根据车次号筛选出你需要的列车。这里的筛选逻辑可以根据自己需求定制比如只保留高铁、只看早上出发的车次、只筛选二等座有票的车次。脚本一旦能自动筛出目标车次你的抢票效率就上了一个台阶。4.4 自动提交与人工确认的配合找到目标车次后点击“预订”或“抢票”按钮进入乘客信息填写页。乘客信息多有姓名、证件类型、证件号码、座位席别等手动填一遍最快也要二三十秒。脚本来做的话就是几次send_keys和几次点击的事这是整个脚本效率提升最明显的环节。但这里我强烈建议保留最终的人工确认步骤。订单提交涉及付款一旦自动点击了“确认下单”并且没有后续拦截你可能连反悔的机会都没有。我的习惯是自动填充乘客信息、自动选择座位席别然后停在这里等你肉眼核对一遍再手动点击提交订单。这样既享受了自动化的速度又避免了信息填错导致的问题。4.5 关键参数与运行节奏频率控制是长期使用的前提脚本跑起来之后很多人容易陷入“拼命刷新”的误区。实际上过于高频的请求只会增加被平台风控的概率导致登录失效甚至IP受限。我在实际使用中会把查询间隔设置在1到3秒之间并且加入随机抖动让访问节奏更接近真人操作import random import time time.sleep(random.uniform(1, 3))既然是春节抢票起售时间前后那几分钟是黄金窗口这段时间可以缩短到1秒左右查一次但不要持续太久。非高峰时段把间隔拉长到5秒以上维持余票监控即可。5. 踩坑记录高频报错与排查手册5.1 找不到元素NoSuchElementException的三种解法这个报错出现频率最高几乎每个刚上手Selenium的人都会遇到。我的排查顺序是先用开发者工具确认元素是否存在如果存在再检查元素是否在iframe中如果不在iframe中再检查是否是因为页面还未加载完成。针对页面未加载完成显式等待是最好的解法。针对iframe切进去再定位。如果以上都排除了就要考虑元素是不是动态ID每次刷新页面都会变化这时需要改用CSS选择器或XPath去匹配稳定的属性。5.2 元素被遮挡或不可点击不只是click那么简单有时候元素明明定位到了但click()就是没反应或者报了ElementClickInterceptedException这通常是因为页面上有一个遮罩层、弹窗或者固定定位的浮层挡住了目标元素。解决办法有两个一是用ActionChains模拟鼠标移动并点击ActionChains(driver).move_to_element(element).click().perform()二是先关掉遮挡元素比如点击弹窗右上角的关闭按钮。我遇到过一种情况页面上有一个“公告浮层”每次都挡住下单按钮代码里花了两行去关闭它问题立马解决。5.3 超时与网络波动重试机制和日志很重要脚本跑在公网上网络抖动是常态。查询超时、订单页面加载失败都是正常现象关键是要有恢复机制。我习惯给关键步骤套一个循环重试from selenium.common.exceptions import TimeoutException for attempt in range(3): try: submit_btn wait.until(EC.element_to_be_clickable((By.ID, submit_order))) submit_btn.click() break except TimeoutException: print(f第{attempt 1}次尝试失败准备刷新重试...) driver.refresh() time.sleep(2)与此同时日志一定要打。别等脚本跑挂了再一头雾水地猜它到底执行到哪一步。用Python自带的logging模块记录关键节点import logging logging.basicConfig(filenameticket.log, levellogging.INFO, format%(asctime)s %(message)s) logging.info(已进入订单确认页)日志文件会告诉你脚本每次卡在哪、崩溃前干了什么排查效率能提升一个数量级。5.4 滑块验证合规处理思路与心态滑块验证码被很多自动化教程当成一个“挑战”但我不建议深入学习自动过滑块的技术。一方面它涉及缺口识别、轨迹拟合、速度模拟等一系列工程化问题学起来成本高另一方面自动化破解各类验证码本身处在平台规则的灰色地带。最稳妥、也最省心的策略就是前面提到的把滑块验证的环节交给人工脚本负责等待验证完成。你在运行脚本时眼睛本来就要盯着屏幕顺手拉一下滑块完全不影响效率但能省掉大量开发和排错时间风险也低得多。如果你是纯粹想学习Selenium的滑块处理技术建议用自己的测试环境或公开的演示站点练习不要拿真实购票系统的验证码当靶子。6. 一些实在的个人建议脚本跑通之后先说一句自动化操作可能违反目标网站的使用条款使用前务必阅读平台规定控制好频率把它当作便利工具而不是暴力工具。这几年的实际体验让我觉得“能不能抢到票”最终拼的还是起售时间、网络延迟和运气脚本最大的贡献是把你在浏览器前重复机械操作的几分钟压缩成了几秒钟把“万一有票我却没看到”这种遗憾降到最低。还有一个小建议第一次跑完整流程时建议选一个人少的时段手动配合跑一遍把账号登录、乘客信息、订单提交全流程走通确认没有隐藏的弹窗和异常。别等到抢票当天才第一次打开脚本那时候遇到问题会非常慌。最后不论脚本最终跑得顺不顺都要留好人工购票的方案。毕竟春节回家这件事技术的意义是让过程更从容而不是让依赖技术的人陷入另一种焦虑。祝你顺利买到票。