从零到一掌握:基于Selenium的大麦网自动抢票脚本全攻略
在演出票务抢购场景中,大麦网的高并发访问与严格反爬机制,让手动抢票的成功率微乎其微。基于Selenium的自动抢票脚本,能通过模拟人工操作提升响应速度,但传统脚本常面临易被风控、元素定位失效、功能单一等痛点。本文带来优化增强版脚本全攻略,从核心优势、环境搭建、脚本使用到技术原理,带你一站式掌握高效、稳定的大麦网抢票技巧。
一、优化核心:四大维度破解传统脚本痛点
传统大麦网抢票脚本往往存在“风控易触发、稳定性差、灵活性不足、体验不佳”四大问题。本次优化版脚本围绕这四大痛点精准突破,核心优势集中体现在四个维度:在反风控能力上,通过完善浏览器指纹伪装、模拟人工随机操作间隔、优化页面刷新策略,彻底摆脱“自动化浏览器”标签,降低账号封禁风险;在稳定性上,采用多定位器备份机制与通用重试装饰器,即使大麦网页面元素更新,脚本也能通过备选定位逻辑正常运行,同时避免网络卡顿导致的操作失效;在功能灵活性上,支持多场次/多票档候选列表、购票人自由切换、抢票模式自定义,解决传统脚本“单一选择售罄即终止”的弊端;在用户体验上,通过独立配置文件、结构化日志输出、可视化倒计时进度条,让新手无需修改核心代码,仅需简单配置即可上手。
二、前置准备:环境搭建与依赖安装
优化版脚本基于Python3.8+Chrome浏览器开发,兼容性最优,新手建议直接采用此组合。首先需完成环境依赖安装,核心依赖包括Selenium自动化框架、webdriver-manager驱动管理工具,新增日志美化与进度条相关包提升体验,具体安装命令如下:
# 核心依赖(自动化框架+驱动管理)
pip install selenium webdriver-manager
# 新增依赖(日志美化+进度条可视化)
pip install logging beautifulsoup4 tqdm安装完成后,无需手动配置浏览器驱动——webdriver-manager会自动识别Chrome版本并下载适配驱动,彻底解决传统脚本“驱动版本不匹配”的高频问题。
三、脚本架构:清晰分离,新手易上手
优化版脚本采用“配置与逻辑分离”的架构,目录结构极简,用户仅需修改配置文件,无需触碰核心抢票逻辑,大幅降低使用门槛。具体目录结构如下:
damai_ticket/
├── config.py # 独立配置文件(仅需修改此处)
└── ticket_grabber.py # 核心抢票逻辑(无需修改)1. 完整配置文件config.py(可直接复制使用)
# ======================== 抢票核心配置 ========================
# 1. 演出购票页URL(复制大麦网演出购票页完整链接)
TARGET_URL = "https://detail.damai.cn/item.htm?id=78901234" # 示例:某演唱会购票页
# 2. 抢票模式(二选一):"INSTANT"(立即抢票,已开票)、"PREORDER"(预约抢票,未开票)
GRAB_MODE = "PREORDER"
# 3. 多场次候选列表(按优先级排序,脚本自动选择第一个有票场次)
# 格式:["场次1", "场次2", ...],必须与页面文字完全一致(含空格/标点)
SESSION_CANDIDATES = [
"2026-02-10 周六 19:30",
"2026-02-11 周日 19:30",
"2026-02-12 周一 19:30"
]
# 4. 多票档候选列表(按优先级排序,脚本自动选择第一个有票票档)
PRICE_CANDIDATES = [
"内场1680元",
"看台1280元",
"看台880元"
]
# 5. 购票数量(1-2张,大麦网多数演出限购2张,建议1张更稳)
BUY_NUM = 1
# 6. 购票人索引(提前在大麦网添加购票人,0=第一个,1=第二个,以此类推)
BUYER_INDEX = 0
# 7. 抢票超时时间(单位:秒):预约抢票建议设3600(1小时),立即抢票设60(1分钟)
TIMEOUT = 3600
# 8. 重试次数(元素定位失败/点击无效时,自动重试次数)
RETRY_COUNT = 3
# 9. 人工操作模拟间隔(单位:秒,随机取0.2~0.5,避免机械操作)
MIN_DELAY = 0.2
MAX_DELAY = 0.5
# ======================== 浏览器配置 ========================
# 禁用的资源类型(提升加载速度,建议保持默认)
DISABLE_RESOURCES = ["images", "css", "fonts", "media", "javascript"]
# 浏览器UA(模拟真实Chrome,可根据自己的浏览器版本修改)
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
# 页面加载超时时间(单位:秒,避免网络卡顿导致脚本卡死)
PAGE_LOAD_TIMEOUT = 10配置文件使用说明:新手只需修改“抢票核心配置”区域的前6个参数,其余参数保持默认即可。修改时需注意:场次和票档名称必须与大麦网页面完全一致,包括空格、标点;购票人索引从0开始计数,需提前在大麦网完成购票人实名认证。
2. 完整核心脚本ticket_grabber.py(可直接复制使用)
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options
from selenium.common.exceptions import (
NoSuchElementException, ElementNotInteractableException,
TimeoutException, WebDriverException
)
from webdriver_manager.chrome import ChromeDriverManager
import logging
from tqdm import tqdm
import random
import time
from config import *
# ======================== 日志配置(结构化输出,方便调试) ========================
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
logger = logging.getLogger("DamaiGrabber")
# ======================== 通用工具函数 ========================
def retry_decorator(max_retry=RETRY_COUNT, delay_range=(MIN_DELAY, MAX_DELAY)):
"""重试装饰器:失败自动重试,重试间隔随机"""
def decorator(func):
def wrapper(*args, **kwargs):
retry_count = 0
while retry_count< max_retry:
try:
result = func(*args, **kwargs)
# 执行成功后,添加随机延时模拟人工操作
time.sleep(random.uniform(*delay_range))
return result
except Exception as e:
retry_count += 1
logger.warning(f"第{retry_count}次重试:{func.__name__} 执行失败,原因:{str(e)}")
time.sleep(random.uniform(delay_range[0]*2, delay_range[1]*2)) # 重试间隔加倍
raise Exception(f"{func.__name__} 执行失败,已重试{max_retry}次")
return wrapper
return decorator
def get_random_delay(min_delay=MIN_DELAY, max_delay=MAX_DELAY):
"""获取随机延时(模拟人工操作节奏)"""
return random.uniform(min_delay, max_delay)
# ======================== 抢票核心类 ========================
class DamaiTicketGrabber:
def __init__(self):
self.driver = self._init_browser()
self.wait = WebDriverWait(
self.driver,
timeout=PAGE_LOAD_TIMEOUT,
poll_frequency=0.3,
ignored_exceptions=[NoSuchElementException, ElementNotInteractableException]
)
def _init_browser(self):
"""初始化浏览器(强化反检测+性能优化)"""
chrome_options = Options()
# 1. 核心反检测配置
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation", "enable-logging"])
chrome_options.add_experimental_option("useAutomationExtension", False)
chrome_options.add_argument("--disable-blink-features=AutomationControlled") # 禁用自动化控制标识
# 2. 伪装真实浏览器环境
chrome_options.add_argument(f"user-agent={USER_AGENT}")
chrome_options.add_argument("--lang=zh-CN") # 中文语言
chrome_options.add_argument("--window-size=1920,1080") # 固定分辨率(模拟桌面端)
# 3. 性能优化:禁用非必要资源
prefs = {}
if "images" in DISABLE_RESOURCES:
prefs["profile.managed_default_content_settings.images"] = 2
if "css" in DISABLE_RESOURCES:
prefs["profile.managed_default_content_settings.stylesheets"] = 2
if "fonts" in DISABLE_RESOURCES:
prefs["profile.managed_default_content_settings.fonts"] = 2
if "media" in DISABLE_RESOURCES:
prefs["profile.managed_default_content_settings.media_stream"] = 2
if "javascript" in DISABLE_RESOURCES:
prefs["profile.managed_default_content_settings.javascript"] = 1 # 仅禁用非必要JS(核心JS保留)
chrome_options.add_experimental_option("prefs", prefs)
# 4. 其他优化配置
chrome_options.add_argument("--disable-gpu") # 禁用GPU加速(避免兼容性问题)
chrome_options.add_argument("--no-sandbox") # 禁用沙箱模式(提升稳定性)
chrome_options.add_argument("--disable-dev-shm-usage") # 禁用临时文件限制
chrome_options.add_argument("--disable-popup-blocking") # 禁用弹窗拦截(避免购票弹窗被屏蔽)
# 5. 初始化驱动(自动适配Chrome版本)
driver = webdriver.Chrome(ChromeDriverManager().install(), options=chrome_options)
# 终极反检测:注入JS覆盖webdriver标识
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
Object.defineProperty(navigator, 'languages', {get: () => ['zh-CN', 'zh']});
Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3]});
"""
})
# 设置页面加载超时
driver.set_page_load_timeout(PAGE_LOAD_TIMEOUT)
logger.info("浏览器初始化完成(反检测+性能优化已启用)")
return driver
@retry_decorator()
def _login(self):
"""扫码登录(最安全,避免账号风控)"""
self.driver.get(TARGET_URL)
logger.info("请在15秒内完成扫码登录!")
# 显示倒计时进度条
for i in tqdm(range(15), desc="登录倒计时", unit="s"):
time.sleep(1)
# 登录后验证是否跳转到演出页
if "detail.damai.cn" not in self.driver.current_url:
logger.warning("⚠️ 登录后页面跳转异常,尝试刷新...")
self.driver.refresh()
time.sleep(2)
logger.info("登录流程完成,开始监控购票通道")
@retry_decorator()
def _wait_buy_button(self):
"""等待购票按钮开放(支持预约抢票/立即抢票模式)"""
start_time = time.time()
button_locators = [
(By.CLASS_NAME, "buybtn"), # 核心定位器1
(By.CLASS_NAME, "btn-buy"), # 备用定位器1
(By.XPATH, "//button[contains(text(), '立即预定')]"), # 备用定位器2
(By.XPATH, "//button[contains(text(), '立即购买')]") # 备用定位器3
]
while time.time() - start_time < TIMEOUT:
try:
# 循环尝试所有定位器,找到可用按钮
for locator in button_locators:
try:
buy_btn = self.wait.until(EC.element_to_be_clickable(locator))
btn_text = buy_btn.text.strip()
if GRAB_MODE == "PREORDER" and btn_text in ["立即预定", "立即购买"]:
logger.info(f"购票通道已开放!按钮状态:{btn_text}")
buy_btn.click()
return True
elif GRAB_MODE == "INSTANT" and btn_text in ["立即预定", "立即购买"]:
logger.info(f"开始立即抢票!按钮状态:{btn_text}")
buy_btn.click()
return True
else:
logger.info(f"购票通道未开放,当前按钮状态:{btn_text},继续等待...")
time.sleep(1)
break
except:
continue
except TimeoutException:
# 按钮未加载,刷新页面(智能刷新:每5秒刷新一次,避免频繁刷新)
if int(time.time() - start_time) % 5 == 0:
logger.info("按钮未加载,刷新页面...")
self.driver.refresh()
time.sleep(1)
raise Exception(f"抢票超时(已等待{TIMEOUT}秒)")
@retry_decorator()
def _select_session(self):
"""选择场次(从候选列表中选第一个有票场次)"""
logger.info(f"开始选择场次,候选列表:{SESSION_CANDIDATES}")
for session in SESSION_CANDIDATES:
try:
# 多定位器匹配场次(支持不同页面结构)
session_locators = [
(By.XPATH, f"//div[text()='{session}']"),
(By.XPATH, f"//span[text()='{session}']"),
(By.XPATH, f"//p[contains(text(), '{session}')]")
]
for locator in session_locators:
session_ele = self.wait.until(EC.element_to_be_clickable(locator))
# 检查场次是否可点击(有票)
if "disabled" not in session_ele.get_attribute("class", default=""):
session_ele.click()
logger.info(f"已选择场次:{session}")
return True
else:
logger.warning(f"场次{session}已售罄,尝试下一个...")
break
except:
logger.warning(f"未找到场次{session},尝试下一个...")
continue
raise Exception("所有候选场次均已售罄或未找到")
@retry_decorator()
def _select_price(self):
"""选择票档(从候选列表中选第一个有票票档)"""
logger.info(f"开始选择票档,候选列表:{PRICE_CANDIDATES}")
for price in PRICE_CANDIDATES:
try:
price_locators = [
(By.XPATH, f"//div[text()='{price}']"),
(By.XPATH, f"//span[text()='{price}']"),
(By.XPATH, f"//p[contains(text(), '{price}')]")
]
for locator in price_locators:
price_ele = self.wait.until(EC.element_to_be_clickable(locator))
if "disabled" not in price_ele.get_attribute("class", default=""):
price_ele.click()
logger.info(f"已选择票档:{price}")
return True
else:
logger.warning(f"票档{price}已售罄,尝试下一个...")
break
except:
logger.warning(f"未找到票档{price},尝试下一个...")
continue
raise Exception("所有候选票档均已售罄或未找到")
@retry_decorator()
def _select_ticket_num(self):
"""选择购票数量"""
if BUY_NUM == 1:
logger.info(f"购票数量:1张(默认选中)")
return True
# 数量大于1时,点击"+"号增加
try:
add_btn = self.wait.until(EC.element_to_be_clickable((By.XPATH, "//div[@class='num']//span[text()='+']")))
for _ in range(BUY_NUM - 1):
add_btn.click()
time.sleep(get_random_delay())
logger.info(f"已选择购票数量:{BUY_NUM}张")
return True
except:
raise Exception(f"无法选择{BUY_NUM}张票(可能超出限购数量或元素未找到)")
@retry_decorator()
def _select_buyer(self):
"""选择购票人(按配置的索引选择)"""
logger.info(f"开始选择购票人(索引:{BUYER_INDEX})")
try:
# 定位所有购票人复选框
buyer_checkboxes = self.wait.until(
EC.presence_of_all_elements_located((By.XPATH, "//input[@type='checkbox' and @name='buyer']"))
)
if len(buyer_checkboxes) <= BUYER_INDEX:
raise Exception(f"购票人索引{BUYER_INDEX}超出范围(当前仅{len(buyer_checkboxes)}个购票人)")
# 勾选目标购票人(如果未勾选)
if not buyer_checkboxes[BUYER_INDEX].is_selected():
buyer_checkboxes[BUYER_INDEX].click()
logger.info(f"已选择购票人:{buyer_checkboxes[BUYER_INDEX].get_attribute('title')}")
return True
except:
raise Exception("选择购票人失败(可能未添加购票人或元素更新)")
@retry_decorator()
def _submit_order(self):
"""提交订单"""
logger.info("开始提交订单...")
try:
submit_locators = [
(By.CLASS_NAME, "submit-btn"),
(By.XPATH, "//button[contains(text(), '确定购票')]"),
(By.XPATH, "//button[contains(text(), '提交订单')]")
]
for locator in submit_locators:
try:
submit_btn = self.wait.until(EC.element_to_be_clickable(locator))
submit_btn.click()
logger.info("订单提交成功!请在15分钟内完成付款!")
return True
except:
continue
raise Exception("未找到提交订单按钮")
except:
raise Exception("提交订单失败(可能票已售罄或风控拦截)")
def run(self):
"""启动抢票流程"""
try:
logger.info("="*50)
logger.info("大麦网自动抢票脚本(优化增强版)启动")
logger.info(f"抢票模式:{GRAB_MODE}")
logger.info(f"目标演出:{TARGET_URL}")
logger.info(f"超时时间:{TIMEOUT}秒")
logger.info("="*50)
# 1. 扫码登录
self._login()
# 2. 等待购票通道开放
self._wait_buy_button()
# 3. 选择场次
self._select_session()
# 4. 选择票档
self._select_price()
# 5. 选择购票数量
self._select_ticket_num()
# 6. 选择购票人
self._select_buyer()
# 7. 提交订单
self._submit_order()
# 保持浏览器打开,方便付款
logger.info("="*50)
logger.info("脚本执行完成!请手动在浏览器中完成付款操作")
logger.info("付款完成后,按回车键关闭浏览器...")
input()
except Exception as e:
logger.error(f"抢票流程异常终止:{str(e)}")
logger.error("常见解决方案:")
logger.error(" 1. 检查场次/票档名称是否与页面完全一致")
logger.error(" 2. 确认账号已添加购票人,且索引正确")
logger.error(" 3. 更换常用IP,避免代理/梯子(大麦风控严格)")
logger.error(" 4. 清除浏览器缓存,重新运行脚本")
finally:
self.driver.quit()
logger.info("浏览器已关闭,抢票流程结束")
if __name__ == "__main__":
grabber = DamaiTicketGrabber()
grabber.run()核心脚本说明:脚本采用面向对象封装,所有核心逻辑都在DamaiTicketGrabber类中,通过run()方法启动完整抢票流程。脚本内置反检测配置、重试机制、随机延时等优化点,无需修改即可直接运行,仅在定位器失效时需微调对应定位器列表。
1. 配置文件config.py:3步完成核心设置
配置文件包含抢票核心参数与浏览器配置,新手只需聚焦3个关键修改点,其余参数保持默认即可正常使用:第一,填写目标演出URL:打开大麦网对应演出的购票页,复制地址栏完整链接粘贴到TARGET_URL参数;第二,设置候选场次与票档:从演出页复制场次(如“2026-02-10 周六 19:30”)和票档(如“内场1680元”)名称,按优先级填入SESSION_CANDIDATES和PRICE_CANDIDATES列表,脚本会自动选择第一个有票项;第三,指定购票人索引:在大麦网“我的-购票人管理”中确认购票人顺序,0代表第一个购票人,1代表第二个,将对应数字填入BUYER_INDEX。此外,可根据抢票场景调整GRAB_MODE参数(“PREORDER”为预约抢票,“INSTANT”为立即抢票)、TIMEOUT超时时间(预约抢票建议设3600秒,即1小时)等,配置文件内均有详细注释,按需修改即可。
2. 核心脚本ticket_grabber.py:封装化逻辑,稳定可靠
核心脚本采用面向对象封装,将抢票流程拆解为登录、等待购票通道、选择场次/票档/购票人、提交订单等独立方法,每个方法均通过重试装饰器增强稳定性。关键优化设计包括:浏览器初始化模块:集成强化版反检测配置,通过注入JS代码覆盖navigator.webdriver标识,伪装真实浏览器的UA、语言、分辨率,同时禁用图片、CSS等非必要资源提升加载速度;定位器设计:每个页面元素(购票按钮、场次、票档)均配置3个以上备选定位器,应对大麦网页面更新;随机延时机制:通过random.uniform生成0.2~0.5秒的随机操作间隔,模拟人工操作节奏,规避机械点击检测。
四、实操指南:4步完成抢票全流程
优化版脚本的使用流程极简,新手按以下步骤操作即可,全程无需复杂技术操作:
步骤1:修改配置文件
打开config.py,按前文说明完成TARGET_URL、SESSION_CANDIDATES、PRICE_CANDIDATES、BUYER_INDEX四个核心参数的修改,其余参数保持默认。
步骤2:运行核心脚本
打开终端,进入damai_ticket目录,执行以下命令启动脚本:
python ticket_grabber.py步骤3:扫码登录(关键!避免风控)
脚本启动后会自动打开Chrome浏览器并跳转至目标演出页,终端会显示15秒倒计时进度条。此时需用大麦APP扫描浏览器页面的登录二维码,务必在倒计时结束前完成登录——自动输入账号密码会100%触发风控,扫码登录是最安全的方式。
步骤4:等待自动抢票,手动完成付款
登录完成后,脚本会自动进入监控状态:预约抢票模式下会等待购票按钮从“预约”变为“立即预定”,立即抢票模式下会直接触发抢票流程。后续的场次选择、票档选择、购票人勾选、订单提交等操作均由脚本自动完成,最终终端会提示“订单提交成功”,此时需在浏览器中手动完成付款(15分钟内有效)。
五、深度解析:优化点背后的技术逻辑
脚本的稳定运行离不开核心优化点的技术支撑,理解这些逻辑能帮助你在脚本失效时快速排查问题:
1. 反风控核心:浏览器指纹伪装与行为模拟
大麦网通过检测navigator.webdriver标识、浏览器UA、操作节奏等判断是否为自动化操作。脚本通过两个关键设计破解:一是执行CDP命令注入JS代码,彻底覆盖navigator.webdriver为undefined,同时伪装languages、plugins等属性;二是引入随机延时机制,每个操作后随机等待0.2~0.5秒,避免机械性点击,让操作节奏更接近人工。
2. 稳定性核心:多定位器与重试机制
大麦网会不定期更新页面元素的class或xpath,传统脚本单一定位器易失效。优化版脚本为每个元素配置多个备选定位器(如购票按钮同时配置class和xpath定位),循环尝试直至找到可用元素;同时通过通用重试装饰器,对每个核心操作设置3次自动重试,重试间隔加倍,解决网络卡顿或元素加载延迟导致的失败。
3. 效率核心:资源禁用与智能刷新
抢票的核心竞争力是响应速度,脚本通过禁用图片、CSS、字体等非必要资源,将页面加载速度提升50%以上;同时采用智能刷新策略,每5秒刷新一次页面而非每秒刷新,减少服务器压力的同时避免频繁刷新触发风控。
五、一 实操案例:脚本失效时的定位器微调方法
脚本最常见的失效原因是大麦网更新页面元素,导致原定位器(class/xpath)失效。此时无需重新编写脚本,只需通过浏览器开发者工具找到新的元素定位信息,微调核心脚本中的定位器即可。以下是完整的实操案例,以“购票按钮定位失效”和“场次选择定位失效”两种高频场景为例:
1. 前期准备:开启浏览器开发者工具
首先启动脚本,当脚本提示“元素未找到”等定位相关错误时,不要关闭浏览器(若已关闭,可临时在脚本中添加time.sleep(60)让浏览器停留)。然后在浏览器页面按F12或Ctrl+Shift+I打开开发者工具,切换到「Elements」(元素)面板,开启「元素选择器」功能(面板左上角的箭头图标),此时点击页面上的目标元素(如购票按钮、场次文字),开发者工具会自动定位到对应的HTML代码。
2. 场景1:购票按钮定位失效(最高频)
#### 问题现象:脚本提示“_wait_buy_button 执行失败,已重试3次”,终端日志显示未找到“buybtn”相关元素。
#### 排查与修改步骤:
第一步,用开发者工具定位购票按钮:点击「元素选择器」后点击页面上的“立即预定”或“立即购买”按钮,找到对应的HTML代码。假设原脚本中购票按钮的定位器是By.CLASS_NAME, "buybtn",而新的HTML代码显示按钮的class变为“btn-buy-now”(示例):
第二步,修改核心脚本中的定位器:打开ticket_grabber.py,找到_wait_buy_button方法中的button_locators列表,将失效的定位器替换为新的class定位,或新增新的xpath定位。示例修改如下:
原定位器列表:
button_locators = [
(By.CLASS_NAME, "buybtn"),
(By.CLASS_NAME, "btn-buy"),
(By.XPATH, "//button[contains(text(), '立即预定')]"),
(By.XPATH, "//button[contains(text(), '立即购买')]")
]
修改后(替换第一个失效的class定位):
button_locators = [
(By.CLASS_NAME, "btn-buy-now"), # 新增的新class定位
(By.CLASS_NAME, "btn-buy"),
(By.XPATH, "//button[contains(text(), '立即预定')]"),
(By.XPATH, "//button[contains(text(), '立即购买')]")
]
第三步,保存修改后重新运行脚本,即可正常定位到购票按钮。
3. 场景2:场次选择定位失效
#### 问题现象:脚本提示“_select_session 执行失败,已重试3次”,无法找到配置的场次名称。
#### 排查与修改步骤:
第一步,定位场次元素:用开发者工具点击页面上的目标场次(如“2026-02-10 周六 19:30”),查看对应的HTML代码。假设原脚本用//div[text()='{session}']定位,新的HTML代码中场次文字包裹在标签内:
2026-02-10 周六 19:30
第二步,修改场次定位器:打开ticket_grabber.py,找到_select_session方法中的session_locators列表,新增一个span标签的xpath定位。示例修改如下:
原代码(未修改前):
session_locators = [
(By.XPATH, f"//div[text()='{session}']"),
(By.XPATH, f"//span[text()='{session}']"),
(By.XPATH, f"//p[contains(text(), '{session}')]")
]
修改后代码(新增class精准匹配):
session_locators = [
(By.XPATH, f"//div[text()='{session}']"),
(By.XPATH, f"//span[@class='session-time' and text()='{session}']"), # 新增class精准匹配
(By.XPATH, f"//p[contains(text(), '{session}')]")
]
第三步,验证修改:保存后运行脚本,若仍失效,可检查配置文件中SESSION_CANDIDATES的场次名称是否与页面完全一致(包括空格、特殊符号)。#### 问题现象:脚本提示“_select_session 执行失败,已重试3次”,无法找到配置的场次名称。
#### 排查与修改步骤:
第一步,定位场次元素:用开发者工具点击页面上的目标场次(如“2026-02-10 周六 19:30”),查看对应的HTML代码。假设原脚本用//div[text()='{session}']定位,新的HTML代码中场次文字包裹在标签内:
2026-02-10 周六 19:30
第二步,修改场次定位器:打开ticket_grabber.py,找到_select_session方法中的session_locators列表,新增一个span标签的xpath定位。示例修改如下:
原定位器列表:
session_locators = [
(By.XPATH, f"//div[text()='{session}']"),
(By.XPATH, f"//span[text()='{session}']"),
(By.XPATH, f"//p[contains(text(), '{session}')]")
]
若原第二个定位器无效,可修改为精准匹配class的xpath(增强稳定性):
session_locators = [
(By.XPATH, f"//div[text()='{session}']"),
(By.XPATH, f"//span[@class='session-time' and text()='{session}']"), # 新增class精准匹配
(By.XPATH, f"//p[contains(text(), '{session}')]")
]
第三步,验证修改:保存后运行脚本,若仍失效,可检查配置文件中SESSION_CANDIDATES的场次名称是否与页面完全一致(包括空格、特殊符号)。
4. 场景3:票档定位失效(专属案例)
#### 问题现象:脚本提示“_select_price 执行失败,已重试3次”,无法找到配置的票档名称(如“内场1680元”)。
#### 排查与修改步骤:
第一步,定位票档元素:用开发者工具点击页面上的目标票档(如“内场1680元”),查看对应的HTML代码。假设原脚本用//div[text()='{price}']定位,新的HTML代码中票档文字包裹在
5. 定位器微调技巧:可视化注意事项清单
- 定位器选择优先级:id定位(最稳定,优先使用)> 语义化class定位(如“price-label”“session-time”)> 文本+标签组合定位 > 随机class定位(坚决避免);
- 文本匹配原则:配置文件中的场次/票档名称需与页面“一字不差”,包括空格、标点、特殊符号(如“内场 1680元”与“内场1680元”视为不同);
- 备份定位器习惯:修改定位器时不要删除原有有效定位,新增新定位器补充到列表中,形成多备份机制;
- 失效预判要点:大麦网大促或演出上新后易更新页面,抢票前建议先运行脚本测试定位器是否有效,提前微调;
- 工具使用技巧:开发者工具定位元素后,可右键HTML代码选择“Copy XPath”“Copy selector”快速获取定位表达式,再手动优化(删除随机参数);
- 调试辅助手段:修改定位器后,可在对应代码处添加print("定位成功"),运行脚本查看是否能正常打印,快速判断定位是否生效。
6. 脚本调试代码片段(高频场景)
场景1:定位器有效性测试(最常用)
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager
from config import USER_AGENT
def test_locator_validity(target_locator):
"""
测试定位器有效性
:param target_locator: 待测试的定位器,格式如(By.CLASS_NAME, "buybtn")
"""
# 初始化简化版浏览器(仅保留核心配置)
chrome_options = Options()
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
chrome_options.add_argument(f"user-agent={USER_AGENT}")
chrome_options.add_argument("--lang=zh-CN")
driver = webdriver.Chrome(ChromeDriverManager().install(), options=chrome_options)
try:
# 访问目标演出页
driver.get("https://detail.damai.cn/item.htm?id=78901234") # 替换为你的演出URL
print("请手动完成扫码登录(30秒时间)...")
import time
time.sleep(30) # 预留登录时间
# 尝试定位元素
element = driver.find_elements(*target_locator)
if element:
print(f"定位器有效!找到{len(element)}个匹配元素")
# 打印元素文本和属性,确认是否为目标元素
for idx, ele in enumerate(element):
print(f" 元素{idx+1}文本:{ele.text.strip()}")
print(f" 元素{idx+1}class属性:{ele.get_attribute('class', default='')}")
else:
print(" 定位器无效,未找到任何匹配元素")
except Exception as e:
print(f"测试过程出错:{str(e)}")
finally:
driver.quit()
# 测试示例:替换为你要测试的定位器
if __name__ == "__main__":
# 测试购票按钮定位器
test_locator = (By.CLASS_NAME, "btn-buy-now")
# 测试场次定位器(替换为你的场次名称)
# test_locator = (By.XPATH, "//span[text()='2026-02-10 周六 19:30']")
# 测试票档定位器(替换为你的票档名称)
# test_locator = (By.XPATH, "//label[text()='内场1680元']")
test_locator_validity(test_locator)
场景2:操作步骤日志增强(定位哪一步失败)
功能:在每个核心操作步骤前后添加详细日志,精准定位是哪个步骤执行失败。将以下修改应用到ticket_grabber.py的run()方法中:
def run(self):
"""启动抢票流程(增强日志版)"""
try:
logger.info("="*50)
logger.info("大麦网自动抢票脚本(优化增强版)启动")
logger.info(f" 抢票模式:{GRAB_MODE}")
logger.info(f" 目标演出:{TARGET_URL}")
logger.info(f" 超时时间:{TIMEOUT}秒")
logger.info("="*50)
# 1. 扫码登录
logger.info(" 开始执行步骤1:扫码登录")
self._login()
logger.info(" 步骤1:扫码登录执行完成")
# 2. 等待购票通道开放
logger.info(" 开始执行步骤2:等待购票通道开放")
self._wait_buy_button()
logger.info(" 步骤2:等待购票通道开放执行完成")
# 3. 选择场次
logger.info(" 开始执行步骤3:选择场次")
self._select_session()
logger.info(" 步骤3:选择场次执行完成")
# 4. 选择票档
logger.info(" 开始执行步骤4:选择票档")
self._select_price()
logger.info(" 步骤4:选择票档执行完成")
# 5. 选择购票数量
logger.info(" 开始执行步骤5:选择购票数量")
self._select_ticket_num()
logger.info(" 步骤5:选择购票数量执行完成")
# 6. 选择购票人
logger.info(" 开始执行步骤6:选择购票人")
self._select_buyer()
logger.info(" 步骤6:选择购票人执行完成")
# 7. 提交订单
logger.info(" 开始执行步骤7:提交订单")
self._submit_order()
logger.info(" 步骤7:提交订单执行完成")
# 保持浏览器打开,方便付款
logger.info("="*50)
logger.info(" 脚本执行完成!请手动在浏览器中完成付款操作")
logger.info(" 付款完成后,按回车键关闭浏览器...")
input()
except Exception as e:
logger.error(f" 抢票流程异常终止:{str(e)}")
logger.error(" 异常发生步骤:查看上方'开始执行步骤X'日志,定位失败步骤")
logger.error(" 常见解决方案:")
logger.error(" 1. 检查场次/票档名称是否与页面完全一致")
logger.error(" 2. 确认账号已添加购票人,且索引正确")
logger.error(" 3. 更换常用IP,避免代理/梯子(大麦风控严格)")
logger.error(" 4. 清除浏览器缓存,重新运行脚本")
finally:
self.driver.quit()
logger.info(" 浏览器已关闭,抢票流程结束")
场景3:页面元素全量打印(排查元素结构变化)
功能:打印页面中所有可能的场次/票档元素,快速确认页面元素结构是否变化,避免定位器匹配不到新元素。将以下代码片段插入_select_session()或_select_price()方法开头:
@retry_decorator()
def _select_session(self):
"""选择场次(从候选列表中选第一个有票场次)"""
# 新增:打印页面中所有可能的场次元素
logger.info(" 开始打印页面中所有可能的场次元素...")
possible_session_elements = self.driver.find_elements(By.XPATH, "//div[contains(@class, 'session') or contains(text(), '2026')]")
for idx, ele in enumerate(possible_session_elements):
if ele.text.strip(): # 只打印有文本的元素
logger.info(f" 场次元素{idx+1}:文本={ele.text.strip()}, class={ele.get_attribute('class', default='')}")
logger.info(" 场次元素打印完成")
# 原有逻辑
logger.info(f" 开始选择场次,候选列表:{SESSION_CANDIDATES}")
# ...(后续原有代码不变)
调试代码使用说明:以上代码片段均保留原有核心逻辑,新增调试代码可在问题解决后删除;运行调试版脚本时,重点关注终端打印的日志,根据元素信息调整定位器或配置参数。
六、常见问题排查:99%新手会遇到的解决方案
即使使用优化版脚本,也可能因配置或环境问题导致异常,以下是高频问题的解决方案(含代码调整示例):问题1:提示“购票人索引超出范围”——解决方案:登录大麦网确认购票人数量,修改BUYER_INDEX为正确索引(0开始),确保已完成购票人实名认证。问题2:提示“所有候选场次/票档均已售罄”——解决方案:检查配置文件中的场次/票档名称是否与页面完全一致(包括空格、标点),扩大候选列表增加命中概率,确认演出是否已到开票时间。问题3:提交订单时提示“操作过于频繁”——解决方案:增大MIN_DELAY和MAX_DELAY参数(如设为0.3~0.6秒),减少RETRY_COUNT重试次数(如设为2次),暂停脚本1~2小时后再尝试,避免账号临时封禁。
代码调整示例(修改config.py):
# 人工操作模拟间隔(单位:秒),随机取0.3~0.6
MIN_DELAY = 0.3
MAX_DELAY = 0.6
# 重试次数(元素定位失败/点击无效时,自动重试次数)
RETRY_COUNT = 2问题4:浏览器启动失败——解决方案:更新Chrome浏览器至最新版本,重新安装依赖包(pip install --upgrade selenium webdriver-manager),关闭杀毒软件避免驱动被拦截。
七、终极抢票技巧:提升成功率的关键细节
脚本是辅助工具,成功率还取决于环境和操作细节,以下技巧能进一步提升抢票概率:1. 网络准备:使用有线网络(比Wi-Fi稳定),关闭视频、下载等占用带宽的应用,必要时重启路由器清理网络缓存;2. 设备准备:选用性能较好的电脑,关闭后台冗余程序,避免浏览器卡顿;3. 账号准备:抢票前1小时用大麦APP登录并浏览演出页,模拟正常用户行为;使用老账号抢票,新账号风控更严格;4. 脚本准备:抢票前10分钟启动脚本完成登录,提前进入监控状态;每次运行前清除Chrome缓存和Cookie。
八、免责声明与理性购票提醒
最后必须强调:本脚本仅为Python+Selenium自动化学习案例,仅供个人自用,严禁用于商业购票、倒卖门票等违规行为。大麦网反爬机制持续升级,脚本可能随页面更新而失效,需根据实际情况微调定位器;抢票成功率受网络、服务器压力、票量等多种因素影响,脚本无法保证100%抢到票。理性购票,拒绝黄牛,享受演出的核心是感受现场氛围,而非为溢价门票买单。希望这份攻略能帮助你顺利抢到心仪的演出门票,在现场感受音乐与艺术的魅力!
版权所属:SO JSON在线解析
原文地址:https://www.sojson.com/blog/560.html
转载时必须以链接形式注明原始出处及本声明。
如果本文对你有帮助,那么请你赞助我,让我更有激情的写下去,帮助更多的人。
