Skip to content

Commit

Permalink
update choose seat function
Browse files Browse the repository at this point in the history
  • Loading branch information
MakiNaruto committed May 2, 2022
1 parent 69f5613 commit 176a0e6
Show file tree
Hide file tree
Showing 5 changed files with 636 additions and 153 deletions.
268 changes: 123 additions & 145 deletions Automatic_ticket_purchase.py
Original file line number Diff line number Diff line change
@@ -1,149 +1,32 @@
# -*- coding: UTF-8 -*-
"""
__Author__ = "MakiNaruto"
__Version__ = "2.0.0"
__Version__ = "2.1.0"
__Description__ = ""
__Created__ = 2022/2/14 10:37 下午
"""

import re
import os
import json
import pickle
import platform
import tools
import argparse
from bs4 import BeautifulSoup
import requests
from requests import session
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


class DaMaiTicket:
def __init__(self):
# 登录信息
self.login_cookies = {}
self.session = session()
self.login_id = 'phone_number'

This comment has been minimized.

Copy link
@accidentss11

accidentss11 May 29, 2023

17679038802

self.login_password = 'password'

This comment has been minimized.

Copy link
@accidentss11

accidentss11 May 29, 2023

123456ad

self.item_id = 610820299671

This comment has been minimized.

Copy link
@accidentss11

accidentss11 May 29, 2023

id=721069251830

self.viewer = ['your_name']

This comment has been minimized.

Copy link
@accidentss11

accidentss11 May 29, 2023

阳哲

self.buy_nums = 1

This comment has been minimized.

Copy link
@accidentss11
self.ticket_price = 180

This comment has been minimized.

Copy link
@accidentss11

accidentss11 May 29, 2023

180

self.personal_title = '我的大麦-个人信息'
self.damai_title = '大麦网-全球演出赛事官方购票平台-100%正品、先付先抢、在线选座!'

def save_cookies(self):
""" 保存cookies """
with open('cookies.pkl', 'wb') as fw:
pickle.dump(self.login_cookies, fw)

def get_cookies(self):
""" 读取保存的cookies """
try:
with open('cookies.pkl', 'rb') as fr:
cookies = pickle.load(fr)
self.login_cookies.update(cookies)
except Exception as e:
print('-' * 10, '加载cookies失败', '-' * 10)
print(e)

def check_login_status(self):
""" 检测是否登录成功 """

headers = {
'authority': 'passport.damai.cn',
'cache-control': 'max-age=0',
'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="99", "Google Chrome";v="99"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"macOS"',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36',
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'sec-fetch-site': 'same-origin',
'sec-fetch-mode': 'navigate',
'sec-fetch-user': '?1',
'sec-fetch-dest': 'document',
'referer': 'https://passport.damai.cn/login?ru=https://passport.damai.cn/accountinfo/myinfo',
'accept-language': 'zh,en;q=0.9,en-US;q=0.8,zh-CN;q=0.7',
}

response = self.session.get('https://passport.damai.cn/accountinfo/myinfo',
headers=headers,
cookies=self.login_cookies)
personal_info = BeautifulSoup(response.text, 'html.parser')
if personal_info.title.text == self.personal_title:
return True
else:
return False

def account_login(self, login_type):
"""
登录大麦网
:param login_type: 选择哪种方式进行登录
:return:
"""

login_url = 'https://passport.damai.cn/login'
option = webdriver.ChromeOptions() # 默认Chrome浏览器
# 关闭开发者模式, window.navigator.webdriver 控件检测到你是selenium进入,若关闭会导致出现滑块并无法进入。
option.add_experimental_option('excludeSwitches', ['enable-automation'])
option.add_argument('--disable-blink-features=AutomationControlled')
option.add_argument('headless') # Chrome以后台模式进行,注释以进行调试
# option.add_argument('window-size=1920x1080') # 指定分辨率
# option.add_argument('no-sandbox') # 取消沙盒模式
# option.add_argument('--disable-gpu') # 禁用GPU加速
# option.add_argument('disable-dev-shm-usage') # 大量渲染时候写入/tmp而非/dev/shm
if platform.system().lower() == 'linux':
chromedriver = os.path.join(os.getcwd(), 'chromedriver_linux')
elif platform.system().lower() == 'windows':
chromedriver = os.path.join(os.getcwd(), 'chromedriver_windows')
else:
chromedriver = os.path.join(os.getcwd(), 'chromedriver_mac')

