【Python】利用selenium实现自动抢票(ticket星球网页版)

前言

最近在学习python里selenium库的使用,正好当前各种演唱会纷飞,可以用来测试。目前开发的程序只适用于票星球网页非选座版,app和要选座版暂未开发,所以网页上能买票的才能运行该程序。
申明:程序仅供学习,请勿用于违法活动,如作他用所承受的法律责任一概与作者无关。

selenium简单介绍

概括来说,selenium能根据编程指令自动在浏览器中执行,解放双手,一般用于自动化测试等。整体实现方法很简单:提前规划好执行逻辑——连接到浏览器——确定要点击/判断的网页元素名称——执行。作者测试的版本是python 3.9,selenium 4.26.1,其他python版本可以直接在网上搜索到对应的版本号。

抢票流程分析

一、进入网页

直接进入购票界面,如图所示:在这里插入图片描述
查看红框可知,抢票界面的网址是:

"https://m.piaoxingqiu.com/booking/"+show_id+"?saleShowSessionId=&seatPickType=SUPPORT_NONE&selectedDate=&showId="+show_id+"&stdShowId="+stdShowId

其中,"show_id"是该演出的id,可以直接在网址上找到,每个项目的id是唯一的;"stdShowId"经过验证,其实就是show_id(十六进制)-1,所以只要知道show_id,就可以计算出stdShowId。

二、 选择观演日期和票价

选择界面如图所示,观演日期(session)和票价(ticket_price)填写时必须与网站显示的完全相同,例如图上显示的’2025-02-21 周五 19:30’和’480元’,在抢票时会与填写的内容进行字符比对,完全一致时才会点击,所以必须要注意。
在这里插入图片描述

三、选择观演人数和观演人

  1. 观演人数(buy_count)按需填写,观演人信息要提前填好。为加快抢票速度,程序中没有判断观演人信息是否对应的部分,所以抢票时只保留需要的观演人,防止选择出错;
  2. 票星球似乎只有在刚开票时不用勾选购票人(所以必须提前预选好!),刷回流票时是需要再次勾选的,所以只抢一个人的回流票一定程度上会加快程序运行速度,抢到的概率更大;
  3. 根据“刚开票”和“刷回流”两种模式不同,程序运行的顺序和逻辑也有两种方式,后续会详细说明。

四、参数设置

上述需要的"show_id"、“session”、“ticket_price”、"buy_count"都可配置在config.py文件中,方便直接修改运行,示例如图。

# 抢票模式,必填
ticket_model = '刚开票'  # 目前只有:'刷回流'、'刚开票'(需提前预选好场次、购票人)
# 项目id,必填
show_id = '66f27787ed376600017c8029'
# 场次,必填
session = '2025-01-01 周三 19:00'
# 票价,必填
ticket_price = '1980元' # '看台 680元'
# 购票数量,一定要看购票须知,不要超过上限,必填
buy_count = 1

程序具体执行

一、连接浏览器

  1. 首先要引入的包,config是“参数设置”里的配置文件
from selenium import webdriver
from selenium.webdriver.edge.service import Service
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
import time
import os

import config

ticket_model = config.ticket_model
show_id = config.show_id
session = config.session
ticket_price = config.ticket_price
buy_count = config.buy_count
  1. 连接到浏览器(这里用的是Edge浏览器)并查询网址,用户配置路径需修改
print('当前模式:', ticket_model)
edge_options = webdriver.EdgeOptions()
edge_options.add_argument(r"user-data-dir=D:\csdn\Mint_V\scoped_dir18252_1702533799") # 用户配置路径,直接在edg浏览器输入edge://version/可以查看
browser = webdriver.Edge(options=edge_options)

stdShowId = hex_str_subtract_one(show_id)
url = "https://m.piaoxingqiu.com/booking/"+show_id+"?saleShowSessionId=&seatPickType=SUPPORT_NONE&selectedDate=&showId="+show_id+"&stdShowId="+stdShowId
print(url)
browser.get(url) # 访问网址
time.sleep(1) # 等待1s反应

