├── .github
└── workflows
│ └── codeql.yml
├── O1CN01QtSzD62GdSE1msrJp_!!2251059038.jpg
├── README.md
├── config.json
└── damai_ticket.py
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ "main" ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ "main" ]
20 | schedule:
21 | - cron: '42 4 * * 1'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'python' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37 | # Use only 'java' to analyze code written in Java, Kotlin or both
38 | # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
39 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
40 |
41 | steps:
42 | - name: Checkout repository
43 | uses: actions/checkout@v3
44 |
45 | # Initializes the CodeQL tools for scanning.
46 | - name: Initialize CodeQL
47 | uses: github/codeql-action/init@v2
48 | with:
49 | languages: ${{ matrix.language }}
50 | # If you wish to specify custom queries, you can do so here or in a config file.
51 | # By default, queries listed here will override any specified in a config file.
52 | # Prefix the list here with "+" to use these queries and those in the config file.
53 |
54 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
55 | # queries: security-extended,security-and-quality
56 |
57 |
58 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
59 | # If this step fails, then you should remove it and run the build manually (see below)
60 | - name: Autobuild
61 | uses: github/codeql-action/autobuild@v2
62 |
63 | # ℹ️ Command-line programs to run using the OS shell.
64 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
65 |
66 | # If the Autobuild fails above, remove it and uncomment the following three lines.
67 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
68 |
69 | # - run: |
70 | # echo "Run, Build Application using script"
71 | # ./location_of_script_within_repo/buildscript.sh
72 |
73 | - name: Perform CodeQL Analysis
74 | uses: github/codeql-action/analyze@v2
75 | with:
76 | category: "/language:${{matrix.language}}"
77 |
--------------------------------------------------------------------------------
/O1CN01QtSzD62GdSE1msrJp_!!2251059038.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kemomi/daimai/d07238dcad3d3229f0ce59e682fc47bfa8e53327/O1CN01QtSzD62GdSE1msrJp_!!2251059038.jpg
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | ### 更新
3 |
4 |
5 |

