From b88d541f93c802b2d793ce743562f02eaee818ed Mon Sep 17 00:00:00 2001 From: wenxianping <931128603@qq.com> Date: Mon, 11 Jun 2018 23:03:23 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81=E5=A2=9E=E5=8A=A0=E5=BF=AB=E9=80=9F?= =?UTF-8?q?=E8=AE=A2=E7=A5=A8=E6=B5=81=E7=A8=8B=E6=8E=A5=E5=8F=A3=202?= =?UTF-8?q?=E3=80=81=E5=B0=9D=E8=AF=95=E8=A7=A3=E5=86=B3=E6=85=A2=E6=8E=92?= =?UTF-8?q?=E9=98=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 0 .idea/dictionaries/wenxianping.xml | 0 README.md | 0 __init__.py | 0 agency/__init__.py | 0 agency/agency_tools.py | 0 agency/cdn_utils.py | 0 cdn_list | 0 config/TicketEnmu.py | 35 +++ config/__init__.py | 0 config/configCommon.py | 0 config/emailConf.py | 0 config/logger.py | 0 config/ticketConf.py | 0 config/ticket_config.yaml | 8 +- config/urlConf.py | 88 +++++--- damatuCode/__init__.py | 0 damatuCode/damatuWeb.py | 0 damatuCode/ruokuai.py | 0 init/SelectTicketInfoFast.py | 293 ++++++++++++++++++++++++++ init/__init__.py | 0 init/login.py | 12 +- init/select_ticket_info.py | 10 +- inter/AutoSubmitOrderRequest.py | 108 ++++++++++ inter/ConfirmSingleForQueueAsys.py | 78 +++++++ inter/GetPassengerDTOs.py | 92 ++++++++ inter/GetQueueCountAsync.py | 102 +++++++++ inter/LiftTicketInit.py | 17 ++ inter/Query.py | 119 +++++++++++ inter/QueryOrderWaitTime.py | 123 +++++++++++ inter/__init__.py | 0 myException/PassengerUserException.py | 0 myException/UserPasswordException.py | 0 myException/__init__.py | 0 myException/balanceException.py | 0 myException/ticketConfigException.py | 0 myException/ticketIsExitsException.py | 0 myException/ticketNumOutException.py | 0 myUrllib/__init__.py | 0 myUrllib/httpUtils.py | 98 ++++++--- myUrllib/myurllib2.py | 0 requirements.txt | 0 run.py | 5 +- station_name.txt | 0 tkcode | Bin 0 -> 12429 bytes tmp/__init__.py | 0 tmp/log/__init__.py | 0 uml/uml.png | Bin 48 files changed, 1116 insertions(+), 72 deletions(-) mode change 100644 => 100755 .gitignore mode change 100644 => 100755 .idea/dictionaries/wenxianping.xml mode change 100644 => 100755 README.md mode change 100644 => 100755 __init__.py mode change 100644 => 100755 agency/__init__.py mode change 100644 => 100755 agency/agency_tools.py mode change 100644 => 100755 agency/cdn_utils.py mode change 100644 => 100755 cdn_list create mode 100644 config/TicketEnmu.py mode change 100644 => 100755 config/__init__.py mode change 100644 => 100755 config/configCommon.py mode change 100644 => 100755 config/emailConf.py mode change 100644 => 100755 config/logger.py mode change 100644 => 100755 config/ticketConf.py mode change 100644 => 100755 config/ticket_config.yaml mode change 100644 => 100755 config/urlConf.py mode change 100644 => 100755 damatuCode/__init__.py mode change 100644 => 100755 damatuCode/damatuWeb.py mode change 100644 => 100755 damatuCode/ruokuai.py create mode 100755 init/SelectTicketInfoFast.py mode change 100644 => 100755 init/__init__.py mode change 100644 => 100755 init/login.py mode change 100644 => 100755 init/select_ticket_info.py create mode 100644 inter/AutoSubmitOrderRequest.py create mode 100644 inter/ConfirmSingleForQueueAsys.py create mode 100644 inter/GetPassengerDTOs.py create mode 100644 inter/GetQueueCountAsync.py create mode 100644 inter/LiftTicketInit.py create mode 100644 inter/Query.py create mode 100644 inter/QueryOrderWaitTime.py create mode 100644 inter/__init__.py mode change 100644 => 100755 myException/PassengerUserException.py mode change 100644 => 100755 myException/UserPasswordException.py mode change 100644 => 100755 myException/__init__.py mode change 100644 => 100755 myException/balanceException.py mode change 100644 => 100755 myException/ticketConfigException.py mode change 100644 => 100755 myException/ticketIsExitsException.py mode change 100644 => 100755 myException/ticketNumOutException.py mode change 100644 => 100755 myUrllib/__init__.py mode change 100644 => 100755 myUrllib/httpUtils.py mode change 100644 => 100755 myUrllib/myurllib2.py mode change 100644 => 100755 requirements.txt mode change 100644 => 100755 run.py mode change 100644 => 100755 station_name.txt mode change 100644 => 100755 tkcode mode change 100644 => 100755 tmp/__init__.py mode change 100644 => 100755 tmp/log/__init__.py mode change 100644 => 100755 uml/uml.png diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/.idea/dictionaries/wenxianping.xml b/.idea/dictionaries/wenxianping.xml old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/__init__.py b/__init__.py old mode 100644 new mode 100755 diff --git a/agency/__init__.py b/agency/__init__.py old mode 100644 new mode 100755 diff --git a/agency/agency_tools.py b/agency/agency_tools.py old mode 100644 new mode 100755 diff --git a/agency/cdn_utils.py b/agency/cdn_utils.py old mode 100644 new mode 100755 diff --git a/cdn_list b/cdn_list old mode 100644 new mode 100755 diff --git a/config/TicketEnmu.py b/config/TicketEnmu.py new file mode 100644 index 0000000..21958a8 --- /dev/null +++ b/config/TicketEnmu.py @@ -0,0 +1,35 @@ +# coding=utf-8 + + +class ticket(object): + QUERY_C = u"查询到有余票,尝试提交订单" + QUERY_IN_BLACK_LIST = u"该车次{} 正在被关小黑屋,跳过此车次" + + SUCCESS_CODE = 000000 + FAIL_CODE = 999999 + AUTO_SUBMIT_ORDER_REQUEST_C = u"提交订单成功" + AUTO_SUBMIT_ORDER_REQUEST_F = u"提交订单失败,重新刷票中" + AUTO_SUBMIT_NEED_CODE = u"需要验证码" + AUTO_SUBMIT_NOT_NEED_CODE = u"不需要验证码" + + TICKET_BLACK_LIST_TIME = 5 # 加入小黑屋的等待时间,默认5 min + + DTO_NOT_FOUND = u"未查找到常用联系人" + DTO_NOT_IN_LIST = u"联系人不在列表中,请查证后添加" + + QUEUE_TICKET_SHORT = u"当前余票数小于乘车人数,放弃订票" + QUEUE_TICKET_SUCCESS = u"排队成功, 当前余票还剩余: {0}张" + QUEUE_JOIN_BLACK = u"排队发现未知错误{0},将此列车 {1}加入小黑屋" + QUEUE_WARNING_MSG = u"排队异常,错误信息:{0}, 将此列车 {1}加入小黑屋" + + OUT_NUM = 30 # 排队请求12306的次数 + WAIT_OUT_NUM = u"超出排队时间,自动放弃,正在重新刷票" + WAIT_ORDER_SUCCESS = u"恭喜您订票成功,订单号为:{0}, 请立即打开浏览器登录12306,访问‘未完成订单’,在30分钟内完成支付!" + WAIT_ORDER_CONTINUE = u"排队等待时间预计还剩 {0} ms" + WAIT_ORDER_FAIL = u"排队等待失败,错误消息:{0}" + WAIT_ORDER_NUM = u"第{0}次排队中,请耐心等待" + WAIT_ORDER_SUB_FAIL = u"订单提交失败!,正在重新刷票" + + CANCEL_ORDER_SUCCESS = u"排队超时,已为您自动取消订单,订单编号: {0}" + CANCEL_ORDER_FAIL = u"排队超时,取消订单失败, 订单号{0}" + diff --git a/config/__init__.py b/config/__init__.py old mode 100644 new mode 100755 diff --git a/config/configCommon.py b/config/configCommon.py old mode 100644 new mode 100755 diff --git a/config/emailConf.py b/config/emailConf.py old mode 100644 new mode 100755 diff --git a/config/logger.py b/config/logger.py old mode 100644 new mode 100755 diff --git a/config/ticketConf.py b/config/ticketConf.py old mode 100644 new mode 100755 diff --git a/config/ticket_config.yaml b/config/ticket_config.yaml old mode 100644 new mode 100755 index 5f377bd..e5dc98b --- a/config/ticket_config.yaml +++ b/config/ticket_config.yaml @@ -41,11 +41,11 @@ set: station_dates: - - "2018-03-19" -# - "2018-02-21" + - "2018-06-21" + - "2018-06-22" station_trains: - - "G4831" + - "G1321" from_station: "上海" to_station: "长沙" @@ -60,7 +60,7 @@ set: 12306count: # - uesr: "" # - pwd: "apple1995" - - uesr: "@qq.com" + - uesr: "931128603@qq.com" - pwd: "QWERTY" select_refresh_interval: 1 diff --git a/config/urlConf.py b/config/urlConf.py old mode 100644 new mode 100755 index 9d4f0bb..1a79097 --- a/config/urlConf.py +++ b/config/urlConf.py @@ -1,24 +1,27 @@ +# coding=utf-8 import random import time urls = { - "auth": { + "auth": { # 登录接口 "req_url": "/passport/web/auth/uamtk", "req_type": "post", "Referer": "https://kyfw.12306.cn/otn/passport?redirect=/otn/login/userLogin", "Host": "kyfw.12306.cn", + "Content-Type": 1, "re_try": 10, "re_time": 0.1, "s_time": 0.1, "is_logger": True, "is_json": True, }, - "login": { + "login": { # 登录接口 "req_url": "/passport/web/login", "req_type": "post", "Referer": "https://kyfw.12306.cn/otn/login/init", "Host": "kyfw.12306.cn", + "Content-Type": 1, "re_try": 10, "re_time": 0.1, "s_time": 0.1, @@ -26,29 +29,44 @@ urls = { "is_json": True, }, - "getCodeImg": { + "left_ticket_init": { # 登录接口 + "req_url": "/otn/leftTicket/init", + "req_type": "post", + "Referer": "https://kyfw.12306.cn/otn/login/init", + "Host": "kyfw.12306.cn", + "Content-Type": 1, + "re_try": 10, + "re_time": 0.1, + "s_time": 0.1, + "is_logger": False, + "is_json": False, + + }, + "getCodeImg": { # 登录验证码 "req_url": "/passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand&{0}", "req_type": "get", "Referer": "https://kyfw.12306.cn/otn/login/init", "Host": "kyfw.12306.cn", + "Content-Type": 1, "re_try": 10, "re_time": 0.1, "s_time": 0.1, "is_logger": False, "is_json": False, }, - "codeCheck": { + "codeCheck": { # 验证码校验 "req_url": "/passport/captcha/captcha-check", "req_type": "post", "Referer": "https://kyfw.12306.cn/otn/login/init", "Host": "kyfw.12306.cn", + "Content-Type": 1, "re_try": 10, "re_time": 0.1, "s_time": 0.1, "is_logger": True, "is_json": True, }, - "loginInit": { + "loginInit": { # 登录页面 "req_url": "/otn/login/init", "req_type": "get", "Referer": "https://kyfw.12306.cn/otn/index/init", @@ -59,7 +77,7 @@ urls = { "is_logger": False, "is_json": False, }, - "getUserInfo": { + "getUserInfo": { # 获取用户信息 "req_url": "/otn/index/initMy12306", "req_type": "get", "Referer": "https://kyfw.12306.cn/otn/passport?redirect=/otn/login/userLogin", @@ -70,7 +88,7 @@ urls = { "is_logger": False, "is_json": False, }, - "userLogin": { + "userLogin": { # 用户登录 "req_url": "/otn/login/userLogin", "req_type": "get", "Referer": "https://kyfw.12306.cn/otn/passport?redirect=/otn/login/userLogin", @@ -81,18 +99,19 @@ urls = { "is_logger": True, "is_json": True, }, - "uamauthclient": { + "uamauthclient": { # 登录 "req_url": "/otn/uamauthclient", "req_type": "post", "Referer": "https://kyfw.12306.cn/otn/passport?redirect=/otn/login/userLogin", "Host": "kyfw.12306.cn", + "Content-Type": 1, "re_try": 10, "re_time": 0.1, "s_time": 0.1, "is_logger": True, "is_json": True, }, - "initdc_url": { + "initdc_url": { # 生成订单页面 "req_url": "/otn/confirmPassenger/initDc", "req_type": "get", "Referer": "https://kyfw.12306.cn/otn/leftTicket/init", @@ -103,7 +122,7 @@ urls = { "is_logger": False, "is_json": False, }, - "GetJS": { + "GetJS": { # 订单页面js "req_url": "/otn/HttpZF/GetJS", "req_type": "get", "Referer": "https://kyfw.12306.cn/otn/confirmPassenger/initDc", @@ -114,7 +133,7 @@ urls = { "is_logger": False, "is_json": False, }, - "odxmfwg": { + "odxmfwg": { # 订单页面js "req_url": "/otn/dynamicJs/odxmfwg", "req_type": "get", "Referer": "https://kyfw.12306.cn/otn/confirmPassenger/initDc", @@ -125,7 +144,7 @@ urls = { "is_logger": False, "is_json": False, }, - "get_passengerDTOs": { + "get_passengerDTOs": { # 获取乘车人 "req_url": "/otn/confirmPassenger/getPassengerDTOs", "req_type": "post", "Referer": "https://kyfw.12306.cn/otn/confirmPassenger/initDc", @@ -136,8 +155,8 @@ urls = { "is_logger": True, "is_json": True, }, - "select_url": { - "req_url": "/otn/leftTicket/queryO?leftTicketDTO.train_date={0}&leftTicketDTO.from_station={1}&leftTicketDTO.to_station={2}&purpose_codes=ADULT", + "select_url": { # 查询余票 + "req_url": "/otn/leftTicket/query?leftTicketDTO.train_date={0}&leftTicketDTO.from_station={1}&leftTicketDTO.to_station={2}&purpose_codes=ADULT", "req_type": "post", "Referer": "https://kyfw.12306.cn/otn/leftTicket/init", "Host": "kyfw.12306.cn", @@ -147,7 +166,7 @@ urls = { "is_logger": False, "is_json": True, }, - "check_user_url": { + "check_user_url": { # 检查用户登录 "req_url": "/otn/login/checkUser", "req_type": "post", "Referer": "https://kyfw.12306.cn/otn/leftTicket/init", @@ -158,7 +177,7 @@ urls = { "is_logger": True, "is_json": True, }, - "submit_station_url": { + "submit_station_url": { # 提交订单 "req_url": "/otn/leftTicket/submitOrderRequest", "req_type": "post", "Referer": "https://kyfw.12306.cn/otn/leftTicket/init", @@ -169,7 +188,7 @@ urls = { "is_logger": True, "is_json": True, }, - "checkOrderInfoUrl": { + "checkOrderInfoUrl": { # 检查订单信息规范 "req_url": "/otn/confirmPassenger/checkOrderInfo", "req_type": "post", "Referer": "https://kyfw.12306.cn/otn/confirmPassenger/initDc", @@ -180,7 +199,7 @@ urls = { "is_logger": True, "is_json": True, }, - "getQueueCountUrl": { + "getQueueCountUrl": { # 剩余余票数 "req_url": "/otn/confirmPassenger/getQueueCount", "req_type": "post", "Referer": "https://kyfw.12306.cn/otn/confirmPassenger/initDc", @@ -191,7 +210,7 @@ urls = { "is_logger": True, "is_json": True, }, - "checkQueueOrderUrl": { + "checkQueueOrderUrl": { # 订单队列排队 "req_url": "/otn/confirmPassenger/confirmSingleForQueue", "req_type": "post", "Referer": "https://kyfw.12306.cn/otn/confirmPassenger/initDc", @@ -202,7 +221,7 @@ urls = { "is_logger": True, "is_json": True, }, - "checkRandCodeAnsyn": { + "checkRandCodeAnsyn": { # 暂时没用到 "req_url": "/otn/passcodeNew/checkRandCodeAnsyn", "req_type": "post", "Referer": "https://kyfw.12306.cn/otn/confirmPassenger/initDc", @@ -213,7 +232,7 @@ urls = { "is_logger": True, "is_json": True, }, - "codeImgByOrder": { + "codeImgByOrder": { # 订单页面验证码 "req_url": "/otn/passcodeNew/getPassCodeNew?module=passenger&rand=randp&%s" % random.random(), "req_type": "post", "Referer": "https://kyfw.12306.cn/otn/confirmPassenger/initDc", @@ -224,9 +243,9 @@ urls = { "is_logger": False, "is_json": False, }, - "queryOrderWaitTimeUrl": { - "req_url": "/otn/confirmPassenger/queryOrderWaitTime", - "req_type": "post", + "queryOrderWaitTimeUrl": { # 订单等待页面 + "req_url": "/otn/confirmPassenger/queryOrderWaitTime?random={0}&tourFlag=dc&_json_att=", + "req_type": "get", "Referer": "https://kyfw.12306.cn/otn/confirmPassenger/initDc", "Host": "kyfw.12306.cn", "re_try": 10, @@ -235,7 +254,7 @@ urls = { "is_logger": True, "is_json": True, }, - "queryMyOrderNoCompleteUrl": { + "queryMyOrderNoCompleteUrl": { # 订单查询页面 "req_url": "/otn/queryOrder/queryMyOrderNoComplete", "req_type": "post", "Referer": "https://kyfw.12306.cn/otn/queryOrder/initNoComplete", @@ -246,7 +265,7 @@ urls = { "is_logger": True, "is_json": True, }, - "initNoCompleteUrl": { + "initNoCompleteUrl": { # 获取订单列表 "req_url": "/otn/queryOrder/initNoComplete", "req_type": "post", "Referer": "https://kyfw.12306.cn/otn/queryOrder/initNoComplete", @@ -257,7 +276,7 @@ urls = { "is_logger": True, "is_json": True, }, - "cancelNoCompleteMyOrder": { + "cancelNoCompleteMyOrder": { # 取消订单 "req_url": "/otn/queryOrder/cancelNoCompleteMyOrder", "req_type": "post", "Referer": "https://kyfw.12306.cn/otn/queryOrder/initNoComplete", @@ -268,32 +287,35 @@ urls = { "is_logger": True, "is_json": True, }, - "autoSubmitOrderRequest": { + "autoSubmitOrderRequest": { # 快速自动提交订单 "req_url": "/otn/confirmPassenger/autoSubmitOrderRequest", "req_type": "post", - "Referer": "https://kyfw.12306.cn/otn/queryOrder/initNoComplete", + "Referer": "https://kyfw.12306.cn/otn/leftTicket/init", "Host": "kyfw.12306.cn", + "Content-Type": 1, "re_try": 10, "re_time": 0.1, "s_time": 0.1, "is_logger": True, "is_json": True, }, - "getQueueCountAsync": { + "getQueueCountAsync": { # 快速获取订单数据 "req_url": "/otn/confirmPassenger/getQueueCountAsync", "req_type": "post", - "Referer": "https://kyfw.12306.cn/otn/queryOrder/initNoComplete", + "Referer": "https://kyfw.12306.cn/otn/leftTicket/init", "Host": "kyfw.12306.cn", + "Content-Type": 1, "re_try": 10, "re_time": 0.1, "s_time": 0.1, "is_logger": True, "is_json": True, }, - "confirmSingleForQueueAsys": { + "confirmSingleForQueueAsys": { # 快速订单排队 "req_url": "/otn/confirmPassenger/confirmSingleForQueueAsys", "req_type": "post", - "Referer": "https://kyfw.12306.cn/otn/queryOrder/initNoComplete", + "Referer": "https://kyfw.12306.cn/otn/leftTicket/init", + "Content-Type": 1, "Host": "kyfw.12306.cn", "re_try": 10, "re_time": 0.1, diff --git a/damatuCode/__init__.py b/damatuCode/__init__.py old mode 100644 new mode 100755 diff --git a/damatuCode/damatuWeb.py b/damatuCode/damatuWeb.py old mode 100644 new mode 100755 diff --git a/damatuCode/ruokuai.py b/damatuCode/ruokuai.py old mode 100644 new mode 100755 diff --git a/init/SelectTicketInfoFast.py b/init/SelectTicketInfoFast.py new file mode 100755 index 0000000..49cb19b --- /dev/null +++ b/init/SelectTicketInfoFast.py @@ -0,0 +1,293 @@ +# -*- coding=utf-8 -*- +import datetime +import json +import random +import re +import socket +import sys +import threading +import time +import urllib +from collections import OrderedDict + +import collections + +from agency.cdn_utils import CDNProxy +from config import urlConf +from config.emailConf import sendEmail +from config.ticketConf import _get_yaml +from init import login +from init.login import GoLogin +from inter.AutoSubmitOrderRequest import autoSubmitOrderRequest +from inter.ConfirmSingleForQueueAsys import confirmSingleForQueueAsys +from inter.GetPassengerDTOs import getPassengerDTOs +from inter.GetQueueCountAsync import getQueueCountAsync +from inter.LiftTicketInit import liftTicketInit +from inter.Query import query +from inter.QueryOrderWaitTime import queryOrderWaitTime +from myException.PassengerUserException import PassengerUserException +from myException.UserPasswordException import UserPasswordException +from myException.ticketConfigException import ticketConfigException +from myException.ticketIsExitsException import ticketIsExitsException +from myException.ticketNumOutException import ticketNumOutException +from myUrllib.httpUtils import HTTPClient + +reload(sys) +sys.setdefaultencoding('utf-8') + + +class selectFast: + """ + 快速提交车票通道 + """ + + def __init__(self): + self.from_station, self.to_station, self.station_dates, self._station_seat, self.is_more_ticket, self.ticke_peoples, self.select_refresh_interval, self.station_trains, self.ticket_black_list_time = self.get_ticket_info() + self.is_aotu_code = _get_yaml()["is_aotu_code"] + self.aotu_code_type = _get_yaml()["aotu_code_type"] + self.is_cdn = _get_yaml()["is_cdn"] + self.httpClint = HTTPClient() + self.urls = urlConf.urls + self.login = GoLogin(self.httpClint, self.urls, self.is_aotu_code, self.aotu_code_type) + self.is_download_img = False + self.cdn_list = [] + self.is_check_user = dict() + self.ticket_black_list = dict() + self.black_train_no = "" + self.passengerTicketStrList = "" + self.oldPassengerStr = "" + + + def get_ticket_info(self): + """ + 获取配置信息 + :return: + """ + ticket_info_config = _get_yaml() + from_station = ticket_info_config["set"]["from_station"].encode("utf8") + to_station = ticket_info_config["set"]["to_station"].encode("utf8") + station_dates = ticket_info_config["set"]["station_dates"] + set_type = ticket_info_config["set"]["set_type"] + is_more_ticket = ticket_info_config["set"]["is_more_ticket"] + ticke_peoples = ticket_info_config["set"]["ticke_peoples"] + select_refresh_interval = ticket_info_config["select_refresh_interval"] + station_trains = ticket_info_config["set"]["station_trains"] + ticket_black_list_time = ticket_info_config["ticket_black_list_time"] + print u"*" * 20 + print u"12306刷票小助手,最后更新于2018.2.28,请勿作为商业用途,交流群号:286271084" + print u"如果有好的margin,请联系作者,表示非常感激\n" + print u"当前配置:出发站:{0}\n到达站:{1}\n乘车日期:{2}\n坐席:{3}\n是否有票自动提交:{4}\n乘车人:{5}\n刷新间隔:随机(1-4S)\n候选购买车次:{7}\n僵尸票关小黑屋时长:{8}\n".format \ + ( + from_station, + to_station, + station_dates, + ",".join(set_type), + is_more_ticket, + ",".join(ticke_peoples), + select_refresh_interval, + ",".join(station_trains), + ticket_black_list_time, + ) + print u"*" * 20 + return from_station, to_station, station_dates, set_type, is_more_ticket, ticke_peoples, select_refresh_interval, station_trains, ticket_black_list_time + + def station_table(self, from_station, to_station): + """ + 读取车站信息 + :param station: + :return: + """ + result = open('station_name.txt') + info = result.read().split('=')[1].strip("'").split('@') + del info[0] + station_name = {} + for i in range(0, len(info)): + n_info = info[i].split('|') + station_name[n_info[1]] = n_info[2] + from_station = station_name[from_station.encode("utf8")] + to_station = station_name[to_station.encode("utf8")] + return from_station, to_station + + def call_login(self, auth=False): + """ + 登录回调方法 + :return: + """ + if auth: + return self.login.auth() + else: + self.login.go_login() + + def check_user(self): + """ + 检查用户是否达到订票条件 + :return: + """ + check_user_url = self.urls["check_user_url"] + data = {"_json_att": ""} + check_user = self.httpClint.send(check_user_url, data) + check_user_flag = check_user['data']['flag'] + if check_user_flag is True: + self.is_check_user["user_time"] = datetime.datetime.now() + else: + if check_user['messages']: + print (u'用户检查失败:%s,可能未登录,可能session已经失效' % check_user['messages'][0]) + print (u'正在尝试重新登录') + self.call_login() + self.is_check_user["user_time"] = datetime.datetime.now() + else: + print (u'用户检查失败: %s,可能未登录,可能session已经失效' % check_user) + print (u'正在尝试重新登录') + self.call_login() + self.is_check_user["user_time"] = datetime.datetime.now() + + def main(self): + l = liftTicketInit(session=self) + l.reqLiftTicketInit() + self.call_login() + self.check_user() + from_station, to_station = self.station_table(self.from_station, self.to_station) + passengerTicketStrList, oldPassengerStr, set_type = "", "", "" + num = 1 + while 1: + try: + num += 1 + if "user_time" in self.is_check_user and ( + datetime.datetime.now() - self.is_check_user["user_time"]).seconds / 60 > 5: + # 5分钟检查一次用户是否登录 + self.check_user() + time.sleep(self.select_refresh_interval) + if time.strftime('%H:%M:%S', time.localtime(time.time())) > "23:00:00" or time.strftime('%H:%M:%S', + time.localtime( + time.time())) < "06:00:00": + print(u"12306休息时间,本程序自动停止,明天早上6点将自动运行") + while 1: + time.sleep(1) + if "06:00:00" < time.strftime('%H:%M:%S', time.localtime(time.time())) < "23:00:00": + print(u"休息时间已过,重新开启检票功能") + self.call_login() + break + start_time = datetime.datetime.now() + + q = query(session=self, + from_station=from_station, + to_station=to_station, + from_station_h=self.from_station, + to_station_h=self.to_station, + _station_seat=self._station_seat, + station_trains=self.station_trains, + station_dates=self.station_dates, + black_train_no=self.black_train_no) + queryResult = q.sendQuery() + self.black_train_no = "" # 重置小黑屋名单 + # 查询接口 + if queryResult.get("status", False): + secretStr = queryResult.get("secretStr", "") + train_no = queryResult.get("train_no", "") + stationTrainCode = queryResult.get("stationTrainCode", "") + train_date = queryResult.get("train_date", "") + query_from_station_name = queryResult.get("query_from_station_name", "") + query_to_station_name = queryResult.get("query_to_station_name", "") + set_type = queryResult.get("set_type", "") + leftTicket = queryResult.get("leftTicket", "") + if self.ticket_black_list.has_key(train_no) and ( + datetime.datetime.now() - self.ticket_black_list[train_no]).seconds / 60 < int( + self.ticket_black_list_time): + print(u"该车次{} 正在被关小黑屋,跳过此车次".format(train_no)) + else: + # 获取联系人 + if not self.passengerTicketStrList and not self.oldPassengerStr: + s = getPassengerDTOs(session=self, ticket_peoples=self.ticke_peoples, set_type=set_type) + getPassengerDTOsResult = s.getPassengerTicketStrListAndOldPassengerStr() + if getPassengerDTOsResult.get("status", False): + self.passengerTicketStrList = getPassengerDTOsResult.get("passengerTicketStrList", "") + self.oldPassengerStr = getPassengerDTOsResult.get("oldPassengerStr", "") + set_type = getPassengerDTOsResult.get("set_type", "") + # 提交订单 + a = autoSubmitOrderRequest(session=self, + secretStr=secretStr, + train_date=train_date, + query_from_station_name=self.from_station, + query_to_station_name=self.to_station, + passengerTicketStr=self.passengerTicketStrList, + oldPassengerStr=self.oldPassengerStr + ) + submitResult = a.sendAutoSubmitOrderRequest() + if submitResult.get("status", False): + result = submitResult.get("result", "") + # 订单排队 + time.sleep(submitResult.get("ifShowPassCodeTime", 1)) + g = getQueueCountAsync(session=self, + train_no=train_no, + stationTrainCode=stationTrainCode, + fromStationTelecode=query_from_station_name, + toStationTelecode=query_to_station_name, + leftTicket=leftTicket, + set_type=set_type, + users=len(self.ticke_peoples), + ) + getQueueCountAsyncResult = g.sendGetQueueCountAsync() + time.sleep(submitResult.get("ifShowPassCodeTime", 1)) + if getQueueCountAsyncResult.get("is_black", False): + self.black_train_no = getQueueCountAsyncResult.get("train_no", "") + if getQueueCountAsyncResult.get("status", False): + # 请求订单快读接口 + c = confirmSingleForQueueAsys(session=self, + passengerTicketStr=self.passengerTicketStrList, + oldPassengerStr=self.oldPassengerStr, + result=result, ) + confirmSingleForQueueAsysResult = c.sendConfirmSingleForQueueAsys() + # 排队 + if confirmSingleForQueueAsysResult.get("status", False): + qwt = queryOrderWaitTime(session=self) + qwt.sendQueryOrderWaitTime() + else: + self.httpClint.del_cookies() + + else: + s_time = random.randint(0, 4) + time.sleep(s_time) + print u"正在第{0}次查询 随机停留时长:{6} 乘车日期: {1} 车次:{2} 查询无票 cdn轮询IP:{4}当前cdn总数:{5} 总耗时:{3}ms".format(num, + ",".join( + self.station_dates), + ",".join( + self.station_trains), + ( + datetime.datetime.now() - start_time).microseconds / 1000, + self.httpClint.cdn, + len( + self.cdn_list), + s_time) + except PassengerUserException as e: + print e.message + break + except ticketConfigException as e: + print e.message + break + except ticketIsExitsException as e: + print e.message + break + except ticketNumOutException as e: + print e.message + break + except UserPasswordException as e: + print e.message + break + except ValueError as e: + if e.message == "No JSON object could be decoded": + print(u"12306接口无响应,正在重试") + else: + print(e.message) + except KeyError as e: + print(e.message) + # except TypeError as e: + # print(u"12306接口无响应,正在重试 {0}".format(e.message)) + except socket.error as e: + print(e.message) + + +if __name__ == '__main__': + s = selectFast() + s.main() + # a = select('上海', '北京') + # a.main() diff --git a/init/__init__.py b/init/__init__.py old mode 100644 new mode 100755 diff --git a/init/login.py b/init/login.py old mode 100644 new mode 100755 index c66ee55..ccdd82f --- a/init/login.py +++ b/init/login.py @@ -216,10 +216,14 @@ class GoLogin: login_num = 0 while True: self.cookietp() - self.httpClint.set_cookies(_jc_save_showIns="true", - _jc_save_wfdc_flag="dc", - _jc_save_fromDate=_get_yaml()["set"]["station_dates"][0], - _jc_save_toDate=_get_yaml()["set"]["station_dates"][0]) + # self.httpClint.set_cookies(_jc_save_showIns="true", + # _jc_save_wfdc_flag="dc", + # _jc_save_toDate="2018-06-06", + # _jc_save_fromDate=_get_yaml()["set"]["station_dates"][0], + # RAIL_EXPIRATION="1528337042724", + # RAIL_DEVICEID="O6DFHLmFChFrZUI7QyY9xcqj94eZG9JH_kD3zSZt53tkCUq0uqdvlo1fm_CmNxr_QAnMOU79JmHI8jbtj2vaNUnOZKCqcsMNbhCaoDIB3vxgsyzMMGOZF-CknXKEFaCLPGyDNXEknPDs7xgSbanwKqsiSRT41xti", + # + # ) self.urlConf["getCodeImg"]["req_url"] = self.urlConf["getCodeImg"]["req_url"].format(random.random()) self.readImg(self.urlConf["getCodeImg"]) self.randCode = self.getRandCode() diff --git a/init/select_ticket_info.py b/init/select_ticket_info.py old mode 100644 new mode 100755 index 4473414..f670ac3 --- a/init/select_ticket_info.py +++ b/init/select_ticket_info.py @@ -43,6 +43,7 @@ class select: self.user_info = "" self.secretStr = "" self.ticket_black_list = dict() + self.ticket_black_list_time = dict() self.is_check_user = dict() self.httpClint = HTTPClient() self.confUrl = urlConf.urls @@ -204,7 +205,7 @@ class select: 'REPEAT_SUBMIT_TOKEN': self.token } jsonData = self.httpClint.send(get_passengerDTOs, get_data) - if 'data' in jsonData and jsonData['data'] and 'normal_passengers' in jsonData['data'] and jsonData['data'][ + if 'data' in jsonData and jsonData['data'] and 'l' in jsonData['data'] and jsonData['data'][ 'normal_passengers']: normal_passengers = jsonData['data']['normal_passengers'] _normal_passenger = [normal_passengers[i] for i in range(len(normal_passengers))if normal_passengers[i]["passenger_name"] in self.ticke_peoples] @@ -337,7 +338,7 @@ class select: """ 获取getPassengerTicketStr 提交对应的代号码 :param str: 坐席 - :return: + :return: """ passengerTicketStr = { '一等座': 'M', @@ -581,7 +582,7 @@ class select: elif "waitTime"in queryOrderWaitTimeResult["data"] and queryOrderWaitTimeResult["data"]["waitTime"]: print(u"排队等待时间预计还剩 {0} ms".format(0-queryOrderWaitTimeResult["data"]["waitTime"])) else: - print ("正在等待中") + print (u"正在等待中") elif "messages" in queryOrderWaitTimeResult and queryOrderWaitTimeResult["messages"]: print(u"排队等待失败: " + queryOrderWaitTimeResult["messages"]) else: @@ -732,7 +733,8 @@ class select: print u"正在第{0}次查询 乘车日期: {1} 车次{2} 查询无票 cdn轮询IP {4} 当前cdn总数{5} 总耗时{3}ms".format(num, ",".join(self.station_dates), ",".join(self.station_trains), - (datetime.datetime.now()-start_time).microseconds/1000, self.httpClint.cdn, + (datetime.datetime.now()-start_time).microseconds/1000, + self.httpClint.cdn, len(self.cdn_list)) self.set_cdn() except PassengerUserException as e: diff --git a/inter/AutoSubmitOrderRequest.py b/inter/AutoSubmitOrderRequest.py new file mode 100644 index 0000000..f9a7053 --- /dev/null +++ b/inter/AutoSubmitOrderRequest.py @@ -0,0 +1,108 @@ +# coding=utf-8 +import urllib +from collections import OrderedDict + +from config.TicketEnmu import ticket + + +class autoSubmitOrderRequest: + """ + 快读提交订单通道 + """ + def __init__(self, session, + secretStr, + train_date, + query_from_station_name, + query_to_station_name, + passengerTicketStr, + oldPassengerStr): + self.secretStr = urllib.unquote(secretStr) + self.train_date = train_date + self.query_from_station_name = query_from_station_name + self.query_to_station_name = query_to_station_name + self.passengerTicketStr = passengerTicketStr + self.oldPassengerStr = oldPassengerStr + self.session = session + + def data_par(self): + """ + 参数结构 + 自动提交代码接口-autoSubmitOrderRequest + - 字段说明 + - secretStr 车票代码 + - train_date 乘车日期 + - tour_flag 乘车类型 + - purpose_codes 学生还是成人 + - query_from_station_name 起始车站 + - query_to_station_name 结束车站 + - cancel_flag 默认2,我也不知道干嘛的 + - bed_level_order_num 000000000000000000000000000000 + - passengerTicketStr 乘客乘车代码 + - oldPassengerStr 乘客编号代码 + :return: + """ + data = OrderedDict() + data["secretStr"] = self.secretStr + data["train_date"] = self.train_date + data["tour_flag"] = "dc" + data["purpose_codes"] = "ADULT" + data["query_from_station_name"] = self.query_from_station_name + data["query_to_station_name"] = self.query_to_station_name + data["cancel_flag"] = 2 + data["bed_level_order_num"] = "000000000000000000000000000000" + data["passengerTicketStr"] = self.passengerTicketStr + data["oldPassengerStr"] = self.oldPassengerStr + return data + + def sendAutoSubmitOrderRequest(self): + """ + 请求下单接口 + :return: + """ + urls = self.session.urls["autoSubmitOrderRequest"] + data = self.data_par() + autoSubmitOrderRequestResult = self.session.httpClint.send(urls, data) + if autoSubmitOrderRequestResult and \ + autoSubmitOrderRequestResult.get("status", False) and\ + autoSubmitOrderRequestResult.get("httpstatus", False) == 200: + requestResultData = autoSubmitOrderRequestResult.get("data", {}) + if requestResultData: + result = requestResultData.get("result", "") + ifShowPassCode = requestResultData.get("ifShowPassCode", "N") + print(ticket.AUTO_SUBMIT_ORDER_REQUEST_C) + if ifShowPassCode == "Y": # 如果需要验证码 + print(ticket.AUTO_SUBMIT_NEED_CODE) + return { + "result": result, + "ifShowPassCode": ifShowPassCode, + "code": ticket.SUCCESS_CODE, + "ifShowPassCodeTime": requestResultData.get("requestResultData", 2000) / float(1000), + "status": True, + } + else: + print(ticket.AUTO_SUBMIT_NOT_NEED_CODE) + return { + "result": result, + "ifShowPassCode": ifShowPassCode, + "code": ticket.SUCCESS_CODE, + "ifShowPassCodeTime": requestResultData.get("requestResultData", 2000) / float(1000), + "status": True, + } + else: + print(ticket.AUTO_SUBMIT_ORDER_REQUEST_F) + if autoSubmitOrderRequestResult.get("messages", ""): + print(autoSubmitOrderRequestResult.get("messages", "")) + return { + "code": ticket.FAIL_CODE, + "status": False, + } + elif autoSubmitOrderRequestResult.get("validateMessages", ""): + print(autoSubmitOrderRequestResult.get("validateMessages", "")) + return { + "code": ticket.FAIL_CODE, + "status": False, + } + + + + diff --git a/inter/ConfirmSingleForQueueAsys.py b/inter/ConfirmSingleForQueueAsys.py new file mode 100644 index 0000000..0499dc6 --- /dev/null +++ b/inter/ConfirmSingleForQueueAsys.py @@ -0,0 +1,78 @@ +# coding=utf-8 +import json +import urllib +from collections import OrderedDict + + +class confirmSingleForQueueAsys: + """ + 订单快读排队 + """ + def __init__(self, + session, + passengerTicketStr, + oldPassengerStr, + result, + randCode="", + ): + self.session = session + self.passengerTicketStr = passengerTicketStr + self.oldPassengerStr = oldPassengerStr + self.result = result if isinstance(result, str) else str(result) + self.randCode = randCode + + def data_par(self): + """ + 字段说明 + passengerTicketStr 乘客乘车代码 + oldPassengerStr 乘客编号代码 + randCode 填空 + purpose_codes 学生还是成人 + key_check_isChange autoSubmitOrderRequest返回的result字段做切割即可 + leftTicketStr autoSubmitOrderRequest返回的result字段做切割即可 + train_location autoSubmitOrderRequest返回的result字段做切割即可 + choose_seats + seatDetailType + _json_att + :return: + """ + results = self.result.split("#") + key_check_isChange = results[1] + leftTicketStr = results[2] + train_location = results[0] + data = OrderedDict() + data["passengerTicketStr"] = self.passengerTicketStr + data["oldPassengerStr"] = self.oldPassengerStr + data["randCode"] = self.randCode + data["purpose_codes"] = "ADULT" + data["key_check_isChange"] = key_check_isChange + data["leftTicketStr"] = leftTicketStr + data["train_location"] = train_location + data["choose_seats"] = "" + data["seatDetailType"] = "" + data["_json_att"] = "" + return data + + def sendConfirmSingleForQueueAsys(self): + """ + 请求订单快读排队接口 + :return: + """ + urls = self.session.urls["confirmSingleForQueueAsys"] + data = self.data_par() + confirmSingleForQueueAsysResult = self.session.httpClint.send(urls, data) + if confirmSingleForQueueAsysResult.get("status", False) and confirmSingleForQueueAsysResult.get("data", False): + queueData = confirmSingleForQueueAsysResult.get("data", {}) + if queueData.get("submitStatus", False): + return { + "status": True + } + else: + print(queueData.get("errMsg", "")) + return { + "status": False + } + else: + return { + "status": False + } \ No newline at end of file diff --git a/inter/GetPassengerDTOs.py b/inter/GetPassengerDTOs.py new file mode 100644 index 0000000..a50c572 --- /dev/null +++ b/inter/GetPassengerDTOs.py @@ -0,0 +1,92 @@ +# coding=utf-8 +from config.TicketEnmu import ticket +from myException.PassengerUserException import PassengerUserException + + +class getPassengerDTOs: + """ + 获取乘客信息 + :return: + """ + def __init__(self, session, ticket_peoples, set_type): + """ + :param session: 登录实例 + :param ticket_peoples: 乘客 + :param set_type: 坐席 + """ + self.session = session + self.ticket_peoples = ticket_peoples + self.set_type = set_type.encode("utf8") + + def sendGetPassengerDTOs(self): + getPassengerDTOsResult = self.session.httpClint.send(self.session.urls["get_passengerDTOs"], {}) + getPassengerDTOsResult["data"].get("normal_passengers", False) + if getPassengerDTOsResult.get("data", False) and getPassengerDTOsResult["data"].get("normal_passengers", False): + normal_passengers = getPassengerDTOsResult['data']['normal_passengers'] + _normal_passenger = [normal_passengers[i] for i in range(len(normal_passengers)) if + normal_passengers[i]["passenger_name"] in self.ticket_peoples] + return _normal_passenger if _normal_passenger else [normal_passengers[0]] # 如果配置乘车人没有在账号,则默认返回第一个用户 + else: + if getPassengerDTOsResult.get("data", False) and getPassengerDTOsResult['data'].get("exMsg", False): + print(getPassengerDTOsResult['data'].get("exMsg", False)) + elif getPassengerDTOsResult.get('messages', False): + print(getPassengerDTOsResult.get('messages', False)) + else: + raise PassengerUserException(ticket.DTO_NOT_FOUND) + + def getPassengerTicketStr(self, set_type): + """ + 获取getPassengerTicketStr 提交对应的代号码 + :param str: 坐席 + :return: + """ + passengerTicketStr = { + '一等座': 'M', + '特等座': 'P', + '二等座': 'O', + '商务座': 9, + '硬座': 1, + '无座': 1, + '软卧': 4, + '硬卧': 3, + } + return str(passengerTicketStr[set_type.replace(' ', '')]) + + def getPassengerTicketStrListAndOldPassengerStr(self): + """ + 获取提交车次人内容格式 + passengerTicketStr O,0,1,文贤平,1,43052419950223XXXX,15618715583,N_O,0,1,梁敏,1,43052719920118XXXX,,N + oldPassengerStr 文贤平,1,43052719920118XXXX,1_梁敏,1,43052719920118XXXX,1_ + :return: + """ + passengerTicketStrList = [] + oldPassengerStr = [] + user_info = self.sendGetPassengerDTOs() + set_type = self.getPassengerTicketStr(self.set_type) + if not user_info: + raise PassengerUserException(ticket.DTO_NOT_IN_LIST) + if len(user_info) is 1: + passengerTicketStrList.append( + '0,' + user_info[0]['passenger_type'] + "," + user_info[0][ + "passenger_name"] + "," + + user_info[0]['passenger_id_type_code'] + "," + user_info[0]['passenger_id_no'] + "," + + user_info[0]['mobile_no'] + ',N') + oldPassengerStr.append( + user_info[0]['passenger_name'] + "," + user_info[0]['passenger_id_type_code'] + "," + + user_info[0]['passenger_id_no'] + "," + user_info[0]['passenger_type'] + '_') + else: + for i in xrange(len(user_info)): + passengerTicketStrList.append( + '0,' + user_info[i]['passenger_type'] + "," + user_info[i][ + "passenger_name"] + "," + user_info[i]['passenger_id_type_code'] + "," + user_info[i][ + 'passenger_id_no'] + "," + user_info[i]['mobile_no'] + ',N_' + set_type) + oldPassengerStr.append( + user_info[i]['passenger_name'] + "," + user_info[i]['passenger_id_type_code'] + "," + + user_info[i]['passenger_id_no'] + "," + user_info[i]['passenger_type'] + '_') + return { + "passengerTicketStrList": set_type + "," + ",".join(passengerTicketStrList), + "oldPassengerStr": "".join(oldPassengerStr), + "code": ticket.SUCCESS_CODE, + "set_type": set_type, + "status": True, + } diff --git a/inter/GetQueueCountAsync.py b/inter/GetQueueCountAsync.py new file mode 100644 index 0000000..040810f --- /dev/null +++ b/inter/GetQueueCountAsync.py @@ -0,0 +1,102 @@ +# coding=utf-8 +import time +from collections import OrderedDict + +from config.TicketEnmu import ticket + + +class getQueueCountAsync: + """ + 排队 + """ + def __init__(self, + session, + train_no, + stationTrainCode, + fromStationTelecode, + toStationTelecode, + leftTicket, + set_type, + users,): + self.train_no = train_no + self.session = session + self.stationTrainCode = stationTrainCode + self.fromStationTelecode = fromStationTelecode + self.toStationTelecode = toStationTelecode + self.set_type = set_type + self.leftTicket = leftTicket + self.users = users + + def data_par(self): + """ + - 字段说明 + - train_date 时间 + - train_no 列车编号,查询代码里面返回 + - stationTrainCode 列车编号 + - seatType 对应坐席 + - fromStationTelecode 起始城市 + - toStationTelecode 到达城市 + - leftTicket 查询代码里面返回 + - purpose_codes 学生还是成人 + - _json_att 没啥卵用,还是带上吧 + :return: + """ + l_time = time.localtime(time.time()) + new_train_date = time.strftime("%b %d %Y %H:%M:%S", l_time) + data = OrderedDict() + # data["train_date"] = "Fri " + str(new_train_date) + " GMT+0800 (CST)" + data["train_date"] = "Fri Jun 21 2018 18:23:54 GMT+0800 (CST)" + data["train_no"] = self.train_no + data["stationTrainCode"] = self.stationTrainCode + data["seatType"] = self.set_type + data["fromStationTelecode"] = self.fromStationTelecode + data["toStationTelecode"] = self.toStationTelecode + data["leftTicket"] = self.leftTicket + data["purpose_codes"] = "ADULT" + data["_json_att"] = "" + return data + + def conversion_int(self, str): + return int(str) + + def sendGetQueueCountAsync(self): + """ + 请求排队接口 + :return: + """ + urls = self.session.urls["getQueueCountAsync"] + data = self.data_par() + getQueueCountAsyncResult = self.session.httpClint.send(urls, data) + if getQueueCountAsyncResult.get("status", False) and getQueueCountAsyncResult.get("data", False): + if "status" in getQueueCountAsyncResult and getQueueCountAsyncResult["status"] is True: + if "countT" in getQueueCountAsyncResult["data"]: + ticket_data = getQueueCountAsyncResult["data"]["ticket"] + ticket_split = sum(map(self.conversion_int, ticket_data.split(","))) if ticket_data.find( + ",") != -1 else ticket_data + countT = getQueueCountAsyncResult["data"]["countT"] + if int(countT) is 0: + if int(ticket_split) < self.users: + print(ticket.QUEUE_TICKET_SHORT) + return {"status": False, "is_black": False} + else: + print(ticket.QUEUE_TICKET_SUCCESS.format(ticket_split)) + return {"status": True, "is_black": False} + else: + return {"status": False, "is_black": True} + else: + print(ticket.QUEUE_JOIN_BLACK.format(getQueueCountAsyncResult, self.train_no)) + return {"status": False, "is_black": True, "train_no": self.train_no} + elif "messages" in getQueueCountAsyncResult and getQueueCountAsyncResult["messages"]: + print(ticket.QUEUE_WARNING_MSG.format(getQueueCountAsyncResult["messages"][0], self.train_no)) + return {"status": False, "is_black": True, "train_no": self.train_no} + else: + if "validateMessages" in getQueueCountAsyncResult and getQueueCountAsyncResult["validateMessages"]: + print(str(getQueueCountAsyncResult["validateMessages"])) + return {"status": False, "is_black": False} + else: + return {"status": False, "is_black": False} + else: + return {"status": False, "is_black": False} + + + diff --git a/inter/LiftTicketInit.py b/inter/LiftTicketInit.py new file mode 100644 index 0000000..98c0eb4 --- /dev/null +++ b/inter/LiftTicketInit.py @@ -0,0 +1,17 @@ +# coding=utf-8 + + +class liftTicketInit: + def __init__(self, session): + self.session = session + + def reqLiftTicketInit(self): + """ + 请求抢票页面 + :return: + """ + urls = self.session.urls["left_ticket_init"] + self.session.httpClint.send(urls) + return { + "status": True + } \ No newline at end of file diff --git a/inter/Query.py b/inter/Query.py new file mode 100644 index 0000000..7c6a6e3 --- /dev/null +++ b/inter/Query.py @@ -0,0 +1,119 @@ +# coding=utf-8 +import copy +import datetime +import random +import time + +from config.TicketEnmu import ticket + + +class query: + """ + 查询接口 + """ + + def __init__(self, session, from_station, to_station, from_station_h, to_station_h, _station_seat, station_trains, + black_train_no, station_dates=None, ): + self.session = session + self.from_station = from_station + self.to_station = to_station + self.from_station_h = from_station_h + self.to_station_h = to_station_h + self.station_trains = station_trains + self._station_seat = _station_seat if isinstance(_station_seat, list) else list(_station_seat) + self.station_dates = station_dates if isinstance(station_dates, list) else list(station_dates) + self.ticket_black_list = dict() + self.black_train_no = black_train_no + + def station_seat(self, index): + """ + 获取车票对应坐席 + :return: + """ + seat = {'商务座': 32, + '一等座': 31, + '二等座': 30, + '特等座': 25, + '软卧': 23, + '硬卧': 28, + '硬座': 29, + '无座': 26, + } + return seat[index] + + def sendQuery(self): + """ + 查询 + :return: + """ + if self.black_train_no: + self.ticket_black_list[self.black_train_no] = datetime.datetime.now() + for station_date in self.station_dates: + select_url = copy.copy(self.session.urls["select_url"]) + select_url["req_url"] = select_url["req_url"].format( + station_date, self.from_station, self.to_station) + station_ticket = self.session.httpClint.send(select_url) + value = station_ticket.get("data", "") + if not value: + print (u'{0}-{1} 车次坐席查询为空'.format(self.from_station, self.to_station)) + else: + result = value.get('result', []) + if result: + for i in value['result']: + ticket_info = i.split('|') + if ticket_info[11] == "Y" and ticket_info[1].encode("utf8") == "预订": # 筛选未在开始时间内的车次 + for j in xrange(len(self._station_seat)): + is_ticket_pass = ticket_info[self.station_seat(self._station_seat[j].encode("utf8"))] + if is_ticket_pass != '' and is_ticket_pass != '无' and ticket_info[ + 3] in self.station_trains and is_ticket_pass != '*': # 过滤有效目标车次 + secretStr = ticket_info[0] + train_no = ticket_info[2] + query_from_station_name = ticket_info[6] + query_to_station_name = ticket_info[7] + train_location = ticket_info[15] + stationTrainCode = ticket_info[3] + train_date = station_date + leftTicket = ticket_info[12] + set_type = self._station_seat[j] + print (u'车次: {0} 始发车站: {1} 终点站: {2} {3}: {4}'.format(train_no, + self.from_station_h, + self.to_station_h, + self._station_seat[j].encode( + "utf8"), + ticket_info[self.station_seat( + self._station_seat[ + j].encode("utf8"))] + )) + if "train_no" in self.ticket_black_list and ( + datetime.datetime.now() - self.ticket_black_list[ + train_no]).seconds / 60 < int(ticket.TICKET_BLACK_LIST_TIME): + print(ticket.QUERY_IN_BLACK_LIST.format(train_no)) + break + else: + print (ticket.QUERY_C) + # self.buy_ticket_time = datetime.datetime.now() + return { + "secretStr": secretStr, + "train_no": train_no, + "stationTrainCode": stationTrainCode, + "train_date": train_date, + "query_from_station_name": query_from_station_name, + "query_to_station_name": query_to_station_name, + # "buy_ticket_time": self.buy_ticket_time, + "set_type": set_type, + "leftTicket": leftTicket, + "train_location": train_location, + "code": ticket.SUCCESS_CODE, + "status": True, + } + else: + pass + else: + pass + else: + print u"车次配置信息有误,或者返回数据异常,请检查 {}".format(station_ticket) + return {"code": ticket.FAIL_CODE, "status": False} + + +if __name__ == "__main__": + q = query() diff --git a/inter/QueryOrderWaitTime.py b/inter/QueryOrderWaitTime.py new file mode 100644 index 0000000..b93b221 --- /dev/null +++ b/inter/QueryOrderWaitTime.py @@ -0,0 +1,123 @@ +# coding=utf-8 +import copy +import time + +from config.TicketEnmu import ticket +from config.emailConf import sendEmail +from myException.ticketIsExitsException import ticketIsExitsException +from myException.ticketNumOutException import ticketNumOutException + + +class queryOrderWaitTime: + """ + 排队 + """ + + def __init__(self, session): + self.session = session + + def sendQueryOrderWaitTime(self): + """ + 排队获取订单等待信息,每隔3秒请求一次,最高请求次数为20次! + :return: + """ + num = 1 + while True: + num += 1 + if num > ticket.OUT_NUM: + print(ticket.WAIT_OUT_NUM) + order_id = self.queryMyOrderNoComplete() # 排队失败,自动取消排队订单 + if order_id: + self.cancelNoCompleteMyOrder(order_id) + break + try: + queryOrderWaitTimeUrl = copy.deepcopy(self.session.urls["queryOrderWaitTimeUrl"]) + queryOrderWaitTimeUrl["req_url"] = queryOrderWaitTimeUrl["req_url"].format(int(round(time.time() * 1000))) + queryOrderWaitTimeResult = self.session.httpClint.send(queryOrderWaitTimeUrl) + except ValueError: + queryOrderWaitTimeResult = {} + if queryOrderWaitTimeResult: + if queryOrderWaitTimeResult.get("status", False): + data = queryOrderWaitTimeResult.get("data", False) + if data and data.get("orderId", ""): + sendEmail(ticket.WAIT_ORDER_SUCCESS.format( + data.get("orderId", ""))) + raise ticketIsExitsException(ticket.WAIT_ORDER_SUCCESS.format( + data.get("orderId"))) + elif data.get("msg", False): + print data.get("msg", "") + break + elif data.get("waitTime", False): + print(ticket.WAIT_ORDER_CONTINUE.format(0 - data.get("waitTime", False))) + else: + pass + elif queryOrderWaitTimeResult.get("messages", False): + print(ticket.WAIT_ORDER_FAIL.format(queryOrderWaitTimeResult.get("messages", ""))) + else: + print(ticket.WAIT_ORDER_NUM.format(num + 1)) + else: + pass + time.sleep(2) + else: + print(ticketNumOutException(ticket.WAIT_ORDER_SUB_FAIL)) + + def queryMyOrderNoComplete(self): + """ + 获取订单列表信息 + :return: + """ + self.initNoComplete() + queryMyOrderNoCompleteUrl = self.session.urls["queryMyOrderNoCompleteUrl"] + data = {"_json_att": ""} + try: + queryMyOrderNoCompleteResult = self.session.httpClint.send(queryMyOrderNoCompleteUrl, data) + except ValueError: + queryMyOrderNoCompleteResult = {} + if queryMyOrderNoCompleteResult: + if queryMyOrderNoCompleteResult.get("data", False) and queryMyOrderNoCompleteResult["data"].get("orderDBList", False): + orderId = queryMyOrderNoCompleteResult["data"]["orderDBList"][0]["sequence_no"] + return orderId + elif queryMyOrderNoCompleteResult.get("data", False) and queryMyOrderNoCompleteResult["data"].get("orderCacheDTO", False): + if queryMyOrderNoCompleteResult["data"]["orderCacheDTO"].get("message", False): + print(queryMyOrderNoCompleteResult["data"]["orderCacheDTO"]["message"]["message"]) + raise ticketNumOutException( + queryMyOrderNoCompleteResult["data"]["orderCacheDTO"]["message"]["message"]) + else: + if queryMyOrderNoCompleteResult.get("message", False): + print queryMyOrderNoCompleteResult.get("message", False) + return False + else: + return False + else: + print(u"接口 {} 无响应".format(queryMyOrderNoCompleteUrl)) + + def initNoComplete(self): + """ + 获取订单前需要进入订单列表页,获取订单列表页session + :return: + """ + self.session.httpClint.set_cookies(acw_tc="AQAAAEnFJnekLwwAtGHjZZCr79B6dpXk", current_captcha_type="Z") + initNoCompleteUrl = self.session.urls["initNoCompleteUrl"] + data = {"_json_att": ""} + self.session.httpClint.send(initNoCompleteUrl, data) + + def cancelNoCompleteMyOrder(self, sequence_no): + """ + 取消订单 + :param sequence_no: 订单编号 + :return: + """ + cancelNoCompleteMyOrderUrl = self.session.urls["cancelNoCompleteMyOrder"] + cancelNoCompleteMyOrderData = { + "sequence_no": sequence_no, + "cancel_flag": "cancel_order", + "_json_att": "" + } + cancelNoCompleteMyOrderResult = self.session.httpClint.send(cancelNoCompleteMyOrderUrl, + cancelNoCompleteMyOrderData) + if cancelNoCompleteMyOrderResult.get("data", False) and cancelNoCompleteMyOrderResult["data"].get("existError", "N"): + print(ticket.CANCEL_ORDER_SUCCESS.format(sequence_no)) + time.sleep(2) + return True + else: + print(ticket.CANCEL_ORDER_FAIL.format(sequence_no)) diff --git a/inter/__init__.py b/inter/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/myException/PassengerUserException.py b/myException/PassengerUserException.py old mode 100644 new mode 100755 diff --git a/myException/UserPasswordException.py b/myException/UserPasswordException.py old mode 100644 new mode 100755 diff --git a/myException/__init__.py b/myException/__init__.py old mode 100644 new mode 100755 diff --git a/myException/balanceException.py b/myException/balanceException.py old mode 100644 new mode 100755 diff --git a/myException/ticketConfigException.py b/myException/ticketConfigException.py old mode 100644 new mode 100755 diff --git a/myException/ticketIsExitsException.py b/myException/ticketIsExitsException.py old mode 100644 new mode 100755 diff --git a/myException/ticketNumOutException.py b/myException/ticketNumOutException.py old mode 100644 new mode 100755 diff --git a/myUrllib/__init__.py b/myUrllib/__init__.py old mode 100644 new mode 100755 diff --git a/myUrllib/httpUtils.py b/myUrllib/httpUtils.py old mode 100644 new mode 100755 index 0a4a2f4..a3c1ae3 --- a/myUrllib/httpUtils.py +++ b/myUrllib/httpUtils.py @@ -1,6 +1,8 @@ # -*- coding: utf8 -*- import json import socket +import urllib +from collections import OrderedDict from time import sleep import requests @@ -8,6 +10,51 @@ import requests from config import logger +def _set_header_default(): + header_dict = OrderedDict() + header_dict["Host"] = "kyfw.12306.cn" + header_dict["Connection"] = "keep-alive" + header_dict["Accept"] = "application/json, text/javascript, */*; q=0.01" + header_dict["Origin"] = "https://kyfw.12306.cn" + header_dict["X-Requested-With"] = "XMLHttpRequest" + header_dict[ + "User-Agent"] = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36" + header_dict["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8" + header_dict["Referer"] = "https://kyfw.12306.cn/otn/leftTicket/init" + header_dict["Accept-Encoding"] = "gzip, deflate, br" + header_dict["Accept-Language"] = "zh-CN,zh;q=0.9,en;q=0.8" + return header_dict + + +def _set_header_j(): + """设置header""" + return { + "Content-Type": "application/json; charset=UTF-8", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/604.3.5 (KHTML, like Gecko) Version/11.0.1 Safari/604.3.5", + "Accept": "application/json, text/javascript, */*; q=0.01", + "Referer": "https://kyfw.12306.cn/otn/leftTicket/init", + "Accept-Encoding": "gzip, deflate, br", + "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", + "Origin": "https://kyfw.12306.cn", + "Connection": "keep-alive", + } + + +def _set_header_x(): + """设置header""" + return { + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + "X-Requested-With": "XMLHttpRequest", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/604.3.5 (KHTML, like Gecko) Version/11.0.1 Safari/604.3.5", + "Accept": "application/json, text/javascript, */*; q=0.01", + "Referer": "https://kyfw.12306.cn/otn/leftTicket/init", + "Accept-Encoding": "gzip, deflate, br", + "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", + "Origin": "https://kyfw.12306.cn", + "Connection": "keep-alive", + } + + class HTTPClient(object): def __init__(self): @@ -20,7 +67,7 @@ class HTTPClient(object): def initS(self): self._s = requests.Session() - self._s.headers.update(self._set_header()) + self._s.headers.update(_set_header_j()) return self def set_cookies(self, **kwargs): @@ -46,27 +93,16 @@ class HTTPClient(object): """ self._s.cookies.set(key, None) - def _set_header(self): - """设置header""" - return { - "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", - "X-Requested-With": "application/json, text/javascript, */*; q=0.01", - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/604.3.5 (KHTML, like Gecko) Version/11.0.1 Safari/604.3.5", - "Referer": "https://kyfw.12306.cn/otn/login/init", - "Accept": "*/*", - "Accept-Encoding": "br, gzip, deflate", - "Origin": "https://kyfw.12306.cn", - "Accept-Language": "zh-cn", - "Connection": "keep-alive", - } - def setHeaders(self, headers): self._s.headers.update(headers) return self - def resetHeaders(self): + def resetHeaders(self, header_type): self._s.headers.clear() - self._s.headers.update(self._set_header()) + if header_type == 1: + self._s.headers.update(_set_header_x()) + else: + self._s.headers.update(_set_header_j()) def getHeadersHost(self): return self._s.headers["Host"] @@ -101,31 +137,43 @@ class HTTPClient(object): def send(self, urls, data=None, **kwargs): """send request to url.If response 200,return response, else return None.""" allow_redirects = False - is_logger = urls["is_logger"] + is_logger = urls.get("is_logger", False) + req_url = urls.get("req_url", "") + re_try = urls.get("re_try", 0) + s_time = urls.get("s_time", 0) + contentType = urls.get("Content-Type", 0) error_data = {"code": 99999, "message": u"重试次数达到上限"} - self.setHeadersReferer(urls["Referer"]) if data: method = "post" self.setHeaders({"Content-Length": "{0}".format(len(data))}) else: method = "get" - self.resetHeaders() + self.resetHeaders(contentType) + self.setHeadersReferer(urls["Referer"]) if is_logger: logger.log( - u"url: {0}\n入参: {1}\n请求方式: {2}\n".format(urls["req_url"],data,method,)) + u"url: {0}\n入参: {1}\n请求方式: {2}\n".format(req_url, data, method, )) self.setHeadersHost(urls["Host"]) if self.cdn: url_host = self.cdn else: url_host = urls["Host"] - for i in range(urls["re_try"]): + if contentType == 1: + # 普通from表单 + self.resetHeaders(contentType) + if method == "post": + pass + data = urllib.urlencode(data) + elif contentType == 0: + self.resetHeaders(contentType) + for i in range(re_try): try: # sleep(urls["s_time"]) if "s_time" in urls else sleep(0.001) - sleep(urls.get("s_time", 0.001)) + sleep(s_time) requests.packages.urllib3.disable_warnings() response = self._s.request(method=method, timeout=2, - url="https://" + url_host + urls["req_url"], + url="https://" + url_host + req_url, data=data, allow_redirects=allow_redirects, verify=False, @@ -135,7 +183,7 @@ class HTTPClient(object): if is_logger: logger.log( u"出参:{0}".format(response.content)) - return json.loads(response.content) if method == "post" else response.content + return json.loads(response.content) if urls["is_json"] else response.content else: logger.log( u"url: {} 返回参数为空".format(urls["req_url"])) diff --git a/myUrllib/myurllib2.py b/myUrllib/myurllib2.py old mode 100644 new mode 100755 diff --git a/requirements.txt b/requirements.txt old mode 100644 new mode 100755 diff --git a/run.py b/run.py old mode 100644 new mode 100755 index 69eb9dd..61d0f07 --- a/run.py +++ b/run.py @@ -1,10 +1,11 @@ # -*- coding=utf-8 -*- -from init import login, select_ticket_info +from init import login, select_ticket_info, SelectTicketInfoFast def run(): # login.main() - select_ticket_info.select().main() + SelectTicketInfoFast.selectFast().main() + # select_ticket_info.select().main() if __name__ == '__main__': diff --git a/station_name.txt b/station_name.txt old mode 100644 new mode 100755 diff --git a/tkcode b/tkcode old mode 100644 new mode 100755 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ce239c2885559c639f0c68d9f17fdb29dbab7cf4 GIT binary patch literal 12429 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 A8vp