使用到的计算stdShowId的函数:

def hex_str_subtract_one(hex_str):
	# 将十六进制字符串转换为整数
    num = int(hex_str, 16)
    # 对整数进行减 1 操作
    num -= 1
    # 将结果转换回十六进制字符串
    # 使用 format 函数确保结果的长度与原始字符串一致
    result_hex_str = format(num, 'x').zfill(len(hex_str))

    return result_hex_str

二、选择场次日期和价位

  1. 选择日期函数如下,其中元素名称"session-name"的获取方式为:进入开发者模式(F12或Fn+F12)——点击检查元素按钮——点击需要的场次名——查看元素名称,可以看到class=“session-name”,即为需要的元素。
def choose_date(browser):
    while True:
     	# 判断是否存在元素,不存在就刷新页面
        try:
            elements = WebDriverWait(browser, 4).until(
                EC.presence_of_all_elements_located((By.XPATH, '//uni-text[contains(@class,"session-name")]'))
            )
            break
        except Exception as e:
            browser.refresh()
            time.sleep(0.5)
    for element in elements:
        if element.text.strip() == session: # 选择的场次与想要的相同
            element.click()  # 单击选择
            time.sleep(0.1)  # 等待0.1s缓冲
            break
    return

在这里插入图片描述

  1. 选择价位函数如下,获取元素名称方式同上
def choose_price(browser):
    elements = WebDriverWait(browser, 3).until(
        EC.presence_of_all_elements_located((By.XPATH, '//uni-text[contains(@class,"ticket")]'))
    )
    # elements = browser.find_elements(By.XPATH, '//uni-text[contains(@class,"ticket")]')
    for element in elements:
        print(element.text.strip())
        if element.text.strip() == ticket_price:
            element.click()
            time.sleep(0.1)
            break
    return
  1. 判断是否已经开票。之前的大多数抢票代码是设定开票时间,通过判断当前时间与开票时间的误差,确定是否抢票,其实是有一些问题的。在此提供一个新的思路,下图分别为能购票和还未开票的界面,可以看到它们之间的区别在于能否选择购票数量,也就是购票数量元素是否存在。因此,选择购票人数的相关代码如下。
    在这里插入图片描述
def choose_ticket_number(buy_count, browser):
    for i in range(buy_count):
        element = browser.find_element(By.XPATH, '//uni-view[contains(@class,"number")]')
        print('当前购票数:', int(element.text.strip()))
        if int(element.text.strip()) == buy_count:
            break
        else:
            element_add = browser.find_element(By.XPATH,
                                               "//uni-view[contains(concat(' ', normalize-space(@class), ' '), ' plus iconfont icon-jia ')]")
            element_add.click()
            time.sleep(0.1)
    return
  1. 综合上述分析,我们可以将函数汇总起来,通过循环“选择演出日期——选择演出票价——判断是否有购票数”这一过程来判断是否开抢,若未开票就刷新界面,若已开票则点击“下一步”到选择观演人。
def ready_to_buy(buy_count, browser):
    while True:
        try:
            choose_date(browser)
            choose_price(browser)
            choose_ticket_number(buy_count, browser)
            break
        except Exception as e:
            print('未开卖')
            browser.refresh()
            time.sleep(0.5)
    return
ready_to_buy(buy_count, browser)
# 点击下一步
element = browser.find_element(By.XPATH, '//uni-view[contains(@class,"one-btn bottom-btn-item")]')
element.click()
time.sleep(0.5)

三、选择观演人及确认下单

