ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python+Selenium抢票脚本实战:环境搭建、登录复用与订单提交全解析

Python+Selenium抢票脚本实战:环境搭建、登录复用与订单提交全解析 1. 抢票脚本的真实定位与核心思路拆解先把话说在前头任何声称“100%成功”的抢票脚本本质上都是在贩卖焦虑。12306的票池是动态的能不能抢到票取决于你的网络延迟、提交时机、候补队列位置以及当天放票的批次策略脚本能做的只是把“人工刷新-点击-提交”这个循环压缩到毫秒级减少手速和注意力带来的损耗。我自己用Python配合Selenium写过几版抢票工具也帮朋友排查过不少跑不起来的脚本踩过的坑比抢到的票多得多。这篇文章就把整套思路、代码骨架、参数调优和避坑经验完整拆开讲适合已经装好Python、懂一点基础语法、想自己动手折腾的读者也适合纯粹想了解自动化测试框架怎么落地到实际场景的人。1.1 为什么选Selenium而不是纯requests很多人第一反应是用requests直接怼接口觉得那样更快。理论上没错但12306的接口有复杂的加密参数、动态token和风控校验纯requests方案需要逆向JS加密逻辑维护成本极高一旦前端改版就得重新逆向。Selenium走的是浏览器真实渲染路线你看到什么它就操作什么稳定性反而更好。代价是速度慢一些但配合合理的等待策略和元素定位优化完全够用。提示Selenium本质是自动化测试工具用它做抢票属于“非典型用法”请控制请求频率避免对公共资源造成压力。1.2 整体架构分层我把脚本分成四层配置层负责账号、车次、日期、乘客信息驱动层负责启动浏览器、加载WebDriver操作层负责登录、查询、提交订单监控层负责日志记录和异常重试。分层的好处是改一个参数不用动核心逻辑比如换车次只改配置换浏览器只改驱动层。1.3 核心难点预判登录环节有滑块验证查询环节有频率限制提交环节有排队机制。这三个点是脚本能否跑通的关键。我的策略是登录尽量走扫码或已保存的Cookie查询用固定间隔加随机抖动提交阶段用显式等待锁定按钮可点击状态。下面会逐一展开。2. 环境搭建与WebDriver版本匹配实操环境问题是新手卡住的第一道坎尤其是WebDriver和浏览器版本对不上报错信息还特别模糊。我见过太多人在这里耗掉一整天。2.1 Python环境与依赖安装假设你已经装好Python 3.8以上版本打开终端执行pip install selenium pip install requests pip install loguruloguru是我个人习惯用的日志库比原生logging省事输出带时间戳和颜色排查问题时一眼就能看到哪一步挂了。如果你不想多装依赖用print也行但正式跑的时候日志文件更靠谱。2.2 ChromeDriver版本匹配的坑热词里有人问“有没有谷歌浏览器版本为138.0.7204.169的webdriver”这个问题非常典型。ChromeDriver必须和Chrome主版本号一致138的浏览器就得配138的驱动。查看浏览器版本的方法地址栏输入chrome://version第一行就是完整版本号。下载地址是Chrome for Testing的官方页面选对应平台的压缩包解压后把可执行文件放到Python脚本同目录或者加入系统PATH。我习惯放同目录避免污染全局环境。from selenium import webdriver from selenium.webdriver.chrome.service import Service service Service(executable_path./chromedriver) options webdriver.ChromeOptions() driver webdriver.Chrome(serviceservice, optionsoptions)如果你用的是Selenium 4.6以上版本其实可以省略手动下载驱动它会自动管理。但自动管理有时会抽风尤其是网络不好的时候所以我还是推荐手动指定路径心里有底。2.3 浏览器选项调优默认启动的浏览器窗口小、加载慢加几个参数能明显改善options.add_argument(--disable-blink-featuresAutomationControlled) options.add_argument(--start-maximized) options.add_argument(--disable-infobars) options.add_experimental_option(excludeSwitches, [enable-automation]) options.add_experimental_option(useAutomationExtension, False)disable-blink-features这个参数是为了去掉部分自动化特征让页面行为更接近正常浏览。注意这不是用来绕过什么限制只是减少不必要的弹窗干扰。2.4 验证环境是否就绪写个最小测试脚本打开12306首页并打印标题driver.get(https://www.12306.cn) print(driver.title) driver.quit()能正常打印出标题就说明环境通了。如果报SessionNotCreatedException九成是驱动版本不匹配如果报连接超时检查网络和代理设置。3. 登录环节的自动化处理与Cookie复用登录是抢票脚本的第一道门槛也是最容易失败的地方。我的建议是能扫码就扫码能复用Cookie就复用不要硬刚账号密码加滑块。3.1 扫码登录的自动化思路12306支持扫码登录二维码在页面上是一个img元素。脚本可以截取二维码区域保存为图片然后人工用手机扫。听起来不够“自动”但实际最稳。因为滑块验证的通过率受网络和风控影响很大扫码一次能管很久。driver.get(https://kyfw.12306.cn/otn/resources/login.html) # 等待二维码加载 qr_code WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CLASS_NAME, qrcode-img)) ) qr_code.screenshot(qr.png) print(请扫描qr.png完成登录)截图后手动扫码登录成功后脚本继续执行。这个方案的好处是绕开了密码输入和滑块稳定性极高。3.2 Cookie持久化复用登录成功后把Cookie保存到本地文件下次直接加载省去扫码步骤import pickle # 保存 pickle.dump(driver.get_cookies(), open(cookies.pkl, wb)) # 加载 cookies pickle.load(open(cookies.pkl, rb)) for cookie in cookies: driver.add_cookie(cookie) driver.refresh()注意Cookie有有效期一般几天到几周不等。如果加载后跳回登录页说明失效了重新扫码即可。3.3 登录状态检测加载Cookie后要验证是否真的登录成功不能盲目往下跑。检测方法很简单访问个人中心页面看是否跳转到登录页。driver.get(https://kyfw.12306.cn/otn/view/index.html) if login in driver.current_url: print(Cookie失效需要重新登录) else: print(登录状态正常)这个检测步骤一定要加否则后面查询和提交全是白费功夫。3.4 常见登录异常处理有时候页面加载慢元素还没出来就去找直接抛NoSuchElementException。解决办法是统一用WebDriverWait加显式等待超时时间设10到15秒。另外如果频繁登录失败可能是触发了风控建议停一段时间再试不要死循环重试。4. 车次查询与提交订单的核心逻辑登录搞定后重头戏是查询和提交。这部分逻辑不复杂但细节决定成败。4.1 查询参数构造12306的查询页面URL带参数直接拼接比在页面上点选更可靠query_url ( https://kyfw.12306.cn/otn/leftTicket/init ?linktypeiddc ffs{from_station} fts{to_station} fdate{travel_date} flagN,N,Y ) driver.get(query_url)车站代码要用三字码比如北京是BJP上海是SHH。这个代码表网上能查到建议提前存成字典。4.2 车次信息解析页面加载后车次列表在table里。用XPath定位每一行提取车次号、出发时间、余票状态rows driver.find_elements(By.XPATH, //tr[contains(id, ticket_)]) for row in rows: train_no row.find_element(By.CLASS_NAME, number).text if train_no target_train: # 找到目标车次 book_btn row.find_element(By.CLASS_NAME, btn72) if book_btn.is_enabled(): book_btn.click() breakbtn72是预订按钮的类名有票时是可点击状态无票时是灰色。判断is_enabled()能避免点到无效按钮。4.3 提交订单的等待策略点击预订后进入确认页面需要选择乘客、席别然后提交。这里的关键是显式等待按钮可点击而不是固定sleepsubmit_btn WebDriverWait(driver, 5).until( EC.element_to_be_clickable((By.ID, submitOrder_id)) ) submit_btn.click()固定sleep的问题是设短了元素没出来设长了浪费时间。显式等待是元素一出现就继续效率最高。4.4 循环重试与频率控制抢票本质是循环查询直到有票为止。但循环不能太猛否则会被限流。我的做法是每次查询间隔1.5到2.5秒随机模拟人工操作节奏import random, time while True: # 查询逻辑 if ticket_found: break time.sleep(random.uniform(1.5, 2.5))提示间隔时间不要低于1秒否则容易触发风控反而得不偿失。4.5 候补与直达的取舍如果直达票一直抢不到可以同步挂候补。候补是官方机制成功率取决于排队位置脚本能做的是尽早提交候补订单。我的经验是放票瞬间先抢直达抢不到立刻转候补两条线并行。5. 常见报错与排查速查表跑脚本的过程中会遇到各种报错这里整理一份速查表方便对照排查。报错信息可能原因解决办法SessionNotCreatedException驱动版本不匹配下载对应版本ChromeDriverNoSuchElementException元素未加载或定位错误加显式等待检查XPathElementClickInterceptedException元素被遮挡滚动到元素位置再点击TimeoutException等待超时增加超时时间或检查网络StaleElementReferenceException页面刷新导致元素失效重新定位元素登录后跳回登录页Cookie失效重新扫码登录5.1 元素定位失效的应对12306前端偶尔改版类名或ID会变。应对方法是把定位器集中写在配置里改版时只改一处。另外优先用相对稳定的属性比如id比class稳定name比xpath稳定。5.2 点击无效的排查有时候按钮明明找到了click却没反应。常见原因是元素不在可视区域或者被弹窗遮挡。解决办法是先scrollIntoView再检查有没有弹窗需要关闭。driver.execute_script(arguments[0].scrollIntoView();, button) time.sleep(0.5) button.click()5.3 脚本跑着跑着卡死多半是某个等待没有超时限制或者页面弹出了未处理的对话框。给所有等待加timeout参数并定期检查driver.window_handles处理意外弹出的窗口。6. 实操心得与避坑经验这部分是我自己踩坑攒下来的文档里不会写但实际跑的时候特别有用。6.1 不要迷信“100%成功”任何脚本都受制于票池和网络放票瞬间几万人同时提交脚本的优势只是快那么几十毫秒。心态要放平脚本是辅助不是保证。6.2 提前测试完整流程不要等到放票当天才第一次跑脚本。提前用非高峰时段的车次走一遍完整流程确认登录、查询、提交、支付每个环节都通。我见过太多人放票时才发现某个按钮定位错了白白错过。6.3 日志要详细每个关键步骤都打日志包括时间戳、当前URL、操作结果。出问题时看日志能快速定位是哪一步挂了。loguru的logger.info和logger.error配合使用排查效率翻倍。6.4 支付环节别自动化提交订单后支付环节建议手动完成。一方面支付涉及资金安全另一方面支付页面有额外的验证自动化容易出问题。脚本跑到“订单提交成功”就可以停了。6.5 尊重公共资源脚本查询频率要克制不要开几十个线程同时怼。12306是公共服务合理使用是底线。我一般单线程跑间隔2秒左右既不影响别人自己也能抢到。6.6 备选方案要准备脚本不是万能的直达抢不到就候补候补不行就换中转中转不行就换日期。多准备几套方案比死磕一个车次强。7. 后续可扩展的方向这套脚本骨架跑通后可以往几个方向扩展。一是加GUI界面用tkinter或PyQt做个简单的配置窗口不用改代码就能换车次。二是加通知功能抢到票后发邮件或推送提醒。三是把配置抽成JSON文件方便管理多个车次和乘客。四是研究官方的候补机制把候补提交也纳入自动化流程。这些扩展都不难核心逻辑不变只是外围包装。我个人在实际操作中的体会是脚本的价值不在于“抢到票”这个结果而在于把重复劳动交给机器让自己从刷新页面的焦虑中解放出来。把精力放在方案规划和备选准备上比盯着屏幕刷票有用得多。最后再分享一个小技巧放票前5分钟启动脚本提前登录好放票瞬间脚本的响应速度比手动快一个数量级这个时间差往往就是成败的关键。
返回列表