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 = "[email protected]"#用于占座成功接收邮件
 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('https://kyfw.12306.cn/passport/captcha/captcha-image')
112         req_catch_image.headers = self.headers
113         code_file = self.opener.open(req_catch_image).read()  # 此时为浏览器的open而不再是request.urlopen,下同
114         with open('/code.jpg', '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("请输入发送提醒的邮箱(例如:[email protected]):")
446         self.receive_email = email if email!='' else "[email protected]"
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 手机号
632                 # 3, 0, 2, 晏文艳, 1, 412728199608184527,, N_1, 0, 4, 晏沈威, 1, 412728199708124556, 13781206061, N_4, 0, 1, 晏建立, 1, 412728196802124575,, N
633                 # 1, 0, 1, 晏沈威, 1, 412728199708124556, 13781206061, N_1, 0, 1, 晏建立, 1, 412728196802124575,, N_1, 0, 1, 晏文艳, 1, 412728199608184527,, N
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 = "[email protected]"
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()
03-07 22:27