上文提到过,票星球在选择观演人这一步有两种模式:刚开票和刷回流。在刚开票模式下,只要提前预选好观演人,选择观演人这一步是不需要再进行操作的,所以直接点击下单即可;在刷回流模式下,无论是否预选过观演人,都需要再次选择观演人,因此这里会选择与购票数量相同的观演人数。为了简化程序,这里并没有写判断勾选的观演人是否与期望的相同,所以大家在使用时,只保留要购票人的信息即可,避免勾选到其他人。两种模式

    if ticket_model == '刚开票':
        while True:
            element = WebDriverWait(browser, 3).until(
                EC.element_to_be_clickable(
                    (By.XPATH, '//uni-view[contains(@class,"btn-pay")]'))
            )
            # element = browser.find_element(By.XPATH, '//uni-view[contains(@class,"btn-pay")]')
            print('可以点击')
            # 循环点击
            time.sleep(0.1)
            element.click()
            try:
                element = WebDriverWait(browser, 3).until(
                    EC.element_to_be_clickable(
                        (By.XPATH, '//uni-view[contains(@class,"payment-type-title")]'))
                )
                if element.text.strip() == "选择支付方式":
                    print('已买到,请立刻付款')
                    time.sleep(5)
                    break
            except Exception as e:
                browser.refresh()
                time.sleep(0.5)

    elif ticket_model == '刷回流':
        # 选购票人
        while True:
            element_xpath = "//uni-view[@class='iconfont icon-choose icon-xuanzhong']"
            try:
                elements = browser.find_elements(By.XPATH, element_xpath)
                break
            except Exception as e:
                browser.refresh()
                time.sleep(0.5)

        if len(elements) > 0:  # 购票人已勾选
            print("已勾选购票人.")
            while True:
                element = WebDriverWait(browser, 3).until(
                    EC.element_to_be_clickable(
                        (By.XPATH, '//uni-view[contains(@class,"btn-pay")]'))
                )
                # element = browser.find_element(By.XPATH, '//uni-view[contains(@class,"btn-pay")]')
                if element.text.strip() == "去支付":
                    print('可以点击')
                    # 循环点击
                    element.click()
                    try:
                        element = WebDriverWait(browser, 3).until(
                            EC.element_to_be_clickable(
                                (By.XPATH, '//uni-view[contains(@class,"payment-type-title")]'))
                        )
                        if element.text.strip() == "选择支付方式":
                            print('已买到,请立刻付款')
                            time.sleep(5)
                            break
                    except Exception as e:
                        browser.refresh()
                        time.sleep(0.5)

        else: # 勾选所有存在的购票人
            print("购票人未勾选,自动选择预购票数相同的前几位购票人.")
            elements = WebDriverWait(browser, 3).until(
                EC.presence_of_all_elements_located((By.XPATH, "//uni-view[@class='iconfont icon-choose icon-weigouxuan']"))
            )
            # elements = browser.find_elements(By.XPATH, "//uni-view[@class='iconfont icon-choose icon-weigouxuan']")
            for element in elements:
                element.click()
                time.sleep(0.1)

            while True:
                element = WebDriverWait(browser, 3).until(
                    EC.element_to_be_clickable(
                        (By.XPATH, '//uni-view[contains(@class,"btn-pay")]'))
                )
                # element = browser.find_element(By.XPATH, '//uni-view[contains(@class,"btn-pay")]')
                if element.text.strip() == "去支付":
                    print('可以点击')
                    # 循环点击
                    element.click()
                    time.sleep(0.8)
                    try:
                        element = WebDriverWait(browser, 3).until(
                            EC.element_to_be_clickable(
                                (By.XPATH, '//uni-view[contains(@class,"payment-type-title")]'))
                        )
                        if element.text.strip() == "选择支付方式":
                            print('已买到,请立刻付款')
                            time.sleep(5)
                            break
                    except Exception as e:
                        browser.refresh()
                        time.sleep(0.5)

四、完整代码

完整代码如下:

  1. config.py
# 抢票模式,必填
ticket_model = '刷回流'  # 目前只有:'刷回流'、'刚开票'(需提前预选好场次、购票人)
# 项目id,必填
show_id = '66f27787ed376600017c8029'
# 场次,必填
session = '2025-01-01 周三 19:00'
# 票价,必填
ticket_price = '1980元' # '看台 680元'
# 购票数量,一定要看购票须知,不要超过上限,必填
buy_count = 1
  1. main.py
