尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Python自动化抢票脚本实战:从Selenium到反爬策略

Python自动化抢票脚本实战:从Selenium到反爬策略 1. 从手动刷新到自动化为什么我们需要一个抢票脚本又到了演唱会门票开售的日子你提前十分钟就坐在电脑前手机、平板、电脑三端齐开浏览器标签页排满了大麦、猫眼、票星球。时间一到你疯狂点击那个灰色的“立即购买”按钮页面却纹丝不动或者直接卡在加载中。几秒钟后页面刷新硕大的“缺货登记”四个字映入眼帘你只能眼睁睁看着黄牛票在二手平台加价数倍。这种经历相信每一个热爱现场音乐的人都深有体会。问题的核心在于热门演唱会的票务发售是一场毫秒级的“战争”。成千上万的用户在同一时刻涌向服务器手动操作的速度和精度在机器面前几乎不值一提。网络延迟、页面渲染时间、人类反应时间每一个环节都在拖后腿。而一个用Python编写的自动化抢票脚本其本质就是模拟一个“超级用户”的行为它能以远超人类的速度完成登录、轮询、点击、提交等一系列操作并且不知疲倦精准无误。我最初写这类脚本纯粹是因为自己屡战屡败。后来发现这不仅仅是“插队”工具更是一个绝佳的Python实战项目。它综合运用了网络请求requests、HTML解析BeautifulSoup/lxml、浏览器自动化selenium、定时任务、异常处理等多个核心知识点。通过这个项目你能深刻理解HTTP协议、会话保持、反爬虫机制以及如何编写健壮的、面向真实业务的自动化程序。今天我就把自己在多次实战包括成功和失败中积累的经验和代码思路毫无保留地分享出来。请注意我们的目标是学习自动化技术和Python编程所有操作都应遵守相关平台的使用条款仅用于个人学习研究切勿用于恶意刷票或干扰正常服务。2. 技术选型与核心工具链requests还是selenium在动手之前第一个关键决策是技术路线的选择。这直接决定了脚本的复杂度、稳定性和运行效率。主流方案有两种纯HTTP请求如requests库和浏览器自动化如selenium。2.1 纯HTTP请求方案快如闪电的“狙击手”这种方案的核心是直接模拟浏览器向服务器发送HTTP请求完全绕过图形界面。它的优点是速度极快、资源占用低适合在服务器或后台长期运行。核心工具包requests: 用于发送所有HTTP请求GET/POST管理cookies和会话Session。BeautifulSoup4/lxml: 用于解析返回的HTML页面提取票务状态、价格、场次等关键信息。json: 大麦网很多动态数据如场次、票价列表是通过Ajax请求返回的JSON格式需要直接解析。工作原理分析网络请求使用Chrome/Firefox的开发者工具F12切换到Network网络标签页。手动在网站上完成一次完整的浏览、选座、提交订单但不支付的操作。仔细观察记录下每一个关键的请求特别是那些返回JSON数据的XHR/Fetch请求。构造请求用requests.Session()保持登录状态。然后按照分析出的请求顺序和参数用代码复现这些请求。例如先请求页面获取令牌token再请求场次信息最后提交购票请求。解析与判断解析服务器返回的数据判断是否有票、是否成功下单。注意这是技术难度较高但效率最高的方式。大麦网等平台有复杂的反爬机制如加密参数、动态令牌、请求签名需要耐心逆向分析。直接复制粘贴别人的代码很可能因为网站改版而失效。2.2 浏览器自动化方案高度模拟的“替身演员”这种方案通过程序控制一个真实的浏览器如Chrome进行操作完全模拟人的行为。优点是绕过前端加密逻辑相对简单因为浏览器本身会执行JavaScript生成必要的参数缺点是速度慢资源占用高更易被检测。核心工具包selenium: 自动化浏览器的核心库。webdriver-manager: 自动管理浏览器驱动如chromedriver的版本避免手动下载和路径配置的麻烦。undetected-chromedriver: 一个增强库可以更好地隐藏自动化特征降低被网站识别为机器人的概率。工作原理启动浏览器通过selenium启动一个浏览器实例。模拟操作使用代码控制浏览器打开网页、输入账号密码登录、点击页面元素、选择场次和票价、点击购买按钮。元素定位使用XPath、CSS Selector等方式定位页面上的按钮、输入框等元素。我的经验与选型建议对于新手或者目标网站前端逻辑极其复杂、难以逆向的情况我强烈建议从selenium方案入手。它让你能快速看到脚本运行效果建立信心并且更贴近“可见即可得”的编程思维。虽然速度不如纯requests方案但在个人网络环境尚可的情况下应对非极端热门的场次开售几分钟内仍有票的成功率已经远高于手动操作。在本篇分享中我将以**selenium方案**为主线进行讲解因为它更直观更适合教学和快速验证。同时我也会在关键环节指出如果采用requests方案需要关注哪些点。3. 环境搭建与基础配置避开第一个坑工欲善其事必先利其器。环境配置是第一步也是最容易出问题的一步。3.1 Python环境与依赖安装首先确保你安装了Python3.7及以上版本。在命令行中使用pip安装必要的库。我建议使用虚拟环境venv来管理依赖避免污染全局环境。# 创建并激活虚拟环境Windows python -m venv venv venv\Scripts\activate # 创建并激活虚拟环境macOS/Linux python3 -m venv venv source venv/bin/activate # 安装核心库 pip install selenium webdriver-manager undetected-chromedriver # 可选用于解析和等待 pip install beautifulsoup4 lxml3.2 浏览器驱动与反检测配置这是selenium使用的关键。webdriver-manager可以自动处理驱动下载和匹配。import undetected_chromedriver as uc from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import time def create_driver(): options uc.ChromeOptions() # 关键配置让浏览器更像真人操作 # 1. 禁用自动化控制特征重要反检测手段 options.add_argument(--disable-blink-featuresAutomationControlled) # 2. 禁用开发者模式扩展避免暴露 options.add_experimental_option(excludeSwitches, [enable-automation]) options.add_experimental_option(useAutomationExtension, False) # 3. 其他实用参数 # options.add_argument(--headless) # 无头模式不显示浏览器界面运行更快但可能更易被识别 options.add_argument(--disable-gpu) # 禁用GPU加速某些环境下更稳定 options.add_argument(--no-sandbox) # Linux环境下可能需要 options.add_argument(--disable-dev-shm-usage) # 解决共享内存问题 # 使用undetected_chromedriver启动它能更好地绕过检测 driver uc.Chrome(optionsoptions) # 执行CDP命令进一步隐藏WebDriver属性 driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, { source: Object.defineProperty(navigator, webdriver, { get: () undefined }); }) return driver # 初始化浏览器 driver create_driver()实操心得undetected_chromedriver是目前对抗网站自动化检测比较有效的方案之一但不是银弹。如果网站更新了检测策略可能仍需调整。另外谨慎使用无头模式--headless很多网站会专门检测无头浏览器。初期调试时建议关闭无头模式亲眼看着脚本运行便于定位问题。4. 脚本核心逻辑拆解从登录到提交订单一个完整的抢票脚本可以分解为以下几个核心步骤。我们以大麦网为例但思路是通用的。4.1 登录环节保持会话是关键登录是后续所有操作的基础。大麦网登录可能有滑块验证码这会给自动化带来巨大挑战。方案A手动登录后复用Cookies推荐这是最稳定、最省事的方法尤其适合有复杂验证码的网站。思路是第一次手动登录将浏览器产生的cookies保存下来后续脚本运行时直接加载cookies跳过登录环节。import pickle import os COOKIES_FILE damai_cookies.pkl def save_cookies(driver): 保存当前浏览器的cookies到文件 cookies driver.get_cookies() with open(COOKIES_FILE, wb) as f: pickle.dump(cookies, f) print(Cookies已保存) def load_cookies(driver): 从文件加载cookies到浏览器 if not os.path.exists(COOKIES_FILE): return False with open(COOKIES_FILE, rb) as f: cookies pickle.load(f) # 需要先打开目标网站域名下的任意页面才能设置cookies driver.get(https://www.damai.cn/) time.sleep(2) for cookie in cookies: # 有些cookie字段如sameSiteselenium可能不支持需要处理 try: driver.add_cookie(cookie) except Exception as e: print(f添加cookie时出错: {e}) print(Cookies已加载) return True # 使用示例 driver.get(https://passport.damai.cn/login?ruhttps://www.damai.cn/) print(请手动完成登录...) input(登录完成后按回车键继续...) save_cookies(driver) # 下次运行脚本时 if load_cookies(driver): driver.refresh() # 刷新页面使cookies生效 # 检查是否登录成功例如查找用户昵称元素 try: WebDriverWait(driver, 5).until( EC.presence_of_element_located((By.CLASS_NAME, user-name)) ) print(登录成功) except: print(Cookies可能已失效需要重新登录。)方案B自动化处理登录不推荐用于复杂验证码如果必须自动化登录需要处理账号密码输入和可能的验证码。验证码是最大的障碍简单的图形验证码可以用OCR库如ddddocr尝试识别但滑动验证码如极验破解难度极大通常需要购买打码服务或使用更复杂的图像识别方案这超出了个人学习项目的范畴且存在法律和道德风险。4.2 票务状态轮询与抢票时机登录成功后就进入核心的抢票循环。策略是在开售时间前不断刷新页面监控“立即购买”按钮的状态变化。def wait_for_ticket(driver, target_url, start_time): 等待票务开售 :param driver: 浏览器驱动 :param target_url: 演唱会详情页URL :param start_time: 开售时间的时间戳 driver.get(target_url) # 计算等待时间 current_time time.time() if start_time current_time: sleep_seconds start_time - current_time - 3 # 提前3秒开始准备 if sleep_seconds 0: print(f离开售还有{sleep_seconds:.0f}秒等待中...) time.sleep(sleep_seconds) # 开始高频轮询和刷新 print(进入抢票状态...) buy_button None retry_count 0 max_retries 100 # 最大重试次数避免无限循环 while retry_count max_retries and buy_button is None: retry_count 1 try: # 尝试定位“立即购买”或“选座购买”按钮 # 注意大麦网的按钮class或id可能会变需要实时分析 # 通常可能是buy-btn, buy-link, buy-now 或是一个div # 使用更通用的XPath或CSS选择器 buy_button WebDriverWait(driver, 0.5).until( # 超时时间设短快速轮询 EC.element_to_be_clickable((By.XPATH, //button[contains(text(), 立即购买) or contains(text(), 选座购买)])) ) print(发现可点击的购票按钮) break except: # 没找到按钮刷新页面 driver.refresh() print(f第{retry_count}次刷新页面...) time.sleep(0.1) # 刷新间隔不要太快以免被封IP if buy_button: return buy_button else: print(在最大重试次数内未找到购票按钮。) return None关键技巧开售前3-5秒开始高频刷新是一个经验值。太早刷新页面可能还是“即将开售”的静态页面太晚则已经落后。这个时间需要根据目标网站服务器时间和自己网络延迟微调。time.sleep(0.1)这样的短间隔刷新比固定1秒刷新更能抓住时机。4.3 处理场次、票价与购买数量选择找到购票按钮只是第一步。点击后通常会跳转到选择“场次”、“票价”和“购买数量”的页面。这里的元素定位是难点因为页面结构复杂且可能动态加载。def select_ticket_options(driver): 选择场次、票价和数量 try: # 1. 选择场次假设第一个可选的场次 # 场次通常是一个列表class可能包含skuitem或sku-item WebDriverWait(driver, 5).until( EC.presence_of_all_elements_located((By.CLASS_NAME, skuitem)) ) session_items driver.find_elements(By.CLASS_NAME, skuitem) if session_items: # 通常第一个是可选的或者根据文本选择特定场次 for item in session_items: if 缺货 not in item.text and 无票 not in item.text: item.click() print(f已选择场次{item.text}) break else: print(所有场次都缺货或无票) return False time.sleep(0.5) # 2. 选择票价同样选择第一个可选的票价档位 price_items driver.find_elements(By.CLASS_NAME, sku-tickets) # 票价元素class可能不同 # 或者通过更精确的XPath定位 # price_items driver.find_elements(By.XPATH, //div[classticket-list]//div[contains(class, item)]) if price_items: for item in price_items: if 缺货 not in item.text and 无票 not in item.text and 售罄 not in item.text: item.click() print(f已选择票价{item.text}) break else: print(所有票价档位都缺货) return False time.sleep(0.5) # 3. 选择购买数量通常有“”按钮或输入框 try: # 方式一点击“”按钮增加数量 plus_btn driver.find_element(By.CLASS_NAME, btn-plus) # class名仅为示例 for _ in range(1): # 假设买1张如果要买2张就循环1次从1加到2 plus_btn.click() time.sleep(0.1) print(已设置购买数量。) except: # 方式二直接操作数量输入框 qty_input driver.find_element(By.XPATH, //input[typenumber and contains(class, quantity)]) driver.execute_script(arguments[0].value arguments[1];, qty_input, 1) # 用JS直接设置值 print(已通过JS设置购买数量。) # 4. 点击“确定”或“提交”按钮进入下一步 confirm_btn WebDriverWait(driver, 3).until( EC.element_to_be_clickable((By.XPATH, //button[contains(text(), 确定) or contains(text(), 提交)])) ) confirm_btn.click() print(已提交票务选择。) return True except Exception as e: print(f选择票务选项时出现错误{e}) # 这里可以截图保存方便调试 driver.save_screenshot(error_select_options.png) return False踩坑实录这个环节最容易因为页面元素加载延迟或动态ID/Class而失败。WebDriverWait配合expected_conditions是必须的。但有时即使元素出现了点击也可能无效这可能是因为元素被其他透明层遮挡或者需要先触发某个事件。此时可以尝试用driver.execute_script(arguments[0].click();, element)来通过JavaScript直接点击往往更可靠。4.4 提交订单与最终确认完成票务选择后会进入订单确认页面。这里需要核对信息并点击最终的“提交订单”按钮。def submit_order(driver): 提交订单 try: # 等待订单页面加载完成通常会有“提交订单”按钮 # 大麦网的按钮可能是submit-btn, go-pay submit_btn WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.ID, submit-btn)) # 根据实际情况修改选择器 ) # 在点击前可以快速检查一下订单信息如票价、数量 # 这里只是示例实际需要根据页面结构定位信息元素 # order_info driver.find_element(By.CLASS_NAME, order-info).text # print(f订单信息{order_info}) # 关键一击点击提交订单 print(准备提交订单...) # 再次使用JS点击确保成功 driver.execute_script(arguments[0].click();, submit_btn) print(已点击提交订单按钮) # 提交后通常会跳转到支付页面或出现成功提示 # 等待一个支付页面特有的元素出现作为成功标志 WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.XPATH, //div[contains(text(), 支付) or contains(text(), 支付宝) or contains(text(), 微信)])) ) print(抢票成功请尽快完成支付) return True except Exception as e: print(f提交订单时出现错误{e}) driver.save_screenshot(error_submit_order.png) return False5. 异常处理与脚本健壮性让程序更“聪明”一个只会按部就班执行的脚本是脆弱的。真实的网络环境和网页交互充满不确定性必须加入完善的异常处理和重试机制。5.1 网络异常与元素定位失败from selenium.common.exceptions import TimeoutException, NoSuchElementException, ElementClickInterceptedException, StaleElementReferenceException import traceback def robust_click(driver, by, value, max_retries3): 一个健壮的点击函数包含多种异常处理和重试 for i in range(max_retries): try: element WebDriverWait(driver, 5).until( EC.element_to_be_clickable((by, value)) ) element.click() return True except TimeoutException: print(f第{i1}次尝试等待元素[{value}]超时。) if i max_retries - 1: raise time.sleep(1) except ElementClickInterceptedException: print(f第{i1}次尝试元素[{value}]被遮挡。尝试用JS点击。) try: element driver.find_element(by, value) driver.execute_script(arguments[0].click();, element) return True except Exception as e: print(fJS点击也失败{e}) except StaleElementReferenceException: print(f第{i1}次尝试元素[{value}]状态过期刷新后重试。) # 元素可能已经从DOM中移除需要重新查找 time.sleep(0.5) return False5.2 主流程的容错与状态恢复将整个抢票流程封装在一个函数中并加入多层try-except和状态判断。def main_ticket_grabbing(target_url, start_timestamp): driver None try: driver create_driver() # 1. 尝试加载cookies登录 if not load_cookies(driver): print(未找到有效cookies需要手动登录。) # 这里可以调用手动登录函数或者直接退出 return # 2. 等待并抢票 buy_btn wait_for_ticket(driver, target_url, start_timestamp) if not buy_btn: print(未能进入购买页面。) return # 使用健壮的点击函数 if robust_click(driver, By.XPATH, //button[contains(text(), 立即购买)]): print(成功点击购票按钮进入选择页面。) else: print(点击购票按钮失败。) return # 3. 选择票务选项 time.sleep(2) # 等待页面跳转 if not select_ticket_options(driver): print(选择票务选项失败。) return # 4. 提交订单 time.sleep(2) # 等待订单页面加载 if submit_order(driver): print(恭喜订单提交成功请尽快支付) # 成功后可选择自动播放提示音或发送通知 # os.system(say 抢票成功请支付) # macOS # 或使用playsound库播放音频文件 else: print(订单提交失败。) except Exception as e: print(f抢票过程发生未知错误{e}) traceback.print_exc() # 打印详细错误堆栈便于调试 finally: if driver: # 可以暂时不关闭浏览器方便手动检查支付 # driver.quit() print(脚本执行结束浏览器窗口保持打开。) input(按回车键关闭浏览器...) driver.quit() # 使用示例 if __name__ __main__: # 目标演唱会详情页URL show_url https://detail.damai.cn/item.htm?id具体项目ID # 开售时间时间戳格式 sale_time 1743451200 # 示例2025-04-01 20:00:00 main_ticket_grabbing(show_url, sale_time)6. 进阶优化与对抗策略在猫鼠游戏中生存平台不会坐视自动化脚本泛滥他们会升级反爬措施。我们的脚本也需要不断进化。6.1 请求随机化与人性化操作模拟完全规律的请求是机器人的典型特征。我们需要引入随机性。import random def human_like_delay(base1.0, variance0.5): 模拟人类操作的不确定延迟 delay base random.uniform(-variance, variance) delay max(0.2, delay) # 确保延迟不为负或过小 time.sleep(delay) def human_like_mouse_move(driver, element): 模拟人类鼠标移动轨迹粗略模拟 # 获取元素位置 location element.location size element.size center_x location[x] size[width] / 2 center_y location[y] size[height] / 2 # 可以在这里加入更复杂的移动轨迹算法如贝塞尔曲线 # 简单示例先移动到大致区域再微调到中心 actions ActionChains(driver) actions.move_by_offset(random.randint(100, 300), random.randint(100, 300)) # 随机起始偏移 actions.pause(random.uniform(0.1, 0.3)) actions.move_to_element(element) actions.pause(random.uniform(0.05, 0.15)) actions.perform()在关键点击操作前可以调用human_like_delay()和human_like_mouse_move()来增加拟真度。6.2 多账号与分布式协作高级话题对于极端热门的场次单一脚本和账号的成功率有限。更复杂的方案会涉及账号池管理多个账号的cookies轮流或随机使用避免单个账号请求过于频繁。任务队列使用Redis等消息队列将抢票任务分发到多个运行脚本的终端或服务器。IP代理池使用高质量的住宅IP代理避免因本地IP请求频率过高被封锁。这里必须强调使用代理必须合法合规不得用于攻击或干扰服务。这些方案复杂度呈指数级上升涉及网络、并发、数据同步等多方面知识更适合作为分布式系统学习的课题而非单纯的抢票脚本。6.3 监控与通知集成脚本运行时你不可能一直盯着屏幕。集成通知功能很重要。# 示例使用桌面通知plyer库 from plyer import notification def send_desktop_notification(title, message): try: notification.notify( titletitle, messagemessage, app_nameTicket Grabber, timeout10 ) except: print(桌面通知发送失败请检查环境。) # 在抢票成功或失败的关键节点调用 # send_desktop_notification(抢票脚本, 已成功提交订单请速去支付)也可以集成邮件、钉钉、微信机器人等通知方式确保第一时间获知结果。7. 法律、道德与风险规避必须划清的界限在分享技术的同时我必须郑重强调其边界。遵守服务条款大麦网等平台的用户协议通常明确禁止使用自动化程序抢票。使用脚本存在账号被封禁的风险。本分享仅供个人学习Python网络爬虫和自动化技术之用。拒绝商业与黄牛行为绝对不要将此类脚本用于大规模、商业化的抢票然后加价转售。这不仅是违反平台规则更可能涉嫌违法扰乱市场秩序损害其他消费者的权益。控制请求频率在编写脚本时务必加入合理的延迟如time.sleep避免对目标服务器造成DoS攻击式的压力。我们的目的是模拟真人操作而非攻击网站。尊重数据版权脚本获取的数据仅用于个人完成购票流程不得进行非法采集、存储、分析或用于其他商业用途。技术是一把双刃剑。我分享这些代码和思路是希望你能从中学习到selenium自动化、网络请求分析、异常处理等实实在在的编程技能并将其应用到更广阔的正向领域比如自动化测试、数据采集在合法合规前提下、日常办公自动化等。当你通过自己写的脚本成功抢到心仪演唱会门票的那一刻获得的不仅是门票更是解决问题的能力和编程带来的巨大成就感。这才是这个项目最大的价值。
返回列表