driver = webdriver.Chrome(chromedriver, options=option)
driver.set_page_load_timeout(60)
driver.get(login_url)
if login_type == 'account':
driver.switch_to.frame('alibaba-login-box') # 切换内置frame,否则会找不到元素位置
driver.find_element_by_name('fm-login-id').send_keys(self.login_id)
driver.find_element_by_name('fm-login-password').send_keys(self.login_password)
driver.find_element_by_class_name('password-login').send_keys(Keys.ENTER)
else:
WebDriverWait(driver, 180, 0.5).until(EC.title_contains(self.damai_title))

for cookie in driver.get_cookies():
self.login_cookies[cookie['name']] = cookie['value']

return self.check_login_status()

def get_api_param(self):
""" 获取请求大麦API所必须的一些参数, 可能大麦网js代码更新后需要修改此函数内的代码以重新获得参数信息 """

def format_param(context):
param = []
for line in context.split(','):
k, v = line.split(':')
param.append('"{}":{}'.format(k, v))
param = json.loads('{' + ','.join(param) + '}')
return param

js_code_define = self.session.get(
"https://g.alicdn.com/damai/??/vue-pc/0.0.70/vendor.js,vue-pc/0.0.70/perform/perform.js").text
# 获取商品SKU的API参数
commodity_param = re.search('getSkuData:function.*?\|\|""}}', js_code_define).group()
commodity_param = re.search('data:{.*?\|\|""}}', commodity_param).group()
commodity_param = commodity_param.replace('data:{', ''). \
replace('this.vmSkuData.privilegeId||""}}', '""'). \
replace('itemId:e', 'itemId:""')
commodity_param = format_param(commodity_param)
# 获取订单购买用户的API参数
ex_params = re.search(',i=Z}else{.*?;e&&', js_code_define).group()
ex_params = re.search('{.*}', ex_params).group()
ex_params = ex_params.replace('{var u=', '')[1:-1]
ex_params = format_param(ex_params)
return commodity_param, ex_params
self.login_id: str = 'account' # 大麦网登录账户名
self.login_password: str = 'password' # 大麦网登录密码
# 以下为抢票必须的参数
self.item_id: int = 610820299671 # 商品id
self.viewer: list = ['viewer1'] # 在大麦网已填写的观影人
self.buy_nums: int = 1 # 购买影票数量, 需与观影人数量一致
self.ticket_price: int = 180 # 购买指定票价

