> 技术文档 > 【Python】利用selenium实现自动抢票(ticket星球网页版)_python自动抢票

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


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

  • 前言
  • selenium简单介绍
  • 抢票流程分析
    • 一、进入网页
    • 二、 选择观演日期和票价
    • 三、选择观演人数和观演人
    • 四、参数设置
  • 程序具体执行
    • 一、连接浏览器
    • 二、选择场次日期和价位
    • 三、选择观演人及确认下单
    • 四、完整代码
    • 五、补充信息
  • 总结
  • 一些杂七杂八

前言

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

selenium简单介绍

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

抢票流程分析

一、进入网页

直接进入购票界面,如图所示:【Python】利用selenium实现自动抢票(ticket星球网页版)_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元’,在抢票时会与填写的内容进行字符比对,完全一致时才会点击,所以必须要注意。
【Python】利用selenium实现自动抢票(ticket星球网页版)_python自动抢票

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

  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 webdriverfrom selenium.webdriver.edge.service import Servicefrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECfrom selenium.common.exceptions import TimeoutExceptionfrom selenium.webdriver.common.by import Byimport timeimport osimport configticket_model = config.ticket_modelshow_id = config.show_idsession = config.sessionticket_price = config.ticket_pricebuy_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=\"+stdShowIdprint(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

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

  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. 判断是否已经开票。之前的大多数抢票代码是设定开票时间,通过判断当前时间与开票时间的误差,确定是否抢票,其实是有一些问题的。在此提供一个新的思路,下图分别为能购票和还未开票的界面,可以看到它们之间的区别在于能否选择购票数量,也就是购票数量元素是否存在。因此,选择购票人数的相关代码如下。
    【Python】利用selenium实现自动抢票(ticket星球网页版)_python自动抢票
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 webdriverfrom selenium.webdriver.edge.service import Servicefrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECfrom selenium.common.exceptions import TimeoutExceptionfrom selenium.webdriver.common.by import Byimport timeimport osimport configdef 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) returndef 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 returndef 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 returndef 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) returnticket_model = config.ticket_modelshow_id = config.show_idsession = config.sessionticket_price = config.ticket_pricebuy_count = config.buy_countif __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. 探索一点监控回流的小方法。