Python12306抢票脚本
本脚本使用一个类来实现所有代码,大体上分为以下几个模块及其步骤:
- 初始化对象属性(在抢票前进行的属性初始化,包括初始化浏览器模拟对象,个人信息等)。
- 建立模拟浏览器,模拟浏览器进行cookie等存储。
- 验证模块:
- 获取验证图片到本地
- 将8个图片坐标位置改装成易于输入的1—8的位置编号,输入对应的位置号
- 发送请求进行后台校验
- 登录模块:
- 输入账号密码,请求服务器
- 获取apptk授权码
- 授权通过,成功获取用户信息,将授权信息存储到cookie
- 获取站点模块:
- 获取所有站点名称
- 获取所有站点码
- 获取余票信息模块:
- 输入起始站点与乘车时间,请求服务器,查询余票信息
- 将余票信息进行格式化输出
- 选择相应车次
- 订单模块:
- 注入起始点、日期,车次码信息,提交请求,返回状态信息
- 获取该车次的详细信息,选择车票类型
- 获取所有已添加乘客
- 选择乘车乘客
- 检查订单信息
- 确认订单信息,占座成功,下单完成
- 发送邮件,短信,提醒支付
以下贴出所有源码,仅供参考,其中发送邮件与发送短信模块所需的参数须自行到相关网站获取。
1 # -*- coding:utf-8 -*-
2 from urllib import request
3 from json import loads
4 from prettytable import PrettyTable
5 from colorama import init, Fore, Back, Style
6 from email.mime.text import MIMEText
7 from qcloudsms_py import SmsSingleSender
8 from qcloudsms_py.httpclient import HTTPError
9 import urllib.parse as parse
10 import http.cookiejar as cookiejar
11 import re
12 import time
13 import smtplib
14 # 200 32
15 # 150 23
16 # 70 10
17 init(autoreset=False)
18 class Colored(object):
19 # 前景色:红色 背景色:默认
20 def red(self, s):
21 return Fore.LIGHTRED_EX + s + Fore.RESET
22 # 前景色:绿色 背景色:默认
23 def green(self, s):
24 return Fore.LIGHTGREEN_EX + s + Fore.RESET
25 def yellow(self, s):
26 return Fore.LIGHTYELLOW_EX + s + Fore.RESET
27 def white(self, s):
28 return Fore.LIGHTWHITE_EX + s + Fore.RESET
29 def blue(self, s):
30 return Fore.LIGHTBLUE_EX + s + Fore.RESET
31
32 class train_ticket_purchase():
33 """
34 初始化对象属性
35 """
36 def __init__(self):
37 self.headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'}
38 self.opener = self.__get_opener()
39 self.username = ""
40 self.phone_number = "13781206061"#用于占座成功接收短信
41 self.receive_email = "wsyjlly@foxmail.com"#用于占座成功接收邮件
42 self.seatType = "1" #1硬座
43 self.seat_types_code = ["M","0","1","N","2","3","4","F","6","9"]
44 self.ticketType = "1" #成人票
45 self.query_seats_count = 1 #查票次数
46 self.passengers_name = "" #乘车人字符串
47 self.seat_list = ["yz_num","wz_num","rz_num","yw_num","rw_num", "dw_num", "gr_num","ze_num","zy_num", "swz_num"]
48 self.ticketTypes = {"1":"成人票","2":"儿童票","3":"学生票","4":"残军票"}
49 self.seatTypes = {
50 "M":"一等座",
51 "0":"二等座",
52 "1":"硬座",
53 "N":"无座",
54 "2":"软座",
55 "3":"硬卧",
56 "4":"软卧",
57 "F":"动卧",
58 "6":"高等软卧",
59 "9":"商务座"
60 }
61 self.seat_dict = {
62 "yz_num":"硬座",
63 "wz_num":"无座",
64 "rz_num":"软座",
65 "yw_num":"硬卧",
66 "rw_num":"软卧",
67 "dw_num":"动卧",
68 "gr_num":"高级软卧",
69 "ze_num":"二等座",
70 "zy_num":"一等座",
71 "swz_num":"商务特等座"
72 }
73
74
75
76
77
78
79
80
81
82
83 """
84 建立模拟浏览器,模拟浏览器进行cookie存储
85 """
86 def __get_opener(self):
87 c = cookiejar.LWPCookieJar()
88 cookie = request.HTTPCookieProcessor(c)
89 opener = request.build_opener(cookie)
90 request.install_opener(opener)
91 return opener
92
93
94
95
96
97
98
99
100
101
102
103 """
104 验证模块:
105 1、获取验证图片到本地
106 2、将8个图片坐标位置改装成易于输入的1—8的位置编号,输入对应的位置号
107 3、发送请求进行后台校验
108 """
109 # 获取验证图片到本地
110 def get_image(self):
111 req_catch_image = request.Request('/file/imgs/upload/202301/30/5l2otemnlol.html', 'wb')as f:
115 f.write(code_file)
116 # 图片校验
117 def verify(self):
118 answer = {
119 "1": "40,40",
120 "2": "110,40",
121 "3": "180,40",
122 "4": "260,40",
123 "5": "40,120",
124 "6": "110,120",
125 "7": "180,120",
126 "8": "260,120",
127 }
128 print("+----------+----------+----------+----------+")
129 print("| 1 | 2 | 3 | 4 |")
130 print("|----------|----------|----------|----------|")
131 print("| 5 | 6 | 7 | 8 |")
132 print("+----------+----------+----------+----------+")
133 input_code = input("请在1—8中选择输入验证图片编号,以半角','隔开。(例如:1,3,5):")
134 answer_code = ""
135 try:
136 for i in input_code.split(","):
137 answer_code += ("," + answer[i]) if (i is not input_code[0]) else answer[i]
138 except:
139 print("输入有误,请重新输入!")
140 self.verify()
141 # 进行图片验证码验证
142 req_check = request.Request('https://kyfw.12306.cn/passport/captcha/captcha-check')
143 req_check.headers = self.headers
144 data = {
145 'answer': answer_code,
146 'login_site': 'E',
147 'rand': 'sjrand'
148 }
149 data = parse.urlencode(data).encode()
150 # 返回验证结果
151 check_result = self.opener.open(req_check, data=data).read().decode() # 读取出来是byts格式,转换为‘utf-8(默认)
152 return loads(check_result)
153 # 验证系统
154 def sys_verify(self):
155 self.get_image()
156 verify_result = self.verify()
157 while verify_result['result_code'] is not '4':
158 print('验证失败,已重新下载图片,请重新验证!')
159 self.get_image()
160 verify_result = self.verify()
161 print("验证通过!")
162 return
163
164
165
166
167
168
169
170
171
172
173 """
174 登录模块:
175 1、输入账号密码,请求服务器
176 2、获取apptk授权码
177 3、授权通过,成功获取用户信息,将授权信息存储到cookie
178 """
179 def login(self):
180 req_login = request.Request('https://kyfw.12306.cn/passport/web/login')
181 req_login.headers = self.headers
182 name = input("请输入12306帐号:")
183 pwd = input("请输入密码:")
184 data = {
185 'username': name,
186 'password': pwd,
187 'appid': 'otn'
188 }
189 data = parse.urlencode(data).encode()
190 # 返回登录结果
191 login_result = self.opener.open(req_login, data=data).read().decode()
192 return loads(login_result)
193 def get_tk(self):
194 req = request.Request('https://kyfw.12306.cn/passport/web/auth/uamtk')
195 req.headers = self.headers
196 data = {
197 "appid": "otn"
198 }
199 data = parse.urlencode(data).encode()
200 # 返回登录结果
201 result = self.opener.open(req, data=data).read().decode()
202 return loads(result)
203 def auth(self,newapptk):
204 req = request.Request('https://kyfw.12306.cn/otn/uamauthclient')
205 req.headers = self.headers
206 data = {
207 "tk": newapptk
208 }
209 data = parse.urlencode(data).encode()
210 # 返回登录结果
211 result = self.opener.open(req, data=data).read().decode()
212 return loads(result)
213 # 登陆系统
214 def sys_login(self):
215 self.login()
216 result = self.get_tk()
217 try:
218 result = self.auth(result['newapptk'])
219 except:
220 print("登录失败,账号或密码错误!")
221 self.sys_verify()
222 self.sys_login()
223 self.username = result["username"]
224 print("欢迎用户",result["username"], "您已登录成功!") if result["result_code"]==0 else print(result["result_message"])
225 return
226
227
228
229
230
231
232
233
234
235
236 """
237 获取站点模块:
238 获取所有站点名称与站点码
239 """
240 def __get_city_result(self):
241 req_city_code = request.Request(
242 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9093')
243 req_city_code.headers = self.headers
244 result = self.opener.open(req_city_code).read().decode()
245 return result
246 def get_city_code(self,name):
247 result = self.__get_city_result()
248 start = result.index(name)+len(name)
249 result = result[start+1:start+4]
250 # print(result)
251 return result
252 def get_station_names(self):
253 result = self.__get_city_result()
254 stations = re.findall(r'([\u4e00-\u9fa5]+)\|([A-Z]+)', result)
255 station_codes = dict(stations)
256 station_names = dict(zip(station_codes.values(), station_codes.keys()))
257 return station_names
258
259
260
261
262
263
264
265
266 """
267 获取余票信息模块:
268 1、输入起始站点与乘车时间,请求服务器,查询余票信息
269 2、将余票信息进行格式化输出
270 3、选择相应车次
271 """
272 def get_tickets(self,from_station, to_station, train_date):
273 url = 'https://kyfw.12306.cn/otn/leftTicket/queryX?'
274 data = {
275 "leftTicketDTO.train_date": train_date,
276 "leftTicketDTO.from_station": from_station,
277 "leftTicketDTO.to_station": to_station,
278 "purpose_codes": "ADULT"
279 }
280 req = request.Request(url + parse.urlencode(data))
281 req.headers = self.headers
282 result = self.opener.open(req).read().decode()
283 return loads(result)
284 def get_ticket_format(self,from_station_name,from_station,to_station_name,to_station,train_date):
285 print('为您查询到从', from_station_name, '到', to_station_name, '的余票信息如下:')
286 result = self.get_tickets(from_station, to_station, train_date)
287 result_list = result['data']['result']
288
289 station_names = self.get_station_names()
290 table = PrettyTable(
291 ["车次", "出发/到达车站", "出发/到达时间", "历时", "商务座", "一等座", "二等座", "高级软卧", "软卧", "动卧", "硬卧", "软座", "硬座", "无座", "其他",
292 "备注"])
293 for item in result_list:
294 name = [
295 "station_train_code",
296 "from_station_name",
297 'start_time',
298 "lishi",
299 "swz_num",
300 "zy_num",
301 "ze_num",
302 "gr_num",
303 "rw_num",
304 "dw_num",
305 "yw_num",
306 "rz_num",
307 "yz_num",
308 "wz_num",
309 "qt_num",
310 "note_num"
311 ]
312 data = {
313 "station_train_code": '',
314 "from_station_name": '',
315 "to_station_name": '',
316 'start_time': '',
317 'end': '',
318 "lishi": '',
319 "swz_num": '',
320 "zy_num": '',
321 "ze_num": '',
322 "dw_num": '',
323 "gr_num": '',
324 "rw_num": '',
325 "yw_num": '',
326 "rz_num": '',
327 "yz_num": '',
328 "wz_num": '',
329 "qt_num": '',
330 "note_num": ''
331 }
332 item = item.split('|') # 用"|"分割字符串
333 data['station_train_code'] = item[3] # 车次在3号位置
334 data['from_station_name'] = item[6] # 始发站信息在6号位置
335 data['to_station_name'] = item[7] # 终点站信息在7号位置
336 data['start_time'] = item[8] # 出发时间信息在8号位置
337 data['arrive_time'] = item[9] # 抵达时间在9号位置
338 data['lishi'] = item[10] # 经历时间在10号位置
339 data['swz_num'] = item[32] or item[25] # 特别注意:商务座在32或25位置
340 data['zy_num'] = item[31] # 一等座信息在31号位置
341 data['ze_num'] = item[30] # 二等座信息在30号位置
342 data['gr_num'] = item[21] # 高级软卧信息在31号位置
343 data['rw_num'] = item[23] # 软卧信息在23号位置
344 data['dw_num'] = item[27] # 动卧信息在27号位置
345 data['yw_num'] = item[28] # 硬卧信息在28号位置
346 data['rz_num'] = item[24] # 软座信息在24号位置
347 data['yz_num'] = item[29] # 硬座信息在29号位置
348 data['wz_num'] = item[26] # 无座信息在26号位置
349 data['qt_num'] = item[22] # 其他信息在22号位置
350 data['note_num'] = item[1] # 备注在1号位置
351
352 color = Colored() # 创建Colored对象
353 data["note_num"] = color.white(item[1])
354 # 如果没有信息用'-'代替
355 for pos in name:
356 if data[pos] == '':
357 data[pos] = '-'
358 tickets = []
359 cont = []
360 cont.append(data)
361 for x in cont:
362 tmp = []
363 for y in name:
364 if y == "from_station_name":
365 s = color.green(station_names[data['from_station_name']]) + '\n' + color.red(
366 station_names[data["to_station_name"]])
367 tmp.append(s)
368 elif y == "start_time":
369 s = color.green(data['start_time']) + '\n' + color.red(data["arrive_time"])
370 tmp.append(s)
371 elif y == "station_train_code":
372 s = color.blue(data['station_train_code'])
373 tmp.append(s)
374 else:
375 tmp.append(data[y])
376 tickets.append(tmp)
377 for ticket in tickets:
378 table.add_row(ticket)
379 print(table)
380 def get_secret_str(self,from_station, to_station, train_date):
381 secret_str = {}
382 result = self.get_tickets(from_station, to_station, train_date)
383 result = result['data']['result']
384 for item in result:
385 msg = item.split("|")
386 secret_str[msg[3]] = parse.unquote(msg[0])
387 # print(secret_str)
388 return secret_str
389 def get_seats(self,station_train_code,from_station, to_station, train_date):
390 seats = {}
391 result = self.get_tickets(from_station, to_station, train_date)
392 result = result['data']['result']
393 for item in result:
394 item = item.split("|")
395 if item[3] == station_train_code :
396 seats['swz_num'] = item[32] or item[25] # 特别注意:商务座在32或25位置
397 seats['zy_num'] = item[31] # 一等座信息在31号位置
398 seats['ze_num'] = item[30] # 二等座信息在30号位置
399 seats['gr_num'] = item[21] # 高级软卧信息在31号位置
400 seats['rw_num'] = item[23] # 软卧信息在23号位置
401 seats['dw_num'] = item[27] # 动卧信息在27号位置
402 seats['yw_num'] = item[28] # 硬卧信息在28号位置
403 seats['rz_num'] = item[24] # 软座信息在24号位置
404 seats['yz_num'] = item[29] # 硬座信息在29号位置
405 seats['wz_num'] = item[26] # 无座信息在26号位置
406 return seats
407 def select_order_details(self):
408 print("座位码对照表:")
409 print("-----------------------")
410 print("| 序号 | 座位类型 |")
411 print("| M | 一等座 |")
412 print("| 0 | 二等座 |")
413 print("| 1 | 硬座 |")
414 print("| N | 无座 |")
415 print("| 2 | 软座 |")
416 print("| 3 | 硬卧 |")
417 print("| 4 | 软卧 |")
418 print("| F | 动卧 |")
419 print("| 6 | 高级软卧 |")
420 print("| 9 | 商务座 |")
421 print("-----------------------")
422 seatType = input("请选择车座类型,enter键默认硬座(例如:1):")
423 if seatType == '':
424 self.seatType = "1"
425 elif seatType in self.seat_types_code:
426 self.seatType = seatType
427 else :
428 raise Exception("没有对应的车座类型!")
429
430 print("车票类型对照表:")
431 print("-----------------------")
432 print("| 序号 | 座位类型 |")
433 print("| 1 | 成人票 |")
434 print("| 2 | 儿童票 |")
435 print("| 3 | 学生票 |")
436 print("| 4 | 残军票 |")
437 print("-----------------------")
438
439 ticketType = input("请选择车票类型,enter键默认成人票(例如:1):")
440 self.ticketType = ticketType if seatType != '' else "1"
441
442 passengers_name = input("请输入乘车人姓名,如有多人,请以英文','隔开(例如:晏沈威,晏文艳):")
443 self.passengers_name = passengers_name if passengers_name!='' else '晏沈威'
444
445 email = input("请输入发送提醒的邮箱(例如:wsyjlly@foxmai.com):")
446 self.receive_email = email if email!='' else "wsyjlly@foxmail.com"
447
448 phone_number = input("请输入发送提醒的手机号(例如:13781206061):")
449 self.phone_number = phone_number if phone_number!='' else "13781206061"
450 def query_ticket(self,seats,seat_msg):
451 if ((seats[seat_msg] == "") | (seats[seat_msg] == "无")):
452 print("无",self.seat_dict[seat_msg],"座位!")
453 return False
454 else:
455 print("查询到",seats[seat_msg], self.seat_dict[seat_msg], "座位!")
456 return True
457 def sys_seek_tickets(self):
458 while True:
459 from_station_name = "郑州"
460 from_station_name = input("出发站点(例:郑州):")
461
462 to_station_name = "开封"
463 to_station_name = input("到达站点(例:开封):")
464
465 train_date = "2019-02-28"
466 train_date = (input("乘车日期(例:2019-02-25):"))
467
468 print("正在为您查询余票信息,请稍等...")
469 from_station = self.get_city_code(from_station_name)
470 to_station = self.get_city_code(to_station_name)
471
472 self.get_ticket_format(from_station_name,from_station,to_station_name,to_station,train_date)
473 if input("输入'1'可继续查询,输入enter键选择车次!")!="1": break
474
475 station_train_code = "K464"
476 station_train_code = input("乘车车次(例:K464):")
477
478 # 选择座位类型与车票类型与乘车人姓名
479 self.select_order_details()
480
481 while True:
482 seats = self.get_seats(station_train_code, from_station, to_station, train_date)
483 print('第{}次查票!'.format(self.query_seats_count),seats)
484 if(self.seatType=="1"):
485 if self.query_ticket(seats,"yz_num")==True :break
486 elif(self.seatType=="N"):
487 if self.query_ticket(seats,"wz_num")==True :break
488 elif(self.seatType=="2"):
489 if self.query_ticket(seats,"rz_num")==True :break
490 elif(self.seatType=="3"):
491 if self.query_ticket(seats,"yw_num")==True :break
492 elif(self.seatType=="4"):
493 if self.query_ticket(seats,"rw_num")==True :break
494 elif(self.seatType=="6"):
495 if self.query_ticket(seats,"gr_num")==True :break
496 elif(self.seatType=="0"):
497 if self.query_ticket(seats,"ze_num")==True :break
498 elif(self.seatType=="M"):
499 if self.query_ticket(seats,"zy_num")==True :break
500 elif(self.seatType=="F"):
501 if self.query_ticket(seats,"dw_num")==True :break
502 elif(self.seatType=="9"):
503 if self.query_ticket(seats,"swz_num")==True :break
504 else:
505 raise Exception("没有相应车次!")
506 break
507 self.query_seats_count+=1
508 time.sleep(2)
509
510 # 获取相应车次的secret_str
511 secret_str = self.get_secret_str(from_station, to_station, train_date)[station_train_code]
512 # print(secret_str)
513 result = {}
514 result["from_station"]=from_station
515 result["to_station"]=to_station
516 result["train_date"]=train_date
517 result["secret_str"]=secret_str
518 return result
519
520
521
522
523
524
525
526
527 """
528 订单模块:
529 1、注入起始点、日期,车次码信息,提交请求,返回状态信息
530 2、获取该车次的详细信息,选择车票类型
531 3、获取所有已添加乘客
532 4、选择乘车乘客
533 5、检查订单信息
534 6、确认订单信息,占座成功,下单完成
535 7、发送邮件,短信,提醒支付
536 """
537 # {'validateMessagesShowId': '_validatorMessage', 'status': True, 'httpstatus': 200, 'data': 'N', 'messages': [], 'validateMessages': {}}
538 def get_train_number(self,tickets):
539 secret_str = parse.unquote(tickets["secret_str"])
540 from_station = tickets["from_station"]
541 to_station = tickets["to_station"]
542 train_date = tickets["train_date"]
543 req = request.Request('https://kyfw.12306.cn/otn/leftTicket/submitOrderRequest')
544 req.headers = self.headers
545 data = {
546 "secretStr": secret_str,
547 "train_date": train_date,
548 "back_train_date": "",
549 "tour_flag": "dc",
550 "purpose_codes": "ADULT",
551 "query_from_station_name": from_station,
552 "query_to_station_name": to_station,
553 "undefined": "",
554 }
555 data = parse.urlencode(data).encode()
556 result = self.opener.open(req, data=data).read().decode()
557 return loads(result)
558 # 获取相应车次的信息
559 def get_train_number_msg(self):
560 req = request.Request('https://kyfw.12306.cn/otn/confirmPassenger/initDc')
561 req.headers = self.headers
562 data = {
563 "_json_att": ""
564 }
565 data = parse.urlencode(data).encode()
566 # 返回登录结果
567 result = self.opener.open(req, data=data).read().decode()
568 try:
569 ticketInfoForPassengerForm = re.findall("var ticketInfoForPassengerForm=(.*?);", result)[0].replace("'", '"')
570 globalRepeatSubmitToken = re.findall("globalRepeatSubmitToken = '(.*?)'", result)[0]
571 key_check_isChange = re.findall("'key_check_isChange':'(.*?)'", result)[0]
572 except:
573 raise Exception("没有获取到车次信息!")
574 ticketInfoForPassengerForm = loads(ticketInfoForPassengerForm)
575 leftDetails = ticketInfoForPassengerForm["leftDetails"]
576 leftTicketStr = ticketInfoForPassengerForm["leftTicketStr"]
577 purpose_codes = ticketInfoForPassengerForm["queryLeftTicketRequestDTO"]["purpose_codes"]
578 train_location = ticketInfoForPassengerForm["train_location"]
579 print("该车次剩余车票详情如下:")
580 for item in leftDetails:
581 print("\t",item)
582 msg_order_finally_submit = {}
583 msg_order_finally_submit["purpose_codes"] = purpose_codes
584 msg_order_finally_submit["key_check_isChange"] = key_check_isChange
585 msg_order_finally_submit["leftTicketStr"] = leftTicketStr
586 msg_order_finally_submit["train_location"] = train_location
587 msg_order_finally_submit["token"] = globalRepeatSubmitToken
588
589 return msg_order_finally_submit
590 # 获取所有已添加乘客
591 def get_passengers(self,token):
592 req = request.Request('https://kyfw.12306.cn/otn/confirmPassenger/getPassengerDTOs')
593 req.headers = self.headers
594 data = {
595 "_json_att": "",
596 "REPEAT_SUBMIT_TOKEN": token
597 }
598 data = parse.urlencode(data).encode()
599 # 返回登录结果
600 result = self.opener.open(req, data=data).read().decode()
601 result = loads(result)
602 normal_passengers = result["data"]["normal_passengers"]
603 result = {}
604 # print("已添加的乘车人如下:")
605 for passenger in normal_passengers:
606 result[passenger["passenger_name"]] = passenger
607 # if passenger != normal_passengers[len(normal_passengers) - 1]:
608 # print(passenger["passenger_name"] + ",", end='')
609 # else:
610 # print(passenger["passenger_name"])
611 return result
612 # 选择乘车人
613 def select_passenger(self,passengers):
614 ps = self.passengers_name
615 oldPassengerStr = ''
616 passengerTicketStr = ''
617 seatType = 1 if self.seatType =="N" else self.seatType
618 try:
619 ps = ps.split(",")
620 for p in ps:
621 oldPassengerStr += passengers[p]["passenger_name"] + "," + \
622 passengers[p]["passenger_id_type_code"] + "," + \
623 passengers[p]["passenger_id_no"] + "," + \
624 passengers[p]["passenger_type"] + "_"
625 # seatType 座位类型:硬座1软座2硬卧3软卧4
626 # passenger_flag 乘客标记:0
627 # ticketType 车票类型: 成人票1儿童票2学生票3残军票4
628 # passenger_name 乘客姓名
629 # passenger_id_type_code 证件类型 中国居民身份证1
630 # passenger_id_no 身份证号
631 # mobile_no 手机号
634 ticketStr = "{},{},{},{},{},{},{},N".format(seatType,
635 passengers[p]["passenger_flag"],
636 self.ticketType,
637 passengers[p]["passenger_name"],
638 passengers[p]["passenger_id_type_code"],
639 passengers[p]["passenger_id_no"],
640 passengers[p]["mobile_no"])
641 passengerTicketStr += ticketStr + '_' if p != ps[len(ps) - 1] else ticketStr
642 except:
643 print("输入有误!")
644 result = {}
645 result["oldPassengerStr"] = oldPassengerStr
646 result["passengerTicketStr"] = passengerTicketStr
647 return result
648 # 检查订单信息
649 def order_submit(self,msg_passenger, token):
650 req = request.Request('https://kyfw.12306.cn/otn/confirmPassenger/checkOrderInfo')
651 req.headers = self.headers
652 data = {
653 "cancel_flag": "2",
654 "bed_level_order_num": "000000000000000000000000000000",
655 "passengerTicketStr": msg_passenger["passengerTicketStr"],
656 "oldPassengerStr": msg_passenger["oldPassengerStr"],
657 "tour_flag": "dc",
658 "randCode": "",
659 "whatsSelect": "1",
660 "_json_att": "",
661 "REPEAT_SUBMIT_TOKEN": token
662 }
663 data = parse.urlencode(data).encode()
664 # 返回登录结果
665 result = self.opener.open(req, data=data).read().decode()
666 return loads(result)
667 # 确认订单
668 def order_ensure(self,msg_passenger,train_number_msg):
669 purpose_codes = train_number_msg["purpose_codes"]
670 key_check_isChange = train_number_msg["key_check_isChange"]
671 leftTicketStr = train_number_msg["leftTicketStr"]
672 train_location = train_number_msg["train_location"]
673 token = train_number_msg["token"]
674 req = request.Request('https://kyfw.12306.cn/otn/confirmPassenger/confirmSingleForQueue')
675 req.headers = self.headers
676 data = {
677 "passengerTicketStr": msg_passenger["passengerTicketStr"],
678 "oldPassengerStr": msg_passenger["oldPassengerStr"],
679 "randCode": "",
680 "purpose_codes": purpose_codes,
681 "key_check_isChange": key_check_isChange,
682 "leftTicketStr": leftTicketStr,
683 "train_location": train_location,
684 "choose_seats": "",
685 "seatDetailType": "000",
686 "whatsSelect": "1",
687 "roomType": "00",
688 "dwAll": "N",
689 "_json_att": "",
690 "REPEAT_SUBMIT_TOKEN": token
691 }
692 data = parse.urlencode(data).encode()
693 # 返回登录结果
694 result = self.opener.open(req, data=data).read().decode()
695 return loads(result)
696 # 发送email
697 def send_email(self):
698 # 第三方SMTP服务
699 mail_host = "smtp.qq.com"
700 mail_user = "*******@foxmail.com"
701 mail_pass = "****************"
702
703 sender = "wsyjlly@foxmail.com"
704 receiver = self.receive_email
705
706 message = MIMEText("席位已锁定,快去支付!")
707 message["From"] = sender
708 message["To"] = receiver
709 message["Subject"] = "Python 12306 抢票!"
710 try:
711 server = smtplib.SMTP()
712 server.connect(mail_host)
713 server.login(mail_user, mail_pass)
714 server.sendmail(sender, receiver, message.as_string())
715 server.close()
716 print("邮件发送成功,已提醒用户",receiver,"付款!")
717 except Exception as e:
718 print("邮件发送失败!", e)
719 # 发送短信
720 def send_short_message(self):
721 name = self.username
722 phone_number = self.phone_number
723 seat_type = self.seatTypes[self.seatType]
724 ticketType = self.ticketTypes[self.ticketType]
725 appid = 1400****** # SDK AppID是1400开头
726 appkey = "********************************"
727 phone_numbers = [phone_number]
728 # phone_numbers = ["13781206061", "18337735150", "15660039893"]
729 template_id = ******
730 sms_sign = "简单点网"
731
732 ssender = SmsSingleSender(appid, appkey)
733 params = [name,ticketType,seat_type]
734 try:
735 result = ssender.send_with_param(86, phone_numbers[0], template_id, params, sign=sms_sign, extend="",ext="")
736 except HTTPError as e:
737 print(e)
738 except Exception as e:
739 print(e)
740 # print(result)
741 if result["errmsg"] == "OK":
742 print("短信发送成功,已提醒用户", name, "付款!")
743 def sys_order(self,tickets):
744 # 1、注入起始点、日期,车次码信息,提交请求,返回状态信息
745 result = self.get_train_number(tickets)
746 if result["status"]==True :print("查询车次信息成功!")
747 # 2、获取该车次的详细信息
748 train_number_msg = self.get_train_number_msg()
749 # 3、获取乘客信息
750 passengers = self.get_passengers(train_number_msg["token"])
751 # 4、选择乘客
752 msg_passenger = self.select_passenger(passengers)
753 # print(msg_passenger)
754 # 5、下单
755 result = self.order_submit(msg_passenger, train_number_msg["token"])
756 if result["status"] == True :print("检查订单信息正确,即将确认订单!")
757
758 print(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())))
759 # 6、确认订单
760 result = self.order_ensure(msg_passenger, train_number_msg)
761 if result["status"] == True :
762 print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())), ":下单成功,已占座,请尽快付款!")
763 self.send_email()
764 self.send_short_message()
765 # print(result)
766 input("按任意键继续!")
767
768
769
770
771
772
773
774
775
776
777
778 def run(self):
779 # 验证码验证
780 self.sys_verify()
781 # 登录
782 self.sys_login()
783 # 查余票
784 tickets = self.sys_seek_tickets()
785 # 下订单
786 self.sys_order(tickets)
787
788
789
790
791
792
793
794
795
796
797
798
799 if __name__ == '__main__':
800 while True:
801 train_ticket_purchase().run()