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('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("请输入发送提醒的邮箱(例如: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()