闲来无事,打开大麦网发现现在大多数演唱票都需要手机端才能抢票,仅有很少一部分支持pc端用网页去抢票,但正所谓:道高一尺,魔高一丈,解决这个反爬问题,我们可以采用Airtest连接仿真机来模拟手机端操作,这次教程我们就先着手去解决利用selenium解决少部分可以用pc端抢票的问题。如果针对手机端抢票的呼声较高,后面我会出一篇关于Airtest抢票的blog。
前提声明:
1、本教程仅用于学习和研究使用,不得用于商业行为。
2、请确保在合法合规的前提前下使用本代码
3、本教程所涉及的操作均为正常模拟用户操作,不涉及任何数据入侵或数据窃取。
一、引言
在热门演出和赛事门票一票难求的今天,利用自动化工具来提高抢票成功率成为很多人的需求,本文将详细介绍如何使用chromedriver及selenium利用python来编写一个简单的大麦网自动抢票脚本。
二、准备工作
在开始之前,确保开发环境中安装了我们所需要的库:
chromedriver版本: 131.0.6778.87
chrome版本:131.0.6778.140
selenium:4.27.1(用于网页自动化操作)
如果这些并未安装,可以看我之前写的blog,也可以在B站等平台搜索资源进行学习。这里我们就不详细说了。
三、代码分析
1、所需页面URL
首先我们需要定义大麦网首页、登陆页面以及我们想要抢的那张票的页面URL。
damai_url = "https://www.damai.cn/“
login_url = ”https://passport.damai.cn/login?ru=https%3A%2F%2Fwww.damai.cn%2F“
target_url ="https://detail.damai.cn/item.htm?spm=a2oeg.home.card_0.ditem_3.591b23e11Li5yj&id=862317821501“
我们主要采取面向对象的思想来编写代码,我们首先定义一个类对象,然后进行初始化加载。
class Concert:
def __init__(self):
self.status = 0 # 状态,表示当前操作执行到哪一步
self.login_method = 1 # 0:模拟登录 1:使用cookie登录
self.driver = webdriver.Chrome()
然后执行登陆操作,我们需要判断是否需要进行模拟登陆操作,如果需要模拟登录就先打开登陆页面:
"""登录"""
def login(self):
# 如果为0,模拟登录
if self.login_method == 0:
self.driver.get(login_url)
elif self.login_method == 1:
# 如果当前目录下没有这个cookie.pkl文件
if not os.path.exists('cookie.pkl'):
# 登陆一下记录登录信息
self.set_cookies()
else:
self.driver.get(target_url)
# 登陆一下 通过selenium传入一些信息
self.get_cookie()
我们来看定义的两个函数:set_cookies()和get_cookie():
"""cookies:登陆网站的时候出现的,记录用户信息"""
def set_cookies(self):
self.driver.get(login_url)
print("###请扫码登陆###")
time.sleep(10)
print("###登陆成功###")
pickle.dump(self.driver.get_cookies(), open('cookie.pkl','wb')) #获取登陆的信息,并保存下来
print("###cookie保存成功###")
# 登陆成功后就跳转到抢票页面
self.driver.get(target_url)
# time.sleep(2)
# 如果文件中已经有了cookie.pkl文件
def get_cookie(self):
cookies = pickle.load(open('cookie.pkl','rb'))
for cookie in cookies:
cookie_dict = {
"domain":".damai.cn",
"name":cookie.get("name"),
"value":cookie.get("value"),
}
self.driver.add_cookie(cookie_dict)
print("###载入cookie成功###")
打开浏览器,状态此时改为1
def enter_concert(self):
print("###打开浏览器,进入大麦网###")
# 调用登录
self.login()
self.driver.refresh()
self.status = 1
print("###登陆成功###")
购票具体逻辑地实现:如果我们还停留在我们要买的这张票的页面,门票的信息可能是缺货,这个时候需要我们不断的刷新,因此我们可以写一个while循环来实现它,直到点击进入页面:订单确认页为止。给不同的情况,赋予不同的状态,并采用不同的逻辑去判断,比如需要我们手动选座购买的逻辑代码等等。然后就是进入订单确认页的具体逻辑执行,我们可以采取xpath的方式去定位元素。把具体的代码封装到方法中。
# 抢票并下单:首先判断是否能够购买,如果不能就一直刷新网页,知道能够购买为止
def choose_ticket(self):
if self.status == 1:
print("="*30)
print("###请选择日期以及票价###")
while self.driver.title.find("订单确认页") == -1: # driver.title.find寻找索引页,如果找不到返回-1,找到返回索引页
# 下单按钮
button = self.driver.find_element(By.XPATH,'/html/body/div[2]/div/div[1]/div[1]/div/div[2]/div[3]/div[9]/div/div[3]/div[3]').text
if button == "提交缺货登记": # 这里也就是说不一定会是"不,立即购票"这几个字,也有可能会是提交缺货登记等
self.driver.refresh()
elif button == "不,立即购票":
self.driver.find_element(By.XPATH,'/html/body/div[2]/div/div[1]/div[1]/div/div[2]/div[3]/div[9]/div/div[3]/div[3]').click()
time.sleep(10)
elif button == "不,选座购票":
self.driver.find_element(By.XPATH,'/html/body/div[2]/div/div[1]/div[1]/div/div[2]/div[3]/div[9]/div/div[3]/div[3]').click()
self.status = 2
else:
self.status = 3
tittle = self.driver.title
if tittle == "选择座位":
print("###请选择座位###")
time.sleep(10)
self.driver.find_element('//*[@id="root"]/div/div[4]/div[2]/button').click()
elif tittle == "订单确认页":
while True:
print("###正在加载中###")
self.order_check()
break
其实这里如果不是企业级的项目,而是自用的话,可以直接通过selenium语句去写观影人等信息。
# self.driver.find_element(By.XPATH,'//*[@id="dmViewerBlock_DmViewerBlock"]/div/div/div[3]').click()
# name = self.driver.find_element(By.XPATH,'//*[@id="addholder-model"]/div/div[1]/div/div[1]/input').clear().send_keys("your_name")
# testify = self.driver.find_element(By.XPATH,'//*[@id="addholder-model"]/div/div[1]/div/div[5]/input').clear().send_keys("your_password")
# self.driver.find_element(By.XPATH,'//*[@id="addholder-model"]/div/div[1]/div/div[8]/div').click()
# time.sleep(2)
# self.driver.find_element(By.XPATH,'//*[@id="dmViewerBlock_DmViewerBlock"]/div[2]/div/div/div[2]/i').click()
我们来看order_check()这个方法的具体实现,其实就是勾选观影人(通过selenium去定位接口)然后提交订单即可。
def order_check(self):
print("###开始确认订单###")
try:
self.driver.find_element(By.XPATH, '//*[@id="dmViewerBlock_DmViewerBlock"]/div[2]/div/div/div[2]/i').click()
except Exception as e:
print("###购票人信息选择失败,请重新选择元素###")
print(e)
time.sleep(0.5)
shoujihao = self.driver.find_element(By.XPATH,'//*[@id="dmContactBlock_DmContactBlock"]/div[2]/div/div[2]/input').clear()
time.sleep(2)
self.driver.find_element(By.XPATH,'//*[@id="dmContactBlock_DmContactBlock"]/div[2]/div/div[2]/input').send_keys("your_iphone_number")
time.sleep(2)
self.driver.find_element(By.XPATH, '//*[@id="dmOrderSubmitBlock_DmOrderSubmitBlock"]/div[2]/div/div[2]/div[2]/div[2]').click()
time.sleep(10)
以上就是实现大麦网自动抢票的全部代码实现,其实逻辑来讲并不复杂,主要就是selenium的一些应用,最重要的是我们需要学习当中的面向对象的思想以及逻辑的复现,这是我们需要我们掌握并且要攻克的难点。
四、代码优化与注意事项
1、元素定位:要准确的使用xpath或者其他定位方式(如CSS选择器)来定位页面元素,因为大麦网也买你可能会更新,元素的xpath语法可能会改变,所以需要定期检查和调整代码。
2、等待时间:合理设置等待的时间,避免因为也页面加载缓慢导致操作失败,但也不能设置过长的时间影响抢票效率。可以使用selenium中的显示等待和隐式等待来优化。
3、多线程:可以考虑使用多线程技术,同时监控多个场次或者多个门票的抢购情况,提高抢票成功的概率,但要注意大麦网的相关规则,避免被判定为异常操作。
五、全部代码
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
import os
import pickle
# 自动登录
# 大麦网首页
damai_url = "https://www.damai.cn/"
# 登录页面网址
login_url = "https://passport.damai.cn/login?ru=https%3A%2F%2Fwww.damai.cn%2F"
# 要抢票的网址
target_url = "https://detail.damai.cn/item.htm?spm=a2oeg.home.card_0.ditem_3.591b23e11Li5yj&id=862317821501"
class Concert:
def __init__(self):
self.status = 0 # 状态,表示当前操作执行到哪一步
self.login_method = 1 # 0:模拟登录 1:使用cookie登录
self.driver = webdriver.Chrome()
"""cookies:登陆网站的时候出现的,记录用户信息"""
def set_cookies(self):
self.driver.get(login_url)
print("###请扫码登陆###")
time.sleep(10)
print("###登陆成功###")
pickle.dump(self.driver.get_cookies(), open('cookie.pkl','wb')) #获取登陆的信息,并保存下来
print("###cookie保存成功###")
# 登陆成功后就跳转到抢票页面
self.driver.get(target_url)
# self.zhanghao = self.driver.find_element(By.XPATH, '//*[@id="fm-login-id"]').send_keys("17719114890")
# self.mima = self.driver.find_element(By.XPATH, '//*[@id="fm-login-password"]').send_keys("dwq0219423")
# self.button = self.driver.find_element(By.XPATH, '//*[@id="fm-login-submit"]').click()
# time.sleep(2)
# 如果文件中已经有了cookie.pkl文件
def get_cookie(self):
cookies = pickle.load(open('cookie.pkl','rb'))
for cookie in cookies:
cookie_dict = {
"domain":".damai.cn",
"name":cookie.get("name"),
"value":cookie.get("value"),
}
self.driver.add_cookie(cookie_dict)
time.sleep(10) # 在这里记得手动刷新一下,用户信息才会显示出来
print("###载入cookie成功###")
"""登录"""
def login(self):
# 如果为0,模拟登录
if self.login_method == 0:
self.driver.get(login_url)
elif self.login_method == 1:
# 如果当前目录下没有这个cookie.pkl文件
if not os.path.exists('cookie.pkl'):
# 登陆一下记录登录信息
self.set_cookies()
else:
self.driver.get(target_url)
# 登陆一下 通过selenium传入一些信息
self.get_cookie()
"""打开浏览器"""
def enter_concert(self):
print("###打开浏览器,进入大麦网###")
# 调用登录
self.login()
self.driver.refresh()
self.status = 1
print("###登陆成功###")
# 抢票并下单:首先判断是否能够购买,如果不能就一直刷新网页,知道能够购买为止
def choose_ticket(self):
if self.status == 1:
print("="*30)
print("###请选择日期以及票价###")
while self.driver.title.find("订单确认页") == -1: # driver.title.find寻找索引页,如果找不到返回-1,找到返回索引页
# 下单按钮
button = self.driver.find_element(By.XPATH,'/html/body/div[2]/div/div[1]/div[1]/div/div[2]/div[3]/div[9]/div/div[3]/div[3]').text
if button == "提交缺货登记": # 这里也就是说不一定会是"不,立即购票"这几个字,也有可能会是提交缺货登记等
self.driver.refresh()
elif button == "不,立即购票":
self.driver.find_element(By.XPATH,'/html/body/div[2]/div/div[1]/div[1]/div/div[2]/div[3]/div[9]/div/div[3]/div[3]').click()
time.sleep(10)
elif button == "不,选座购票":
self.driver.find_element(By.XPATH,'/html/body/div[2]/div/div[1]/div[1]/div/div[2]/div[3]/div[9]/div/div[3]/div[3]').click()
self.status = 2
else:
self.status = 3
tittle = self.driver.title
if tittle == "选择座位":
print("###请选择座位###")
time.sleep(10)
self.driver.find_element('//*[@id="root"]/div/div[4]/div[2]/button').click()
elif tittle == "订单确认页":
while True:
print("###正在加载中###")
self.order_check()
break
# 实现下单的逻辑
# self.driver.find_element(By.XPATH,'//*[@id="dmViewerBlock_DmViewerBlock"]/div/div/div[3]').click()
# name = self.driver.find_element(By.XPATH,'//*[@id="addholder-model"]/div/div[1]/div/div[1]/input').clear().send_keys("your_name")
# testify = self.driver.find_element(By.XPATH,'//*[@id="addholder-model"]/div/div[1]/div/div[5]/input').clear().send_keys("your_password")
# self.driver.find_element(By.XPATH,'//*[@id="addholder-model"]/div/div[1]/div/div[8]/div').click()
# time.sleep(2)
# self.driver.find_element(By.XPATH,'//*[@id="dmViewerBlock_DmViewerBlock"]/div[2]/div/div/div[2]/i').click()
def order_check(self):
print("###开始确认订单###")
try:
self.driver.find_element(By.XPATH, '//*[@id="dmViewerBlock_DmViewerBlock"]/div[2]/div/div/div[2]/i').click()
except Exception as e:
print("###购票人信息选择失败,请重新选择元素###")
print(e)
time.sleep(0.5)
shoujihao = self.driver.find_element(By.XPATH,'//*[@id="dmContactBlock_DmContactBlock"]/div[2]/div/div[2]/input').clear()
time.sleep(2)
self.driver.find_element(By.XPATH,'//*[@id="dmContactBlock_DmContactBlock"]/div[2]/div/div[2]/input').send_keys("your_iphone_number")
time.sleep(2)
self.driver.find_element(By.XPATH, '//*[@id="dmOrderSubmitBlock_DmOrderSubmitBlock"]/div[2]/div/div[2]/div[2]/div[2]').click()
time.sleep(10)
if __name__ == '__main__':
concert = Concert()
concert.enter_concert()
concert.choose_ticket()
六、总结
通过使用chromedriver和selenium库,我们可以编写一个简单的大麦网自动抢票脚本。但要注意,自动抢票可能存在违反平台规则以及法律风险等情况,在使用时需要谨慎并确保自己的行为合法合规。同时,也希望票务平台能够不断优化售票机制,让更多真正有需求的用户能够公平的购买到门票。