from selenium import webdriver
from selenium.webdriver.edge.service import Service
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
import time
import os

import config


def hex_str_subtract_one(hex_str):
    # 将十六进制字符串转换为整数
    num = int(hex_str, 16)
    # 对整数进行减 1 操作
    num -= 1
    # 将结果转换回十六进制字符串
    # 使用 format 函数确保结果的长度与原始字符串一致
    result_hex_str = format(num, 'x').zfill(len(hex_str))

    return result_hex_str
    

def ready_to_buy(buy_count, browser):
    while True:
        try:
            choose_date(browser)
            choose_price(browser)
            choose_ticket_number(buy_count, browser)
            break
        except Exception as e:
            print('未开卖')
            browser.refresh()
            time.sleep(0.5)
    return


def choose_date(browser):
    while True:
        try:
            elements = WebDriverWait(browser, 4).until(
                EC.presence_of_all_elements_located((By.XPATH, '//uni-text[contains(@class,"session-name")]'))
            )
            break
        except Exception as e:
            browser.refresh()
            time.sleep(0.5)
    for element in elements:
        if element.text.strip() == session:
            element.click()
            time.sleep(0.1)
            break
    return


def choose_price(browser):
    elements = WebDriverWait(browser, 3).until(
        EC.presence_of_all_elements_located((By.XPATH, '//uni-text[contains(@class,"ticket")]'))
    )
    # elements = browser.find_elements(By.XPATH, '//uni-text[contains(@class,"ticket")]')
    for element in elements:
        print(element.text.strip())
        if element.text.strip() == ticket_price:
            element.click()
            time.sleep(0.1)
            break
    return

def choose_ticket_number(buy_count, browser):
    for i in range(buy_count):
        element = browser.find_element(By.XPATH, '//uni-view[contains(@class,"number")]')
        print('当前购票数:', int(element.text.strip()))
        if int(element.text.strip()) == buy_count:
            break
        else:
            element_add = browser.find_element(By.XPATH,
                                               "//uni-view[contains(concat(' ', normalize-space(@class), ' '), ' plus iconfont icon-jia ')]")
            element_add.click()
            time.sleep(0.1)
    return


ticket_model = config.ticket_model
show_id = config.show_id
session = config.session
ticket_price = config.ticket_price
buy_count = config.buy_count