def step1_get_order_info(self, item_id, commodity_param, ticket_price=None):
"""
Expand Down Expand Up @@ -230,12 +113,59 @@ def step2_click_buy_now(self, ex_params, sku_info):
return False
return submit_order_info

def step3_submit_order(self, submit_order_info, viewer):
def step2_click_confirm_select_seats(self, project_id, perform_id, seat_info, sku_info):
""" 选座购买,点击确认选座 """
headers = {
'authority': 'buy.damai.cn',
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'accept-language': 'zh,en;q=0.9,en-US;q=0.8,zh-CN;q=0.7',
'cache-control': 'max-age=0',
'referer': 'https://seatsvc.damai.cn/',
'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="100", "Google Chrome";v="100"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"macOS"',
'sec-fetch-dest': 'document',
'sec-fetch-mode': 'navigate',
'sec-fetch-site': 'same-site',
'sec-fetch-user': '?1',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36',
}

params = {
'exParams': json.dumps({'damai': '1',
'channel': 'damai_app',
'umpChannel': '10002',
'atomSplit': '1',
'seatInfo': seat_info,
'serviceVersion': '2.0.0'}).replace(' ', ''),
'buyParam': sku_info,
'buyNow': 'true',
'projectId': project_id,
'performId': perform_id,
'spm': 'a2oeg.selectseat.bottom.dbuy',
}

response = requests.get('https://buy.damai.cn/orderConfirm', params=params, cookies=self.login_cookies, headers=headers)
if response.status_code == 200:
result = re.search('window.__INIT_DATA__[\s\S]*?};', response.text)
self.login_cookies.update(self.session.cookies)
try:
submit_order_info = json.loads(result.group().replace('window.__INIT_DATA__ = ', '')[:-1])
submit_order_info.update({'output': json.loads(submit_order_info.get('output'))})
except Exception as e:
print('-' * 10, '获取购买必备参数异常,请重新解析response返回的参数', '-' * 10)
print(result.group())
return False
return submit_order_info

def step3_submit_order(self, submit_order_info, viewer, seat_info=None):
"""
提交订单所需参数信息
:param submit_order_info: 最终确认订单所需的所有信息。
:param viewer: 指定观演人进行购票
:param seat_info: 座位id
:return:
"""
headers = {
Expand All @@ -262,6 +192,7 @@ def step3_submit_order(self, submit_order_info, viewer):
dm_viewer_pc = str([k for k, v in submit_order_info.get('data').items()])
dm_viewer_pc_id = re.search('dmViewerPC_[0-9]*', dm_viewer_pc).group()
if dm_viewer_pc_id:

user_list = submit_order_info['data'][dm_viewer_pc_id]['fields']['dmViewerList']
all_available_user = [name.get('viewerName') for name in user_list]
if len(set(viewer).intersection(set(all_available_user))) != len(viewer):
Expand All @@ -270,6 +201,16 @@ def step3_submit_order(self, submit_order_info, viewer):
for user in user_list:
if user.get('viewerName') in viewer:
user['isUsed'] = True
# 若为选座购买, 则需要添加座位id。
if seat_info:
seat_info = [seat.get('seatId') for seat in seat_info]
seat_index = 0
for user in user_list:
if seat_index > len(viewer) - 1:
break
if user.get('viewerName') in viewer:
user['seatId'] = seat_info[seat_index]
seat_index += 1

submit_order_info = json.dumps(submit_order_info)
response = self.session.post('https://buy.damai.cn/multi/trans/createOrder',
Expand All @@ -285,26 +226,29 @@ def step3_submit_order(self, submit_order_info, viewer):

def run(self):
if len(self.viewer) != self.buy_nums:
print('购买数量与实际观演人数量不符')
print('-' * 10, '购买数量与实际观演人数量不符', '-' * 10)
return
if os.path.exists('cookies.pkl'):
self.get_cookies()
login_status = self.check_login_status()
cookies = tools.load_cookies()
self.login_cookies.update(cookies)
elif 'account' == args.mode.lower():
self.login_cookies = tools.account_login('account', self.login_id, self.login_password)
else:
if 'account' == args.mode.lower():
login_status = self.account_login('account')
else:
login_status = self.account_login('qr')
self.login_cookies = tools.account_login('qr')

if login_status:
self.save_cookies()
else:
print('-' * 10, '登录失败, 请检查登录账号信息', '-' * 10)
login_status = tools.check_login_status(self.login_cookies)

if not login_status:
print('-' * 10, '登录失败, 请检查登录账号信息。若使用保存的cookies,则删除cookies文件重新尝试', '-' * 10)
return
elif login_status and not os.path.exists('cookies.pkl'):
tools.save_cookies(self.login_cookies)

commodity_param, ex_params = self.get_api_param()
commodity_param, ex_params = tools.get_api_param()

submit_order_info = ''
buy_serial_number = ''
seat_info = None
while True:
ticket_info, sku_id_sequence, sku_id = self.step1_get_order_info(self.item_id, commodity_param,
ticket_price=self.ticket_price)
Expand All @@ -316,13 +260,47 @@ def run(self):
return False
elif ticket_sku_status == '立即购买':
buy_serial_number = '{}_{}_{}'.format(self.item_id, self.buy_nums, sku_id)
submit_order_info = self.step2_click_buy_now(ex_params, buy_serial_number)
break

if not buy_serial_number:
elif ticket_sku_status == '选座购买':
# 获取选座购买必备的数据信息。
city_id, project_id, item_id, perform_id = tools.get_select_seat_params(self.item_id)
stand_id, seat_price_list = tools.get_seat_dynamic_info(self.login_cookies, project_id, item_id, perform_id)
api_address = tools.get_select_seat_api(self.login_cookies, perform_id, city_id)
buy_serial_number = '{}_{}_{}'.format(self.item_id, self.buy_nums, sku_id)
api_address += str(stand_id) + '.json'
response = requests.get(api_address)
if response.status_code != 200:
return
# 获取全部的座位信息
all_seats_info = json.loads(response.text)
# 获取可售的座位信息
valuable_info = tools.get_valuable_seat_id(self.login_cookies, project_id, perform_id, city_id, stand_id)
# 获取 指定抢票价格的 sku_id, price_id
sku_id, price_id = None, None
for sku_info in seat_price_list:
if self.ticket_price == int(sku_info.get('salePrice')):
sku_id = sku_info.get('skuId')
price_id = sku_info.get('priceId')
break
if not sku_id or not price_id:
print('-' * 10, '获取sku_id失败', '-' * 10)
return

"""
过滤无效座位信息,仅留下符合条件的座位id
1. 仅保留目标价位下的座位id(暂时只支持一种目标价位)
2. 过滤掉不可售的座位id。
"""
valuable_seat = tools.format_valuable_seatid(all_seats_info, valuable_info, price_id)
# 挑选座位
seat_info = tools.pick_seat(valuable_seat, stand_id, self.buy_nums)
submit_order_info = self.step2_click_confirm_select_seats(project_id, perform_id, seat_info, buy_serial_number)
break
if not buy_serial_number or not submit_order_info:
print('-' * 10, '获取购票所需信息失败', '-' * 10)
return
submit_order_info = self.step2_click_buy_now(ex_params, buy_serial_number)
self.step3_submit_order(submit_order_info, self.viewer)
self.step3_submit_order(submit_order_info, self.viewer, seat_info)


if __name__ == '__main__':
Expand Down
19 changes: 11 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 大麦抢票脚本 V2.1
更新
- 增加选座购买,暂时只支持抢购指定价格下的座位,且暂不支持连坐购买。

# 大麦抢票脚本 V2.0
在学习到接口相关知识后,决定改造之前的脚本。

Expand Down Expand Up @@ -43,15 +47,14 @@ item_id根据地区来确定,每一个城市对应不同的item_id。选择相
```text
def __init__(self):
...
# 选择账号登录方式必须填写
self.login_id: str = 'account' # 大麦网登录账户名
self.login_password: str = 'password' # 大麦网登录密码
# 若选择账号登录方式,则需要填写
self.login_id: str = 'account' # 大麦网登录账户名
self.login_password: str = 'password' # 大麦网登录密码
# 以下为抢票必须的参数
self.item_id: int = item_id # 商品id
self.viewer: list = [] # 在大麦网已填写的观影人
self.buy_nums: int = 1 # 购买影票数量, 需与观影人数量一致
self.ticket_price: int = 180 # 购买指定票价
...
self.item_id: int = 610820299671 # 商品id
self.viewer: list = ['viewer1'] # 在大麦网已填写的观影人
self.buy_nums: int = 1 # 购买影票数量, 需与观影人数量一致
self.ticket_price: int = 180 # 购买指定票价
```
![image](images/item_id.png)

Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
beautifulsoup4==4.9.3
requests==2.24.0
selenium==3.141.0
pyexecjs==1.5.1

0 comments on commit 176a0e6

Please sign in to comment.