从零到一掌握:基于Selenium的大麦网自动抢票脚本全攻略

JSON 2026-01-12 15:38:49 1519

  在演出票务抢购场景中,大麦网的高并发访问与严格反爬机制,让手动抢票的成功率微乎其微。基于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)让浏览器停留)。然后在浏览器页面按F12Ctrl+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

转载时必须以链接形式注明原始出处及本声明。

本文主题:

如果本文对你有帮助,那么请你赞助我,让我更有激情的写下去,帮助更多的人。

相关文章
Linux Centos 使用 Redis service 启动,Redis service 脚本编写
JS加密V6版上线了,JS加密升级,原来的sojson.v5加密升级JS加密第六个版本!!!
2019年 中秋节、国庆节放假通知来了,拼假攻略
JS解密,JavaScript 解密,任何JS加密都能100%解密,以及JS 加密讲解
JS加密,JS在线混淆加密,JavaScript在线加密
Javascript 生成UUID,Java生成UUID
javascript核心知识点
分解质因数 JavaScript 计算方式,及分解质因数讲解
Javascript 删除节点后是否需要将手动将事件解绑?
最新文章
文件上传漏洞与防御 4058
前端构建工具选型指南:Webpack、Vite、Rollup、esbuild 深度对比 1444
物联网时代2026年时序数据库选型指南 1151
SaaS行业面临AI挑战:从“无限复用”到“灵活适应” 1269
神经网络:从构造到模型训练全链路解析 1168
一文吃透 Redis 核心存储结构:ziplist、listpack 与哈希表扩容 / 并发查询 1593
Linux sudo提权完整指南:从基础用法到生产级安全配置 691
XSS 和 CSRF 的本质区别及开发防御全解析 772
JVM垃圾回收(GC)全维度解析:从原理到调优实战 813
Linux动静态库与ELF加载全解析:从实操制作到底层原理 912
最热文章
免费天气API,天气JSON API,不限次数获取十五天的天气预报 783114
最新MyEclipse8.5注册码,有效期到2020年 (已经更新) 711464
苹果电脑Mac怎么恢复出厂系统?苹果系统怎么重装系统? 679993
Jackson 时间格式化,时间注解 @JsonFormat 用法、时差问题说明 562673
我为什么要选择RabbitMQ ,RabbitMQ简介,各种MQ选型对比 512621
Elasticsearch教程(四) elasticsearch head 插件安装和使用 484794
Jackson 美化输出JSON,优雅的输出JSON数据,格式化输出JSON数据... ... 302947
Java 信任所有SSL证书,HTTPS请求抛错,忽略证书请求完美解决 247433
Elasticsearch教程(一),全程直播(小白级别) 233097
谈谈斐讯路由器劫持,你用斐讯路由器,你需要知道的事情 228329
支付扫码

所有赞助/开支都讲公开明细,用于网站维护:赞助名单查看

查看我的收藏

正在加载... ...