if __name__ == '__main__':
    print('当前模式:', ticket_model)
    edge_options = webdriver.EdgeOptions()
    edge_options.add_argument(r"user-data-dir=D:\csdn\Mint_V\scoped_dir18252_1702533799")
    browser = webdriver.Edge(options=edge_options)

    stdShowId = hex_str_subtract_one(show_id)
    url = "https://m.piaoxingqiu.com/booking/"+show_id+"?saleShowSessionId=&seatPickType=SUPPORT_NONE&selectedDate=&showId="+show_id+"&stdShowId="+stdShowId
    print(url)
    browser.get(url)
    time.sleep(1)

    # 刷新页面直到可以买票
    ready_to_buy(buy_count, browser)
    # 点击下一步
    element = browser.find_element(By.XPATH, '//uni-view[contains(@class,"one-btn bottom-btn-item")]')
    element.click()
    time.sleep(0.5)

    # =======页面跳转========
    # 下单
    if ticket_model == '刚开票':
        while True:
            element = WebDriverWait(browser, 3).until(
                EC.element_to_be_clickable(
                    (By.XPATH, '//uni-view[contains(@class,"btn-pay")]'))
            )
            print('可以点击')
            # 循环点击
            time.sleep(0.1)
            element.click()
            try:
                element = WebDriverWait(browser, 3).until(
                    EC.element_to_be_clickable(
                        (By.XPATH, '//uni-view[contains(@class,"payment-type-title")]'))
                )
                if element.text.strip() == "选择支付方式":
                    print('已买到,请立刻付款')
                    time.sleep(5)
                    break
            except Exception as e:
                browser.refresh()
                time.sleep(0.5)

    elif ticket_model == '刷回流':
        # 选购票人
        while True:
            element_xpath = "//uni-view[@class='iconfont icon-choose icon-xuanzhong']"
            try:
                elements = browser.find_elements(By.XPATH, element_xpath)
                break
            except Exception as e:
                browser.refresh()
                time.sleep(0.5)

        if len(elements) > 0:  # 购票人已勾选
            print("已勾选购票人.")
            while True:
                element = WebDriverWait(browser, 3).until(
                    EC.element_to_be_clickable(
                        (By.XPATH, '//uni-view[contains(@class,"btn-pay")]'))
                )
                if element.text.strip() == "去支付":
                    print('可以点击')
                    # 循环点击
                    element.click()
                    try:
                        element = WebDriverWait(browser, 3).until(
                            EC.element_to_be_clickable(
                                (By.XPATH, '//uni-view[contains(@class,"payment-type-title")]'))
                        )
                        if element.text.strip() == "选择支付方式":
                            print('已买到,请立刻付款')
                            time.sleep(5)
                            break
                    except Exception as e:
                        browser.refresh()
                        time.sleep(0.5)

        else:
            print("购票人未勾选,自动选择预购票数相同的前几位购票人.")
            elements = WebDriverWait(browser, 3).until(
                EC.presence_of_all_elements_located((By.XPATH, "//uni-view[@class='iconfont icon-choose icon-weigouxuan']"))
            )
            for element in elements:
                element.click()
                time.sleep(0.1)

            while True:
                element = WebDriverWait(browser, 3).until(
                    EC.element_to_be_clickable(
                        (By.XPATH, '//uni-view[contains(@class,"btn-pay")]'))
                )
                # element = browser.find_element(By.XPATH, '//uni-view[contains(@class,"btn-pay")]')
                if element.text.strip() == "去支付":
                    print('可以点击')
                    # 循环点击
                    element.click()
                    time.sleep(0.8)
                    try:
                        element = WebDriverWait(browser, 3).until(
                            EC.element_to_be_clickable(
                                (By.XPATH, '//uni-view[contains(@class,"payment-type-title")]'))
                        )
                        if element.text.strip() == "选择支付方式":
                            print('已买到,请立刻付款')
                            time.sleep(5)
                            break
                    except Exception as e:
                        browser.refresh()
                        time.sleep(0.5)

    input()

五、补充信息

  1. 作者用程序抢到了一张张悬的回流票,但是没付款只是测试程序。建议大家拿还有票的演出运行下程序,能对各部分程序的功能有更深入的了解。文章的内容如果有表述不清晰的地方,欢迎大家讨论;
  2. 整体程序运行起来可能还是不太智能,需要在使用过程中慢慢优化。比如到最后的下单页面时,因为被别人抢先买走了,会出现弹窗提示,而此时程序已经默认执行结束,所以不会有反应。这时候,就需要手动终止运行,再重新运行程序的回流模式。这一块有兴趣的朋友可以自己改进一下,后面有时间的话作者会考虑优化;
  3. 能否抢到还是跟网速有直接关系,其次是号是否被盾;
  4. 使用须知:票星球有服务声明,使用自动化程序的交易订单,官方有权利取消哦= ̄ω ̄=。

总结

本篇主要实现了selenium自动抢票,做下来感觉抢票还挺有意思的,关键点在于各个元素的获取和整体逻辑要考虑好,剩下的就是无脑执行了。其他网站的爬虫方法也大同小异,主要是一些网站的反爬虫机制比较厉害,后续有机会的话可以再深入了解一下。

一些杂七杂八

  1. 未来可以考虑使用uiautomator2等库,用电脑连接手机直接操作APP,彻底解放双手自由;
  2. 探索一点监控回流的小方法。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值