6 |
7 |
8 |
9 | ### 提前准备
10 | * Python 3.6.3
11 | * Chromedriver.exe
12 | * Chrome 浏览器安装好后需将chromedriver.exe放置于Chrome浏览器目录下
13 | * `pip install selenium requests lxml`
14 |
15 | ### 参数设置
16 |
17 | 在`config.json`中输入相应配置信息,具体说明如下:
18 |
19 | * `date`: 日期选择
20 | * `sess`: 场次优先级列表
21 | * `price`: 票价优先级,如本例中共有三档票价,根据下表,则优先选择1,再选择3;也可以仅设置1个。
22 | * `real_name`: [1,2], 实名者序号,根据序号共选择两位实名者,根据序号,也可仅选择一位
23 | * #选择一位或是多位根据购票需知要求:若无需实名制信息则不需要填写;若一个订单仅需提供一位购票人信息则选择一位;若一张门票对应一位购票人信息则选择多位。
24 | * `driver_path`:浏览器驱动地址
25 | * `nick_name`: 用户在大麦网的昵称,用于验证登录是否成功
26 | * `ticket_num`: 购买票数
27 | * `damai_url`: https://www.damai.cn, 大麦网官网网址
28 | * `target_url`: https://detail.damai.cn/item.htm?id=607865020360 目标,例如:[周杰伦2023嘉年华世界巡回演唱会--海口站](https://detail.damai.cn/item.htm?id=607865020360)
29 |
30 |
31 | * 部分门票需要选择城市,只需选择相应城市后将其网址复制到config.json文件的`target_url`参数。
32 |
33 | * 根据需要选择的场次和票价分别修改config.json文件中的`sess`和`price`参数。
34 |
35 | * 查看购票须知中实名制一栏,若无需实名制则config.json文件中的`real_name`参数不需要填写(即为[]);若每笔订单只需一个证件号则`real_name`参数只需选择一个;若每张门票需要一个证件号,则real_name参数根据需购票数量进行相应添加。
36 |
37 |
38 | * 若是首次登录,根据终端输出的提示,依次点击登录、扫码登录,代码将自动保存cookie文件(cookie.pkl)
39 |
40 | * 使用前请将待抢票者的姓名、手机、地址设为默认。
41 |
42 | * 配置完成后执行`python damai_ticket.py`即可,注意观察控制台输出。
43 |
44 | * 本代码为保证抢票顺利,设置循环直到抢票成功才退出循环,若中途需要退出程序请直接终止程序。
45 |
46 |
47 |
48 |
49 |
50 |
51 | ### 热门演唱会信息
52 | * [周杰伦演唱会-天津](https://detail.damai.cn/item.htm?spm=a2oeg.search_category.searchtxt.ditem_0.f4294d15c5tGAZ&id=611160757855)
53 | * [周杰伦演唱会-海口](https://detail.damai.cn/item.htm?id=607865020360)
54 | * [周杰伦演唱会-呼和浩特](https://detail.damai.cn/item.htm?id=704967827554)
55 | * [周杰伦演唱会-太原](https://detail.damai.cn/item.htm?id=704762591363)
56 |
--------------------------------------------------------------------------------
/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "date": [2023/3/1],
3 | "sess": [1],
4 | "price": [2, 3, 1],
5 | "real_name": [1, 2],
6 | "nick_name": "麦子1Akli",
7 | "ticket_num": 1,
8 | "driver_path": "C:\Program Files (x86)\Microsoft\Edge\Application",
9 | "damai_url": "https://www.damai.cn/",
10 | "target_url": "https://search.damai.cn/search.htm?spm=a2oeg.home.category.ditem_0.591b23e1SM3Gnc&ctl=%E6%BC%94%E5%94%B1%E4%BC%9A&order=1&cty=%E6%9D%AD%E5%B7%9E"
11 | }
--------------------------------------------------------------------------------
/damai_ticket.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | from json import loads
3 | from time import sleep, time
4 | from pickle import dump, load
5 | from os.path import exists
6 | from selenium import webdriver
7 | from selenium.webdriver.common.by import By
8 | from selenium.webdriver.support.ui import WebDriverWait
9 | from selenium.webdriver.support import expected_conditions as EC
10 | from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
11 |
12 | class Concert(object):
13 | def __init__(self, date, session, price, real_name, nick_name, ticket_num, damai_url, target_url,driver_path):
14 | self.date = date # 日期序号
15 | self.session = session # 场次序号优先级
16 | self.price = price # 票价序号优先级
17 | self.real_name = real_name # 实名者序号
18 | self.status = 0 # 状态标记
19 | self.time_start = 0 # 开始时间
20 | self.time_end = 0 # 结束时间
21 | self.num = 0 # 尝试次数
22 | self.ticket_num = ticket_num # 购买票数
23 | self.nick_name = nick_name # 用户昵称
24 | self.damai_url = damai_url # 大麦网官网网址
25 | self.target_url = target_url # 目标购票网址
26 | self.driver_path = driver_path # 浏览器驱动地址
27 | self.driver = None
28 |
29 | def isClassPresent(self, item, name, ret=False):
30 | try:
31 | result = item.find_element_by_class_name(name)
32 | if ret:
33 | return result
34 | else:
35 | return True
36 | except:
37 | return False
38 |
39 | # 获取账号的cookie信息
40 | def get_cookie(self):
41 | self.driver.get(self.damai_url)
42 | print(u"###请点击登录###")
43 | self.driver.find_element_by_class_name('login-user').click()
44 | while self.driver.title.find('大麦网-全球演出赛事官方购票平台') != -1: # 等待网页加载完成
45 | sleep(1)
46 | print(u"###请扫码登录###")
47 | while self.driver.title == '大麦登录': # 等待扫码完成
48 | sleep(1)
49 | dump(self.driver.get_cookies(), open("cookies.pkl", "wb"))
50 | print(u"###Cookie保存成功###")
51 |
52 | def set_cookie(self):
53 | try:
54 | cookies = load(open("cookies.pkl", "rb")) # 载入cookie
55 | for cookie in cookies:
56 | cookie_dict = {
57 | 'domain':'.damai.cn', # 必须有,不然就是假登录
58 | 'name': cookie.get('name'),
59 | 'value': cookie.get('value'),
60 | "expires": "",
61 | 'path': '/',
62 | 'httpOnly': False,
63 | 'HostOnly': False,
64 | 'Secure': False}
65 | self.driver.add_cookie(cookie_dict)
66 | print(u'###载入Cookie###')
67 | except Exception as e:
68 | print(e)
69 |
70 | def login(self):
71 | print(u'###开始登录###')
72 | self.driver.get(self.target_url)
73 | WebDriverWait(self.driver, 10, 0.1).until(EC.title_contains('大麦网'))
74 | self.set_cookie()
75 |
76 | def enter_concert(self):
77 | print(u'###打开浏览器,进入大麦网###')
78 | if not exists('cookies.pkl'): # 如果不存在cookie.pkl,就获取一下
79 | self.driver = webdriver.Chrome(executable_path=self.driver_path)
80 | self.get_cookie()
81 | print(u'###成功获取Cookie,重启浏览器###')
82 | self.driver.quit()
83 |
84 | options = webdriver.ChromeOptions()
85 | # 禁止图片、js、css加载
86 | prefs = {"profile.managed_default_content_settings.images": 2,
87 | "profile.managed_default_content_settings.javascript": 1,
88 | 'permissions.default.stylesheet': 2}
89 | options.add_experimental_option("prefs", prefs)
90 |
91 | # 更换等待策略为不等待浏览器加载完全就进行下一步操作
92 | capa = DesiredCapabilities.CHROME
93 | capa["pageLoadStrategy"] = "none"
94 | self.driver = webdriver.Chrome(executable_path=self.driver_path, options=options, desired_capabilities=capa)
95 | # 登录到具体抢购页面
96 | self.login()
97 | self.driver.refresh()
98 | try:
99 | # 等待nickname出现
100 | locator = (By.XPATH, "/html/body/div[1]/div/div[3]/div[1]/a[2]/div")
101 | WebDriverWait(self.driver, 5, 0.3).until(EC.text_to_be_present_in_element(locator, self.nick_name))
102 | self.status = 1
103 | print(u"###登录成功###")
104 | self.time_start = time()
105 | except:
106 | self.status = 0
107 | self.driver.quit()
108 | raise Exception(u"***错误:登录失败,请删除cookie后重试***")
109 |
110 | # 实现购买函数
111 | def choose_ticket(self):
112 | print(u"###进入抢票界面###")
113 | while self.driver.title.find('确认订单') == -1: # 如果跳转到了确认界面就算这步成功了,否则继续执行此步
114 | self.num += 1 #尝试次数加1
115 |
116 | if con.driver.current_url.find("buy.damai.cn") != -1:
117 | break
118 |
119 | # 确认页面刷新成功
120 | try:
121 | box = WebDriverWait(self.driver, 1, 0.1).until(EC.presence_of_element_located((By.CLASS_NAME, 'perform__order__box')))
122 | except:
123 | raise Exception(u"***Error: 页面刷新出错***")
124 |
125 | try:
126 | realname_popup = box.find_elements_by_xpath("//div[@class='realname-popup']") # 寻找实名身份遮罩
127 | if len(realname_popup) != 0:
128 | known_button = realname_popup[0].find_elements_by_xpath("//div[@class='operate']//div[@class='button']")
129 | known_button[0].click()
130 | except:
131 | raise Exception(u"***Error: 实名制遮罩关闭失败***")
132 |
133 | try:
134 | buybutton = box.find_element_by_class_name('buybtn') # 寻找立即购买标签
135 | buybutton_text = buybutton.text
136 | except:
137 | raise Exception(u"***Error: buybutton 位置找不到***")
138 |
139 | if buybutton_text == "即将开抢" or buybutton_text == "即将开售":
140 | self.status = 2
141 | raise Exception(u"---尚未开售,刷新等待---")
142 |
143 | try:
144 | selects = box.find_elements_by_class_name('perform__order__select') # 日期、场次和票档进行定位
145 | date = None # 有的演出没有日期的选项
146 | for item in selects:
147 | if item.find_element_by_class_name('select_left').text == '日期':
148 | date = item
149 | # print('\t日期定位成功')
150 | elif item.find_element_by_class_name('select_left').text == '场次':
151 | session = item
152 | # print('\t场次定位成功')
153 | elif item.find_element_by_class_name('select_left').text == '票档':
154 | price = item
155 | # print('\t票档定位成功')
156 |
157 | if date is not None:
158 | date_list = date.find_elements_by_xpath("//div[@class='wh_content_item']//div[starts-with(@class,'wh_item_date')]") #选定日期
159 | # print('可选日期数量为:{}'.format(len(date_list)))
160 | for i in self.date:
161 | j = date_list[i-1]
162 | j.click()
163 | break
164 |
165 | session_list = session.find_elements_by_class_name('select_right_list_item')#选定场次
166 | # print('可选场次数量为:{}'.format(len(session_list)))
167 | for i in self.session: # 根据优先级选择一个可行场次
168 | j = session_list[i-1]
169 | k = self.isClassPresent(j, 'presell', True)
170 | if k: # 如果找到了带presell的类
171 | if k.text == '无票':
172 | continue
173 | elif k.text == '预售':
174 | j.click()
175 | break
176 | elif k.text == '惠':
177 | j.click()
178 | break
179 | else:
180 | j.click()# 选定好场次点击按钮确定
181 | break
182 |
183 | price_list = price.find_elements_by_class_name('select_right_list_item')#选定票档
184 | # print('可选票档数量为:{}'.format(len(price_list)))
185 | for i in self.price:
186 | j = price_list[i-1]
187 | k = self.isClassPresent(j, 'notticket')
188 | if k: # 存在notticket代表存在缺货登记,跳过
189 | continue
190 | else:
191 | j.click()#选定好票档点击确定
192 | break
193 | except:
194 | raise Exception(u"***Error: 选择日期or场次or票档不成功***")
195 |
196 | try:
197 | ticket_num_up = box.find_element_by_class_name('cafe-c-input-number-handler-up')
198 | except:
199 | if buybutton_text == "选座购买": # 选座购买没有增减票数键
200 | buybutton.click()
201 | self.status = 5
202 | print(u"###请自行选择位置和票价###")
203 | break
204 | elif buybutton_text == "提交缺货登记":
205 | raise Exception(u'###票已被抢完,持续捡漏中...或请关闭程序并手动提交缺货登记###')
206 | else:
207 | raise Exception(u"***Error: ticket_num_up 位置找不到***")
208 |
209 | if buybutton_text == "立即预订":
210 | for i in range(self.ticket_num-1): # 设置增加票数
211 | ticket_num_up.click()
212 | buybutton.click()
213 | self.status = 3
214 |
215 | elif buybutton_text == "立即购买":
216 | for i in range(self.ticket_num-1): # 设置增加票数
217 | ticket_num_up.click()
218 | buybutton.click()
219 | self.status = 4
220 |
221 | def check_order(self):
222 | if self.status in [3, 4, 5]:
223 | if self.real_name is not None:
224 | print(u"###等待--确认订单--页面出现,可自行刷新,若长期不跳转可选择-- CRTL+C --重新抢票###")
225 | try:
226 | tb = WebDriverWait(self.driver, 1, 0.1).until(EC.presence_of_element_located((By.XPATH, '/html/body/div[3]/div[2]/div')))
227 | except:
228 | raise Exception(u"***Error:实名信息选择框没有显示***")
229 |
230 | print(u'###开始确认订单###')
231 | print(u'###选择购票人信息,可手动帮助点击###')
232 | init_sleeptime = 0.0
233 | Labels = tb.find_elements_by_tag_name('label')
234 |
235 | # 防止点击过快导致没有选择多个人
236 | while True:
237 | init_sleeptime += 0.1
238 | true_num = 0
239 | for num_people in self.real_name:
240 | tag_input = Labels[num_people-1].find_element_by_tag_name('input')
241 | if tag_input.get_attribute('aria-checked') == 'false':
242 | sleep(init_sleeptime)
243 | tag_input.click()
244 | else:
245 | true_num += 1
246 | if true_num == len(self.real_name):
247 | break
248 | print("本次抢票时间:", time()-self.time_start)
249 | self.driver.find_element_by_xpath('/html/body/div[3]/div[2]/div/div[9]/button').click() # 同意以上协议并提交订单
250 |
251 | else:
252 | self.driver.find_element_by_xpath('/html/body/div[2]/div[2]/div/div[8]/button').click()
253 |
254 | # 判断title是不是支付宝
255 | print(u"###等待跳转到--付款界面--,可自行刷新,若长期不跳转可选择-- CRTL+C --重新抢票###")
256 | try:
257 | WebDriverWait(self.driver, 3600, 0.1).until(EC.title_contains('支付宝'))
258 | except:
259 | raise Exception(u'***Error: 长期跳转不到付款界面***')
260 |
261 | self.status = 6
262 | print(u'###成功提交订单,请手动支付###')
263 | self.time_end = time()
264 |
265 |
266 | if __name__ == '__main__':
267 | try:
268 | with open('./config.json', 'r', encoding='utf-8') as f:
269 | config = loads(f.read())
270 | # params: 场次优先级,票价优先级,实名者序号, 用户昵称, 购买票数, 官网网址, 目标网址, 浏览器驱动地址
271 | con = Concert(config['date'], config['sess'], config['price'], config['real_name'], config['nick_name'], config['ticket_num'], config['damai_url'], config['target_url'], config['driver_path'])
272 | con.enter_concert() #进入到具体抢购页面
273 | except Exception as e:
274 | print(e)
275 | exit(1)
276 | while True:
277 | try:
278 | con.choose_ticket()
279 | con.check_order()
280 | except Exception as e:
281 | con.driver.get(con.target_url)
282 | print(e)
283 | continue
284 |
285 | if con.status == 6:
286 | print(u"###经过%d轮奋斗,共耗时%.1f秒,抢票成功!请确认订单信息###" % (con.num, round(con.time_end-con.time_start, 3)))
287 | break
288 |
--------------------------------------------------------------------------------