File: run.py

# -*- coding=utf-8 -*-
import argparse
import sys


def parser_arguments(argv):
    """
    不应该在这里定义，先放在这里
    :param argv:
    :return:
    """
    parser = argparse.ArgumentParser()
    parser.add_argument("operate", type=str, help="r: 运行抢票程序, c: 过滤cdn, t: 测试邮箱和server酱，server酱需要打开开关")

    return parser.parse_args(argv)


if __name__ == '__main__':
    args = parser_arguments(sys.argv[1:])
    if args.operate == "r":
        from init import select_ticket_info
        select_ticket_info.select().main()
    elif args.operate == "t":
        from config.emailConf import sendEmail
        from config.serverchanConf import sendServerChan
        sendEmail(u"订票小助手测试一下")
        sendServerChan("订票小助手测试一下")
    elif args.operate == "c":
        from agency.cdn_utils import filterCdn
        filterCdn()




File: TickerConfig.py

# -*- coding=utf-8 -*-
# 关于软件使用配置说明，一定要看！！！
# ps: 如果是候补车票，需要通过人证一致性核验的用户及激活的“铁路畅行”会员可以提交候补需求，请您按照操作说明在铁路12306app.上完成人证核验
# 关于候补了之后是否还能继续捡漏的问题在此说明： 软件为全自动候补加捡漏，如果软件候补成功则会停止抢票，发出邮件通知，但是不会影响你继续捡漏，
# 如果这个时候捡漏捡到的话，也是可以付款成功的，也就是说，捡漏+候补，可以最大程度提升抢票成功率

# 刷票模式：1=刷票 2=候补+刷票
TICKET_TYPE = 1

# 出发日期(list) "2018-01-06", "2018-01-07"
STATION_DATES = [
    "2020-01-18"
]

# 填入需要购买的车次(list)，"G1353"
# 修改车次填入规则，注：(以前设置的车次逻辑不变)，如果车次填入为空，那么就是当日乘车所有车次都纳入筛选返回
# 不填车次是整个list为空才算，如果不是为空，依然会判断车次的，这种是错误的写法 [""], 正确的写法 []
STATION_TRAINS = []

# 出发城市，比如深圳北，就填深圳就搜得到
FROM_STATION = "广州南"

# 到达城市 比如深圳北，就填深圳就搜得到
TO_STATION = "隆回"

# 座位(list) 多个座位ex:
# "商务座",
# "一等座",
# "二等座",
# "特等座",
# "软卧",
# "硬卧",
# "硬座",
# "无座",
# "动卧",
SET_TYPE = ["二等座"]

# 当余票小于乘车人，如果选择优先提交，则删减联系人和余票数一致在提交
# bool
IS_MORE_TICKET = True

# 乘车人(list) 多个乘车人ex:
# "张三",
# "李四"
TICKET_PEOPLES = []

# 12306登录账号
USER = ""
PWD = ""

# 加入小黑屋时间默认为5分钟，此功能为了防止僵尸票导致一直下单不成功错过正常的票
TICKET_BLACK_LIST_TIME = 5

# 自动打码
IS_AUTO_CODE = True

# 设置2本地自动打码，需要配置tensorflow和keras库，3为云打码，由于云打码服务器资源有限(为2h4C的cpu服务器)，请不要恶意请求，不然只能关闭服务器
# ps: 请不要一直依赖云服务器资源，在此向所有提供服务器同学表示感谢
AUTO_CODE_TYPE = 3

# 此处设置云打码服务器地址，如果有自建的服务器，可以自行更改
HOST = "120.77.154.140:8000"
REQ_URL = "/verify/base64/"
HTTP_TYPE = "http"
# HOST="12306.yinaoxiong.cn" #备用服务器稳定性较差
# REQ_URL="/verify/base64/"
# HTTP_TYPE="https"

#  邮箱配置，如果抢票成功，将通过邮件配置通知给您
#  列举163
#  email: "xxx@163.com"
#  notice_email_list: "123@qq.com"
#  username: "xxxxx"
#  password: "xxxxx
#  host: "smtp.163.com"
#  列举qq  ，qq设置比较复杂，需要在邮箱-->账户-->开启smtp服务，取得授权码==邮箱登录密码
#  email: "xxx@qq.com"
#  notice_email_list: "123@qq.com"
#  username: "xxxxx"
#  password: "授权码"
#  host: "smtp.qq.com"
EMAIL_CONF = {
    "IS_MAIL": True,
    "email": "",
    "notice_email_list": "",
    "username": "",
    "password": "",
    "host": "smtp.qq.com",
}

# 是否开启 server酱 微信提醒， 使用前需要前往 http://sc.ftqq.com/3.version 扫码绑定获取 SECRET 并关注获得抢票结果通知的公众号
SERVER_CHAN_CONF = {
    "is_server_chan": False,
    "secret": ""
}

# 是否开启cdn查询，可以更快的检测票票 1为开启，2为关闭
IS_CDN = 1

# 下单接口分为两种，1 模拟网页自动捡漏下单（不稳定），2 模拟车次后面的购票按钮下单（稳如老狗）
ORDER_TYPE = 2

# 下单模式 1 为预售，整点刷新，刷新间隔0.1-0.5S, 然后会校验时间，比如12点的预售，那脚本就会在12.00整检票，刷新订单
#         2 是捡漏，捡漏的刷新间隔时间为0.5-3秒，时间间隔长，不容易封ip
ORDER_MODEL = 1

# 是否开启代理, 0代表关闭， 1表示开始
# 开启此功能的时候请确保代理ip是否可用，在测试放里面经过充分的测试，再开启此功能，不然可能会耽误你购票的宝贵时间
# 使用方法：
# 1、在agency/proxy_list列表下填入代理ip
# 2、测试UnitTest/TestAll/testProxy 测试代理是否可以用
# 3、开启代理ip
IS_PROXY = 0

# 预售放票时间, 如果是捡漏模式，可以忽略此操作
OPEN_TIME = "12:59:57"
# 1=使用selenium获取devicesID
# 2=使用网页端/otn/HttpZF/logdevice获取devicesId，这个接口的算法目前可能有点问题，如果登录一直302的请改为配置1
# 3=自己打开浏览器在headers-Cookies中抓取RAIL_DEVICEID和RAIL_EXPIRATION，这个就不用配置selenium
COOKIE_TYPE = 3
# 如果COOKIE_TYPE=1，则需配置chromeDriver路径,下载地址http://chromedriver.storage.googleapis.com/index.html
# chromedriver配置版本只要和chrome的大版本匹配就行
CHROME_PATH = "/usr/src/app/chromedriver"

# 为了docker37 准备的环境变量，windows环境可以不用管这个参数
CHROME_CHROME_PATH = "/opt/google/chrome/google-chrome"

# 如果COOKIE_TYPE=3, 则需配置RAIL_EXPIRATION、RAIL_DEVICEID的值
RAIL_EXPIRATION = ""
RAIL_DEVICEID = ""
# RAIL_EXPIRATION = "1577034103293"
# RAIL_DEVICEID = "CDno29Erc_Pf3FSXb4dzq-Op64EhWrsi5yUZKVIKR1MAfYo2qFlCeXD8VkexY7_1qg-ClV-fE8j9jgVlPZxRh3wVc2iqLe_5A8sdr62qZx4B22JPF8lFCjpgTKZ5ODW90HJd5tiQsJ1KR9nOqHRxHj1FT5LEIwfw"


# 1=>为一直随机ua,2->只启动的时候随机一次ua
RANDOM_AGENT = 2

PASSENGER_TICKER_STR = {
    '一等座': 'M',
    '特等座': 'P',
    '二等座': 'O',
    '商务座': 9,
    '硬座': 1,
    '无座': 1,
    '软座': 2,
    '软卧': 4,
    '硬卧': 3,
}

# 保护12306官网请求频率，设置随机请求时间，原则为5分钟不大于80次
# 最大间隔请求时间
MAX_TIME = 3
# 最小间隔请求时间
MIN_TIME = 1

# 软件版本
RE_VERSION = "1.2.004"



File: __init__.py




File: init/__init__.py




File: init/select_ticket_info.py

# -*- coding=utf-8 -*-
import datetime
import random
import os
import socket
import sys
import threading
import time
import TickerConfig
import wrapcache
from agency.cdn_utils import CDNProxy, open_cdn_file
from config import urlConf, configCommon
from config.TicketEnmu import ticket
from config.configCommon import seat_conf_2, seat_conf
from config.getCookie import getDrvicesID
from init.login import GoLogin
from inter.AutoSubmitOrderRequest import autoSubmitOrderRequest
from inter.ChechFace import chechFace
from inter.CheckUser import checkUser
from inter.GetPassengerDTOs import getPassengerDTOs
from inter.LiftTicketInit import liftTicketInit
from inter.Query import query
from inter.SubmitOrderRequest import submitOrderRequest
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


class select:
    """
    快速提交车票通道
    """
    def __init__(self):
        self.cdn_list = open_cdn_file("filter_cdn_list")
        self.get_ticket_info()
        self._station_seat = [seat_conf[x] for x in TickerConfig.SET_TYPE]
        self.auto_code_type = TickerConfig.AUTO_CODE_TYPE
        self.httpClint = HTTPClient(TickerConfig.IS_PROXY, self.cdn_list)
        self.httpClint.cdn = self.cdn_list[random.randint(0, 4)]
        self.urls = urlConf.urls
        self.login = GoLogin(self, TickerConfig.IS_AUTO_CODE, self.auto_code_type)
        self.cookies = ""
        self.queryUrl = "leftTicket/queryO"
        self.passengerTicketStrList = ""
        self.passengerTicketStrByAfterLate = ""
        self.oldPassengerStr = ""
        self.set_type = ""
        self.flag = True

    @staticmethod
    def get_ticket_info():
        """
        获取配置信息
        :return:
        """

        print(u"*" * 50)
        print(f"检查当前版本为: {TickerConfig.RE_VERSION}")
        version = sys.version.split(" ")[0]
        print(u"检查当前python版本为：{}，目前版本只支持3.6以上".format(version))
        if version < "3.6.0":
            raise Exception
        print(u"12306刷票小助手，最后更新于2019.09.18，请勿作为商业用途，交流群号："
              u" 1群：286271084(已满)\n"
              u" 2群：649992274(已满)\n"
              u" 3群：632501142(已满)\n"
              u" 4群: 606340519(已满)\n"
              u" 5群: 948526733(已满)\n"
              u" 7群: 660689659(已满)\n"
              u" 8群: 620629239(已满)\n"
              u" 6群: 608792930(未满)\n"
              u" 9群: 693035807(未满)\n"
              )
        print(
            f"当前配置：\n出发站：{TickerConfig.FROM_STATION}\n到达站：{TickerConfig.TO_STATION}\n车次: {','.join(TickerConfig.STATION_TRAINS) or '所有车次'}\n乘车日期：{','.join(TickerConfig.STATION_DATES)}\n坐席：{','.join(TickerConfig.SET_TYPE)}\n是否有票优先提交：{TickerConfig.IS_MORE_TICKET}\n乘车人：{TickerConfig.TICKET_PEOPLES}\n" \
            f"刷新间隔: 随机(1-3S)\n僵尸票关小黑屋时长: {TickerConfig.TICKET_BLACK_LIST_TIME}\n下单接口: {TickerConfig.ORDER_TYPE}\n下单模式: {TickerConfig.ORDER_MODEL}\n预售踩点时间:{TickerConfig.OPEN_TIME}")
        print(u"*" * 50)

    def station_table(self, from_station, to_station):
        """
        读取车站信息
        :param station:
        :return:
        """
        path = os.path.join(os.path.dirname(__file__), '../station_name.txt')
        try:
            with open(path, encoding="utf-8") as result:
                info = result.read().split('=')[1].strip("'").split('@')
        except Exception:
            with open(path) as result:
                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]
        try:
            from_station = station_name[from_station.encode("utf8")]
            to_station = station_name[to_station.encode("utf8")]
        except KeyError:
            from_station = station_name[from_station]
            to_station = station_name[to_station]
        return from_station, to_station

    def call_login(self, auth=False):
        """
        登录回调方法
        :return:
        """
        if auth:
            return self.login.auth()
        else:
            configCommon.checkSleepTime(self)  # 防止网上启动晚上到点休眠
            self.login.go_login()

    def main(self):
        l = liftTicketInit(self)
        l.reqLiftTicketInit()
        getDrvicesID(self)
        self.call_login()
        check_user = checkUser(self)
        t = threading.Thread(target=check_user.sendCheckUser)
        t.setDaemon(True)
        t.start()
        from_station, to_station = self.station_table(TickerConfig.FROM_STATION, TickerConfig.TO_STATION)
        num = 0
        s = getPassengerDTOs(selectObj=self, ticket_peoples=TickerConfig.TICKET_PEOPLES)
        passenger = s.sendGetPassengerDTOs()
        wrapcache.set("user_info", passenger, timeout=9999999)

        now = datetime.datetime.now()
        if TickerConfig.ORDER_MODEL is 1:
            print(f"预售还未开始，阻塞中，预售时间为{TickerConfig.OPEN_TIME}, 当前时间为: {now.strftime('%H:%M:%S')}")
            sleep_time_s = 0.1
            sleep_time_t = 0.3
            # 测试了一下有微妙级的误差，应该不影响，测试结果：2019-01-02 22:30:00.004555，预售还是会受到前一次刷新的时间影响，暂时没想到好的解决方案
            while now.strftime("%H:%M:%S") < TickerConfig.OPEN_TIME:
                now = datetime.datetime.now()
                time.sleep(0.0001)
            print(f"预售开始，开启时间为: {now.strftime('%H:%M:%S')}")
        else:
            sleep_time_s = TickerConfig.MIN_TIME
            sleep_time_t = TickerConfig.MAX_TIME

        while 1:
            try:
                num += 1
                now = datetime.datetime.now()  # 感谢群里大佬提供整点代码
                configCommon.checkSleepTime(self)  # 晚上到点休眠
                q = query(selectObj=self,
                          from_station=from_station,
                          to_station=to_station,
                          from_station_h=TickerConfig.FROM_STATION,
                          to_station_h=TickerConfig.TO_STATION,
                          _station_seat=self._station_seat,
                          station_trains=TickerConfig.STATION_TRAINS,
                          station_dates=TickerConfig.STATION_DATES,
                          ticke_peoples_num=len(TickerConfig.TICKET_PEOPLES),
                          )
                queryResult = q.sendQuery()
                # 查询接口
                if queryResult.get("status"):
                    train_no = queryResult.get("train_no", "")
                    train_date = queryResult.get("train_date", "")
                    stationTrainCode = queryResult.get("stationTrainCode", "")
                    secretStr = queryResult.get("secretStr", "")
                    secretList = queryResult.get("secretList", "")
                    seat = queryResult.get("seat", "")
                    leftTicket = queryResult.get("leftTicket", "")
                    query_from_station_name = queryResult.get("query_from_station_name", "")
                    query_to_station_name = queryResult.get("query_to_station_name", "")
                    is_more_ticket_num = queryResult.get("is_more_ticket_num", len(TickerConfig.TICKET_PEOPLES))
                    if wrapcache.get(train_no):
                        print(ticket.QUEUE_WARNING_MSG.format(train_no))
                    else:
                        # 获取联系人
                        s = getPassengerDTOs(selectObj=self, ticket_peoples=TickerConfig.TICKET_PEOPLES,
                                             set_type="" if isinstance(seat, list) else seat_conf_2[seat],
                                             # 候补订单需要设置多个坐席
                                             is_more_ticket_num=is_more_ticket_num)
                        getPassengerDTOsResult = s.getPassengerTicketStrListAndOldPassengerStr(secretStr, secretList)
                        if getPassengerDTOsResult.get("status", False):
                            self.passengerTicketStrList = getPassengerDTOsResult.get("passengerTicketStrList", "")
                            self.passengerTicketStrByAfterLate = getPassengerDTOsResult.get(
                                "passengerTicketStrByAfterLate", "")
                            self.oldPassengerStr = getPassengerDTOsResult.get("oldPassengerStr", "")
                            self.set_type = getPassengerDTOsResult.get("set_type", "")
                        # 提交订单
                        # 订单分为两种，一种为抢单，一种为候补订单
                        if secretStr:  # 正常下单
                            if TickerConfig.ORDER_TYPE == 1:  # 快速下单
                                a = autoSubmitOrderRequest(selectObj=self,
                                                           secretStr=secretStr,
                                                           train_date=train_date,
                                                           passengerTicketStr=self.passengerTicketStrList,
                                                           oldPassengerStr=self.oldPassengerStr,
                                                           train_no=train_no,
                                                           stationTrainCode=stationTrainCode,
                                                           leftTicket=leftTicket,
                                                           set_type=self.set_type,
                                                           query_from_station_name=query_from_station_name,
                                                           query_to_station_name=query_to_station_name,
                                                           )
                                a.sendAutoSubmitOrderRequest()
                            elif TickerConfig.ORDER_TYPE == 2:  # 普通下单
                                sor = submitOrderRequest(self, secretStr, from_station, to_station, train_no,
                                                         self.set_type,
                                                         self.passengerTicketStrList, self.oldPassengerStr, train_date,
                                                         TickerConfig.TICKET_PEOPLES)
                                sor.sendSubmitOrderRequest()
                        elif secretList:  # 候补订单
                            c = chechFace(self, secretList, train_no)
                            c.sendChechFace()
                else:
                    random_time = round(random.uniform(sleep_time_s, sleep_time_t), 2)
                    nateMsg = ' 无候补机会' if TickerConfig.ORDER_TYPE == 2 else ""
                    print(f"正在第{num}次查询 停留时间：{random_time} 乘车日期: {','.join(TickerConfig.STATION_DATES)} 车次：{','.join(TickerConfig.STATION_TRAINS) or '所有车次'} 下单无票{nateMsg} 耗时：{(datetime.datetime.now() - now).microseconds / 1000} {queryResult.get('cdn')}")
                    time.sleep(random_time)
            except PassengerUserException as e:
                print(e)
                break
            except ticketConfigException as e:
                print(e)
                break
            except ticketIsExitsException as e:
                print(e)
                break
            except ticketNumOutException as e:
                print(e)
                break
            except UserPasswordException as e:
                print(e)
                break
            except ValueError as e:
                if e == "No JSON object could be decoded":
                    print(u"12306接口无响应，正在重试")
                else:
                    print(e)
            except KeyError as e:
                print(e)
            except TypeError as e:
                print(u"12306接口无响应，正在重试 {0}".format(e))
            except socket.error as e:
                print(e)


if __name__ == '__main__':
    s = select()
    cdn = s.station_table("长沙", "深圳")



File: init/login.py

# -*- coding=utf-8 -*-
import copy
import time
from collections import OrderedDict
from time import sleep
import TickerConfig
from inter.GetPassCodeNewOrderAndLogin import getPassCodeNewOrderAndLogin1
from inter.GetRandCode import getRandCode
from inter.LoginAysnSuggest import loginAysnSuggest
from inter.LoginConf import loginConf
from myException.UserPasswordException import UserPasswordException


class GoLogin:
    def __init__(self, session, is_auto_code, auto_code_type):
        self.session = session
        self.randCode = ""
        self.is_auto_code = is_auto_code
        self.auto_code_type = auto_code_type

    def auth(self):
        """
        :return:
        """
        self.session.httpClint.send(self.session.urls["loginInitCdn1"])
        uamtkStaticUrl = self.session.urls["uamtk-static"]
        uamtkStaticData = {"appid": "otn"}
        return self.session.httpClint.send(uamtkStaticUrl, uamtkStaticData)

    def codeCheck(self):
        """
        验证码校验
        :return:
        """
        codeCheckUrl = copy.deepcopy(self.session.urls["codeCheck1"])
        codeCheckUrl["req_url"] = codeCheckUrl["req_url"].format(self.randCode, int(time.time() * 1000))
        fresult = self.session.httpClint.send(codeCheckUrl)
        if not isinstance(fresult, str):
            print("登录失败")
            return
        fresult = eval(fresult.split("(")[1].split(")")[0])
        if "result_code" in fresult and fresult["result_code"] == "4":
            print(u"验证码通过,开始登录..")
            return True
        else:
            if "result_message" in fresult:
                print(fresult["result_message"])
            sleep(1)
            self.session.httpClint.del_cookies()

    def baseLogin(self, user, passwd):
        """
        登录过程
        :param user:
        :param passwd:
        :return: 权限校验码
        """
        logurl = self.session.urls["login"]

        loginData = OrderedDict()
        loginData["username"] = user,
        loginData["password"] = passwd,
        loginData["appid"] = "otn",
        loginData["answer"] = self.randCode,

        tresult = self.session.httpClint.send(logurl, loginData)
        if 'result_code' in tresult and tresult["result_code"] == 0:
            print(u"登录成功")
            tk = self.auth()
            if "newapptk" in tk and tk["newapptk"]:
                return tk["newapptk"]
            else:
                return False
        elif 'result_message' in tresult and tresult['result_message']:
            messages = tresult['result_message']
            if messages.find(u"密码输入错误") is not -1:
                raise UserPasswordException("{0}".format(messages))
            else:
                print(u"登录失败: {0}".format(messages))
                print(u"尝试重新登陆")
                return False
        else:
            return False

    def getUserName(self, uamtk):
        """
        登录成功后,显示用户名
        :return:
        """
        if not uamtk:
            return u"权限校验码不能为空"
        else:
            uamauthclientUrl = self.session.urls["uamauthclient"]
            data = {"tk": uamtk}
            uamauthclientResult = self.session.httpClint.send(uamauthclientUrl, data)
            if uamauthclientResult:
                if "result_code" in uamauthclientResult and uamauthclientResult["result_code"] == 0:
                    print(u"欢迎 {} 登录".format(uamauthclientResult["username"]))
                    return True
                else:
                    return False
            else:
                self.session.httpClint.send(uamauthclientUrl, data)
                url = self.session.urls["getUserInfo"]
                self.session.httpClint.send(url)

    def go_login(self):
        """
        登陆
        :param user: 账户名
        :param passwd: 密码
        :return:
        """
        user, passwd = TickerConfig.USER, TickerConfig.PWD
        if not user or not passwd:
            raise UserPasswordException(u"温馨提示: 用户名或者密码为空，请仔细检查")
        login_num = 0
        while True:
            if loginConf(self.session):

                result = getPassCodeNewOrderAndLogin1(session=self.session, imgType="login")
                if not result:
                    continue
                self.randCode = getRandCode(self.is_auto_code, self.auto_code_type, result)
                print(self.randCode)
                login_num += 1
                self.auth()
                if self.codeCheck():
                    uamtk = self.baseLogin(user, passwd)
                    if uamtk:
                        self.getUserName(uamtk)
                        break
            else:
                loginAysnSuggest(self.session, username=user, password=passwd)
                login_num += 1
                break


File: verify/mlearn_for_image.py

# coding: utf-8
import TickerConfig
if TickerConfig.AUTO_CODE_TYPE == 2:
    import sys

    import cv2
    import numpy as np
    from keras import models
    from keras import layers
    from keras import optimizers
    from keras.applications import VGG16
    from keras.callbacks import ReduceLROnPlateau
    from keras.preprocessing.image import ImageDataGenerator


def preprocess_input(x):
    x = x.astype('float32')
    # 我是用cv2来读取的图片，其已经是BGR格式了
    mean = [103.939, 116.779, 123.68]
    x -= mean
    return x


def load_data():
    # 这是统计学专家提供的训练集
    data = np.load('captcha.npz')
    train_x, train_y = data['images'], data['labels']
    train_x = preprocess_input(train_x)
    # 由于是统计得来的信息，所以在此给定可信度
    sample_weight = train_y.max(axis=1) / np.sqrt(train_y.sum(axis=1))
    sample_weight /= sample_weight.mean()
    train_y = train_y.argmax(axis=1)

    # 这是人工提供的验证集
    data = np.load('captcha.test.npz')
    test_x, test_y = data['images'], data['labels']
    test_x = preprocess_input(test_x)
    return (train_x, train_y, sample_weight), (test_x, test_y)


def learn():
    (train_x, train_y, sample_weight), (test_x, test_y) = load_data()
    datagen = ImageDataGenerator(horizontal_flip=True,
                                 vertical_flip=True)
    train_generator = datagen.flow(train_x, train_y, sample_weight=sample_weight)
    base = VGG16(weights='imagenet', include_top=False, input_shape=(None, None, 3))
    for layer in base.layers[:-4]:
        layer.trainable = False
    model = models.Sequential([
        base,
        layers.BatchNormalization(),
        layers.Conv2D(64, (3, 3), activation='relu', padding='same'),
        layers.GlobalAveragePooling2D(),
        layers.BatchNormalization(),
        layers.Dense(64, activation='relu'),
        layers.BatchNormalization(),
        layers.Dropout(0.20),
        layers.Dense(80, activation='softmax')
    ])
    model.compile(optimizer=optimizers.RMSprop(lr=1e-5),
                  loss='sparse_categorical_crossentropy',
                  metrics=['accuracy'])
    model.summary()
    reduce_lr = ReduceLROnPlateau(verbose=1)
    model.fit_generator(train_generator, epochs=400,
                        steps_per_epoch=100,
                        validation_data=(test_x[:800], test_y[:800]),
                        callbacks=[reduce_lr])
    result = model.evaluate(test_x, test_y)
    print(result)
    model.save('12306.image.model.h5', include_optimizer=False)


def predict(imgs):
    imgs = preprocess_input(imgs)
    model = models.load_model('12306.image.model.h5')
    labels = model.predict(imgs)
    return labels


def _predict(fn):
    imgs = cv2.imread(fn)
    imgs = cv2.resize(imgs, (67, 67))
    imgs.shape = (-1, 67, 67, 3)
    labels = predict(imgs)
    print(labels.max(axis=1))
    print(labels.argmax(axis=1))


if __name__ == '__main__':
    if len(sys.argv) >= 2:
        _predict(sys.argv[1])
    else:
        learn()



File: verify/pretreatment.py

#! env python
# coding: utf-8
# 功能：对图像进行预处理，将文字部分单独提取出来
# 并存放到ocr目录下
# 文件名为原验证码文件的文件名
import TickerConfig
if TickerConfig.AUTO_CODE_TYPE == 2:
    import hashlib
    import os
    import pathlib

    import cv2
    import numpy as np
    import requests
    import scipy.fftpack


PATH = 'imgs'


def download_image():
    # 抓取验证码
    # 存放到指定path下
    # 文件名为图像的MD5
    url = 'https://kyfw.12306.cn/otn/passcodeNew/getPassCodeNew?module=login&rand=sjrand'
    r = requests.get(url)
    fn = hashlib.md5(r.content).hexdigest()
    with open(f'{PATH}/{fn}.jpg', 'wb') as fp:
        fp.write(r.content)


def download_images():
    pathlib.Path(PATH).mkdir(exist_ok=True)
    for idx in range(40000):
        download_image()
        print(idx)


def get_text(img, offset=0):
    # 得到图像中的文本部分
    return img[3:22, 120 + offset:177 + offset]


def avhash(im):
    im = cv2.resize(im, (8, 8), interpolation=cv2.INTER_CUBIC)
    avg = im.mean()
    im = im > avg
    im = np.packbits(im)
    return im


def phash(im):
    im = cv2.resize(im, (32, 32), interpolation=cv2.INTER_CUBIC)
    im = scipy.fftpack.dct(scipy.fftpack.dct(im, axis=0), axis=1)
    im = im[:8, :8]
    med = np.median(im)
    im = im > med
    im = np.packbits(im)
    return im


def _get_imgs(img):
    interval = 5
    length = 67
    for x in range(40, img.shape[0] - length, interval + length):
        for y in range(interval, img.shape[1] - length, interval + length):
            yield img[x:x + length, y:y + length]


def get_imgs(img):
    imgs = []
    for img in _get_imgs(img):
        imgs.append(phash(img))
    return imgs


def pretreat():
    if not os.path.isdir(PATH):
        download_images()
    texts, imgs = [], []
    for img in os.listdir(PATH):
        img = os.path.join(PATH, img)
        img = cv2.imread(img, cv2.IMREAD_GRAYSCALE)
        texts.append(get_text(img))
        imgs.append(get_imgs(img))
    return texts, imgs


def load_data(path='data.npz'):
    if not os.path.isfile(path):
        texts, imgs = pretreat()
        np.savez(path, texts=texts, images=imgs)
    f = np.load(path)
    return f['texts'], f['images']


if __name__ == '__main__':
    texts, imgs = load_data()
    print(texts.shape)
    print(imgs.shape)
    imgs = imgs.reshape(-1, 8)
    print(np.unique(imgs, axis=0).shape)



File: verify/__init__.py




File: verify/localVerifyCode.py

# coding: utf-8
import TickerConfig

if TickerConfig.AUTO_CODE_TYPE == 2:
    import base64
    import os
    import cv2
    import numpy as np
    from keras import models, backend
    import tensorflow as tf
    from verify import pretreatment
    from verify.mlearn_for_image import preprocess_input

    graph = tf.get_default_graph()

PATH = lambda p: os.path.abspath(
    os.path.join(os.path.dirname(__file__), p)
)


TEXT_MODEL = ""
IMG_MODEL = ""


def get_text(img, offset=0):
    text = pretreatment.get_text(img, offset)
    text = cv2.cvtColor(text, cv2.COLOR_BGR2GRAY)
    text = text / 255.0
    h, w = text.shape
    text.shape = (1, h, w, 1)
    return text


def base64_to_image(base64_code):
    # base64解码
    img_data = base64.b64decode(base64_code)
    # 转换为np数组
    img_array = np.fromstring(img_data, np.uint8)
    # 转换成opencv可用格式
    img = cv2.imdecode(img_array, cv2.COLOR_RGB2BGR)

    return img


class Verify:
    def __init__(self):
        self.textModel = ""
        self.imgModel = ""
        self.loadImgModel()
        self.loadTextModel()

    def loadTextModel(self):
        if not self.textModel:
            self.textModel = models.load_model(PATH('../model.v2.0.h5'))
        else:
            print("无需加载模型model.v2.0.h5")

    def loadImgModel(self):
        if not self.imgModel:
            self.imgModel = models.load_model(PATH('../12306.image.model.h5'))

    def verify(self, fn):
        verify_titles = ['打字机', '调色板', '跑步机', '毛线', '老虎', '安全帽', '沙包', '盘子', '本子', '药片', '双面胶', '龙舟', '红酒', '拖把', '卷尺',
                         '海苔', '红豆', '黑板', '热水袋', '烛台', '钟表', '路灯', '沙拉', '海报', '公交卡', '樱桃', '创可贴', '牌坊', '苍蝇拍', '高压锅',
                         '电线', '网球拍', '海鸥', '风铃', '订书机', '冰箱', '话梅', '排风机', '锅铲', '绿豆', '航母', '电子秤', '红枣', '金字塔', '鞭炮',
                         '菠萝', '开瓶器', '电饭煲', '仪表盘', '棉棒', '篮球', '狮子', '蚂蚁', '蜡烛', '茶盅', '印章', '茶几', '啤酒', '档案袋', '挂钟', '刺绣',
                         '铃铛', '护腕', '手掌印', '锦旗', '文具盒', '辣椒酱', '耳塞', '中国结', '蜥蜴', '剪纸', '漏斗', '锣', '蒸笼', '珊瑚', '雨靴', '薯条',
                         '蜜蜂', '日历', '口哨']
        # 读取并预处理验证码
        img = base64_to_image(fn)
        text = get_text(img)
        imgs = np.array(list(pretreatment._get_imgs(img)))
        imgs = preprocess_input(imgs)
        text_list = []
        # 识别文字
        self.loadTextModel()
        global graph
        with graph.as_default():
            label = self.textModel.predict(text)
        label = label.argmax()
        text = verify_titles[label]
        text_list.append(text)
        # 获取下一个词
        # 根据第一个词的长度来定位第二个词的位置
        if len(text) == 1:
            offset = 27
        elif len(text) == 2:
            offset = 47
        else:
            offset = 60
        text = get_text(img, offset=offset)
        if text.mean() < 0.95:
            with graph.as_default():
                label = self.textModel.predict(text)
            label = label.argmax()
            text = verify_titles[label]
            text_list.append(text)
        print("题目为{}".format(text_list))
        # 加载图片分类器
        self.loadImgModel()
        with graph.as_default():
            labels = self.imgModel.predict(imgs)
        labels = labels.argmax(axis=1)
        results = []
        for pos, label in enumerate(labels):
            l = verify_titles[label]
            print(pos + 1, l)
            if l in text_list:
                results.append(str(pos + 1))
        return results


if __name__ == '__main__':
    pass
    # verify("verify-img1.jpeg")



File: config/emailConf.py

# -*- coding: utf8 -*-
import socket
__author__ = 'MR.wen'
import TickerConfig
from email.header import Header
from email.mime.text import MIMEText
import smtplib


def sendEmail(msg):
    """
    邮件通知
    :param str: email content
    :return:
    """
    try:
        if TickerConfig.EMAIL_CONF["IS_MAIL"]:
            sender = TickerConfig.EMAIL_CONF["email"]
            receiver = TickerConfig.EMAIL_CONF["notice_email_list"]
            subject = '恭喜，您已订票成功'
            username = TickerConfig.EMAIL_CONF["username"]
            password = TickerConfig.EMAIL_CONF["password"]
            host = TickerConfig.EMAIL_CONF["host"]
            s = "{0}".format(msg)

            msg = MIMEText(s, 'plain', 'utf-8')  # 中文需参数‘utf-8’，单字节字符不需要
            msg['Subject'] = Header(subject, 'utf-8')
            msg['From'] = sender
            msg['To'] = receiver

            try:
                smtp = smtplib.SMTP_SSL(host)
                smtp.connect(host)
            except socket.error:
                smtp = smtplib.SMTP()
                smtp.connect(host)
            smtp.connect(host)
            smtp.login(username, password)
            smtp.sendmail(sender, receiver.split(","), msg.as_string())
            smtp.quit()
            print(u"邮件已通知, 请查收")
    except Exception as e:
        print(u"邮件配置有误{}".format(e))


if __name__ == '__main__':
    sendEmail(1)


File: config/pushbearConf.py

# -*- coding: utf8 -*-
import TickerConfig
from config.urlConf import urls
from myUrllib.httpUtils import HTTPClient

PUSH_BEAR_API_PATH = "https://pushbear.ftqq.com/sub"


def sendPushBear(msg):
    """
    pushBear微信通知
    :param str: 通知内容 content
    :return:
    """
    if TickerConfig.PUSHBEAR_CONF["is_pushbear"] and TickerConfig.PUSHBEAR_CONF["send_key"].strip() != "":
        try:
            sendPushBearUrls = urls.get("Pushbear")
            data = {
                "sendkey": TickerConfig.PUSHBEAR_CONF["send_key"].strip(),
                "text": "易行购票成功通知",
                "desp": msg
            }
            httpClint = HTTPClient(0)
            sendPushBeaRsp = httpClint.send(sendPushBearUrls, data=data)
            if sendPushBeaRsp.get("code") is 0:
                print(u"已下发 pushbear 微信通知, 请查收")
            else:
                print(sendPushBeaRsp)
        except Exception as e:
            print(u"pushbear 配置有误 {}".format(e))
    else:
        pass


if __name__ == '__main__':
    sendPushBear(1)



File: config/__init__.py




File: config/logger.py

#coding: utf-8

import os
import time
import logging

from config import configCommon

logger = None
loggerHandler = None
dateStr = '' #默认拥有日期后缀
suffix = '' #除了日期外的后缀

def setSuffix(s):
	global suffix
	suffix = s

def getTodayDateStr():
	return time.strftime("%Y-%m-%d", time.localtime(configCommon.getNowTimestamp()))

def setDateStr(s):
	global dateStr
	dateStr = s

def isAnotherDay(s):
	global dateStr
	return dateStr != s

def getLogFile():
	global dateStr, suffix
	rtn = os.path.join(configCommon.getLogDir(), dateStr)
	if suffix:
		rtn += "_" + suffix
	return rtn + ".log"

def log(msg, func = "info"):
	global logger
	if not logger:
		logger = logging.getLogger()
		logger.setLevel(logging.INFO)

	todayStr = getTodayDateStr()
	if isAnotherDay(todayStr):
		setDateStr(todayStr)
		logger.removeHandler(loggerHandler)
		
		fh = logging.FileHandler(getLogFile())
		fm = logging.Formatter(u'[%(asctime)s][%(levelname)8s] --- %(message)s (%(filename)s:%(lineno)s)')
		fh.setFormatter(fm)

		logger.addHandler(fh)

	levels = {
		"debug": logger.debug,
		"info": logger.info,
		"warning": logger.warning,
		"error": logger.error,
		"critical": logger.critical
	}

	levels[func](msg)


File: config/configCommon.py

# -*- coding: utf-8 -*-
import datetime
import os
import random
import sys
import time

from myException.ticketConfigException import ticketConfigException

rushRefreshMinTimeIntval = 2000
rushRefreshMaxTimeIntval = 3600000
rushRefreshTimeIntval = 100
# 最早运行时间
maxRunTime = 6
# 程序停止时间
maxRunStopTime = 23
# 可售天数
maxDate = 29

RS_SUC = 0
RS_TIMEOUT = 1
RS_JSON_ERROR = 2
RS_OTHER_ERROR = 3

seat_conf = {'商务座': 32,
             '一等座': 31,
             '二等座': 30,
             '特等座': 25,
             '软卧': 23,
             '硬卧': 28,
             '软座': 24,
             '硬座': 29,
             '无座': 26,
             '动卧': 33,
             }
if sys.version_info.major == 2:
    seat_conf_2 = dict([(v, k) for (k, v) in seat_conf.iteritems()])
else:
    seat_conf_2 = dict([(v, k) for (k, v) in seat_conf.items()])


def getNowTimestamp():
    return time.time()


def decMakeDir(func):
    def handleFunc(*args, **kwargs):
        dirname = func(*args, **kwargs)
        if not os.path.exists(dirname):
            os.makedirs(dirname)
        elif not os.path.isdir(dirname):
            pass

        return dirname

    return func


def getWorkDir():
    return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

#
# def fileOpen(path):
#     """
#     文件读取兼容2和3
#     :param path: 文件读取路径
#     :return:
#     """
#     try:
#         with open(path, "r", ) as f:
#             return f
#     except TypeError:
#         with open(path, "r", ) as f:
#             return f



@decMakeDir
def getTmpDir():
    return os.path.join(getWorkDir(), "tmp")


@decMakeDir
def getLogDir():
    return os.path.join(getTmpDir(), "log")


@decMakeDir
def getCacheDir():
    return os.path.join(getTmpDir(), "cache")


@decMakeDir
def getVCodeDir():
    return os.path.join(getTmpDir(), "vcode")


def getVCodeImageFile(imageName):
    return os.path.join(getVCodeDir(), imageName + ".jpg")


def getCacheFile(cacheType):
    return os.path.join(getCacheDir(), cacheType + ".cache")


def checkSleepTime(session):
    now = datetime.datetime.now()
    if now.hour >= maxRunStopTime or now.hour < maxRunTime:
        print(u"12306休息时间，本程序自动停止,明天早上六点将自动运行")
        open_time = datetime.datetime(now.year, now.month, now.day, maxRunTime)
        if open_time < now:
            open_time += datetime.timedelta(1)
        time.sleep((open_time - now).seconds + round(random.uniform(1, 10)))
        session.call_login()


def checkDate(station_dates):
    """
    检查日期是否合法
    :param station_dates:
    :return:
    """
    today = datetime.datetime.now()
    maxDay = (today + datetime.timedelta(maxDate)).strftime("%Y-%m-%d")
    for station_date in station_dates[::-1]:
        date = datetime.datetime.strftime(datetime.datetime.strptime(station_date, "%Y-%m-%d"), "%Y-%m-%d")
        if date < today.strftime("%Y-%m-%d") or date > maxDay:
            print(u"警告：当前时间配置有小于当前时间或者大于最大时间: {}, 已自动忽略".format(station_date))
            station_dates.remove(station_date)
            if not station_dates:
                print(u"当前日期设置无符合查询条件的，已被全部删除，请查证后添加!!!")
                raise ticketConfigException(u"当前日期设置无符合查询条件的，已被全部删除，请查证后添加!!!")
        else:
            station_dates[station_dates.index(station_date)] = date
    return station_dates


File: config/AutoSynchroTime.py

# coding=utf-8
import os
import platform

import ntplib
import datetime


def autoSynchroTime():
    """
    同步北京时间，执行时候，请务必用sudo，sudo，sudo 执行，否则会报权限错误，windows打开ide或者cmd请用管理员身份
    :return:
    """
    c = ntplib.NTPClient()

    hosts = ['ntp1.aliyun.com', 'ntp2.aliyun.com', 'ntp3.aliyun.com', 'ntp4.aliyun.com', 'cn.pool.ntp.org']

    print(u"正在同步时间，请耐心等待30秒左右，如果下面有错误发送，可以忽略！！")
    print(u"系统当前时间{}".format(str(datetime.datetime.now())[:22]))
    system = platform.system()
    if system == "Windows":  # windows 同步时间未测试过，参考地址：https://www.jianshu.com/p/92ec15da6cc3
        for host in hosts:
            os.popen('w32tm /register')
            os.popen('net start w32time')
            os.popen('w32tm /config /manualpeerlist:"{}" /syncfromflags:manual /reliable:yes /update'.format(host))
            os.popen('ping -n 3 127.0.0.1 >nul')
            sin = os.popen('w32tm /resync')
            if sin is 0:
                break
    else:  # mac同步地址，如果ntpdate未安装，brew install ntpdate    linux 安装 yum install -y ntpdate
        for host in hosts:
            sin = os.popen('ntpdate {}'.format(host))
            if sin is 0:
                break
    print(u"同步后时间:{}".format(str(datetime.datetime.now())[:22]))


if __name__ == '__main__':
    autoSynchroTime()


File: config/getCookie.py

import json
import random
import re
import time
import os
import TickerConfig
from config.urlConf import urls


def getDrvicesID(session):
    """
    :return:
    """
    print("cookie获取中")
    if TickerConfig.COOKIE_TYPE is 1:
        from selenium import webdriver
        cookies = []
        # 解决放镜像里 DevToolsActivePort file doesn't exist的问题
        options = webdriver.ChromeOptions()
        if os.name != 'nt' and TickerConfig.CHROME_CHROME_PATH:
            options = webdriver.ChromeOptions()
            options.binary_location = TickerConfig.CHROME_CHROME_PATH
            options.add_argument(
                '--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36')
            options.add_argument("--no-sandbox")
            options.add_argument("--headless")
        driver = webdriver.Chrome(executable_path=TickerConfig.CHROME_PATH,chrome_options=options)
        driver.get("https://www.12306.cn/index/index.html")
        time.sleep(10)
        for c in driver.get_cookies():
            cookie = dict()
            print()
            if c.get("name") == "RAIL_DEVICEID" or c.get("name") == "RAIL_EXPIRATION":
                cookie[c.get("name")] = c.get("value")
                cookies.append(cookie)
        print(f"获取cookie: {cookies}")
        if cookies:
            session.httpClint.set_cookies(cookies)
            session.cookies = cookies
        print("cookie获取完成")
    elif TickerConfig.COOKIE_TYPE is 2:
        request_device_id(session)
    elif TickerConfig.COOKIE_TYPE is 3:
        # RAIL_DEVICEID,RAIL_EXPIRATION的值打开12306官网可以获取headers-Cookies
        if not TickerConfig.RAIL_DEVICEID or not TickerConfig.RAIL_EXPIRATION:
            print("警告！！: RAIL_DEVICEID,RAIL_EXPIRATION的值为空，请手动打开12306官网可以获取headers-Cookies中的RAIL_DEVICEID,RAIL_EXPIRATION，填入配置文件中")
        cookies = [{
            "RAIL_DEVICEID": TickerConfig.RAIL_DEVICEID,
            "RAIL_EXPIRATION": TickerConfig.RAIL_EXPIRATION,
        }]
        session.httpClint.set_cookies(cookies)
        session.cookies = cookies


def request_device_id(session):
    """
    获取加密后的浏览器特征 ID
    :return:
    """
    params = {"algID": request_alg_id(session), "timestamp": int(time.time() * 1000)}
    params = dict(params, **_get_hash_code_params())
    response = session.httpClint.send(urls.get("getDevicesId"), params=params)
    if response.find('callbackFunction') >= 0:
        result = response[18:-2]
        try:
            result = json.loads(result)
            session.httpClint.set_cookies([{
                'RAIL_EXPIRATION': result.get('exp'),
                'RAIL_DEVICEID': result.get('dfp'),
            }])
            session.cookies = [{
                'RAIL_EXPIRATION': result.get('exp'),
                'RAIL_DEVICEID': result.get('dfp'),
            }]
        except:
            return False


def request_alg_id(session):
    response = session.httpClint.send(urls.get("GetJS"))
    result = re.search(r'algID\\x3d(.*?)\\x26', response)
    try:
        return result.group(1)
    except (IndexError, AttributeError) as e:
        pass
    return ""


def _get_hash_code_params():
    from collections import OrderedDict
    data = {
        'adblock': '0',
        'browserLanguage': 'en-US',
        'cookieEnabled': '1',
        'custID': '133',
        'doNotTrack': 'unknown',
        'flashVersion': '0',
        'javaEnabled': '0',
        'jsFonts': 'c227b88b01f5c513710d4b9f16a5ce52',
        'localCode': '3232236206',
        'mimeTypes': '52d67b2a5aa5e031084733d5006cc664',
        'os': 'MacIntel',
        'platform': 'WEB',
        'plugins': 'd22ca0b81584fbea62237b14bd04c866',
        'scrAvailSize': str(random.randint(500, 1000)) + 'x1920',
        'srcScreenSize': '24xx1080x1920',
        'storeDb': 'i1l1o1s1',
        'timeZone': '-8',
        'touchSupport': '99115dfb07133750ba677d055874de87',
        'userAgent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.' + str(
            random.randint(
                5000, 7000)) + '.0 Safari/537.36',
        'webSmartID': 'f4e3b7b14cc647e30a6267028ad54c56',
    }
    data_trans = {
        'browserVersion': 'd435',
        'touchSupport': 'wNLf',
        'systemLanguage': 'e6OK',
        'scrWidth': 'ssI5',
        'openDatabase': 'V8vl',
        'scrAvailSize': 'TeRS',
        'hasLiedResolution': '3neK',
        'hasLiedOs': 'ci5c',
        'timeZone': 'q5aJ',
        'userAgent': '0aew',
        'userLanguage': 'hLzX',
        'jsFonts': 'EOQP',
        'scrAvailHeight': '88tV',
        'browserName': '-UVA',
        'cookieCode': 'VySQ',
        'online': '9vyE',
        'scrAvailWidth': 'E-lJ',
        'flashVersion': 'dzuS',
        'scrDeviceXDPI': '3jCe',
        'srcScreenSize': 'tOHY',
        'storeDb': 'Fvje',
        'doNotTrack': 'VEek',
        'mimeTypes': 'jp76',
        'sessionStorage': 'HVia',
        'cookieEnabled': 'VPIf',
        'os': 'hAqN',
        'hasLiedLanguages': 'j5po',
        'hasLiedBrowser': '2xC5',
        'webSmartID': 'E3gR',
        'appcodeName': 'qT7b',
        'javaEnabled': 'yD16',
        'plugins': 'ks0Q',
        'appMinorVersion': 'qBVW',
        'cpuClass': 'Md7A',
        'indexedDb': '3sw-',
        'adblock': 'FMQw',
        'localCode': 'lEnu',
        'browserLanguage': 'q4f3',
        'scrHeight': '5Jwy',
        'localStorage': 'XM7l',
        'historyList': 'kU5z',
        'scrColorDepth': "qmyu"
    }
    data = OrderedDict(data)
    d = ''
    params = {}
    for key, item in data.items():
        d += key + item
        key = data_trans[key] if key in data_trans else key
        params[key] = item
    d_len = len(d)
    d_f = int(d_len / 3) if d_len % 3 == 0 else int(d_len / 3) + 1
    if d_len >= 3:
        d = d[d_f:2 * d_f] + d[2 * d_f:d_len] + d[0: d_f]
    d_len = len(d)
    d_f = int(d_len / 3) if d_len % 3 == 0 else int(d_len / 3) + 1
    if d_len >= 3:
        d = d[2 * d_f:d_len] + d[0: d_f] + d[1 * d_f: 2 * d_f]

    d = _encode_data_str_v2(d)
    d = _encode_data_str_v2(d)
    d = _encode_data_str_v2(d)
    data_str = _encode_string(d)
    params['hashCode'] = data_str
    return params


def _encode_data_str_v2(d):
    b = len(d)
    if b % 2 == 0:
        return d[b // 2: b] + d[0:b // 2]
    else:
        return d[b // 2 + 1:b] + d[b // 2] + d[0:b // 2]


def _encode_string(str):
    import hashlib
    import base64
    result = base64.b64encode(hashlib.sha256(str.encode()).digest()).decode()
    return result.replace('+', '-').replace('/', '_').replace('=', '')



File: config/TicketEnmu.py

# coding=utf-8
from enum import Enum


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 = 120  # 排队请求12306的次数
    WAIT_OUT_NUM = u"超出排队时间，自动放弃，正在重新刷票"
    WAIT_ORDER_SUCCESS = u"恭喜您订票成功，订单号为：{0}, 请立即打开浏览器登录12306，访问‘未完成订单’，在30分钟内完成支付!"
    WAIT_AFTER_NATE_SUCCESS = u"候补订单已完成，请立即打开浏览器登录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}"

    REST_TIME = u"12306休息时间，本程序自动停止,明天早上6点将自动运行"
    REST_TIME_PAST = u"休息时间已过，重新开启检票功能"

    LOGIN_SESSION_FAIL = u"用户检查失败：{0}，可能未登录，可能session已经失效, 正在重新登录中"



File: config/serverchanConf.py

# -*- coding: utf8 -*-
import TickerConfig
from config.urlConf import urls
from myUrllib.httpUtils import HTTPClient

PUSH_SERVER_CHAN_PATH = "https://sc.ftqq.com"


def sendServerChan(msg):
    """
    pushBear微信通知
    :param str: 通知内容 content
    :return:
    """
    if (
        TickerConfig.SERVER_CHAN_CONF["is_server_chan"]
        and TickerConfig.SERVER_CHAN_CONF["secret"].strip() != ""
    ):
        try:
            secret = TickerConfig.SERVER_CHAN_CONF["secret"].strip()
            sendServerChanUrls = urls.get("ServerChan")
            sendServerChanUrls["req_url"] += f'{secret}.send'

            params = {"text": "易行购票成功通知", "desp": msg}
            httpClint = HTTPClient(0)
            sendServerChanRsp = httpClint.send(sendServerChanUrls, params=params)
            if sendServerChanRsp.get("errno") == 0:
                print(u"已下发 Server酱 微信通知, 请查收")
            else:
                print(sendServerChanRsp)
        except Exception as e:
            print(u"Server酱 配置有误 {}".format(e))


if __name__ == "__main__":
    sendServerChan(1)



File: config/urlConf.py

# coding=utf-8
import random
import TickerConfig
import time

urls = {
    "auth": {  # 登录接口
        "req_url": "/passport/web/auth/uamtk",
        "req_type": "post",
        "Referer": "https://kyfw.12306.cn/otn/resources/login.html",
        "Host": "kyfw.12306.cn",
        "Content-Type": 1,
        "re_try": 10,
        "re_time": 1,
        "s_time": 0.1,
        "is_logger": True,
        "is_json": True,
        "is_cdn": True,
    },
    "uamtk-static": {  # 登录接口
        "req_url": "/passport/web/auth/uamtk-static",
        "req_type": "get",
        "Referer": "https://kyfw.12306.cn/otn/resources/login.html",
        "Host": "kyfw.12306.cn",
        "Content-Type": 1,
        "re_try": 10,
        "re_time": 3,
        "s_time": 0.1,
        "is_logger": True,
        "is_json": True,
        "is_cdn": True,
    },
    "login": {   # 登录接口
        "req_url": "/passport/web/login",
        "req_type": "post",
        "Referer": "https://kyfw.12306.cn/otn/resources/login.html",
        "Host": "kyfw.12306.cn",
        "Content-Type": 1,
        "re_try": 10,
        "re_time": 1,
        "s_time": 0.5,
        "is_logger": True,
        "is_cdn": True,
        "is_json": True,

    },
    "left_ticket_init": {   # 登录接口
        "req_url": "/otn/leftTicket/init",
        "req_type": "post",
        "Referer": "https://kyfw.12306.cn/otn/resources/login.html",
        "Host": "kyfw.12306.cn",
        "Content-Type": 1,
        "re_try": 10,
        "re_time": 1,
        "s_time": 0.1,
        "is_logger": False,
        "is_cdn": True,
        "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/resources/login.html",
        "Host": "kyfw.12306.cn",
        "Content-Type": 1,
        "re_try": 10,
        "re_time": 1,
        "s_time": 0.1,
        "is_logger": False,
        "is_json": False,
        "is_cdn": True,
        "not_decode": True,
    },
    "getCodeImg1": {  # 登录验证码
        "req_url": "/passport/captcha/captcha-image64?login_site=E&module=login&rand=sjrand&{0}&callback=jQuery19108016482864806321_1554298927290&_=1554298927293",
        "req_type": "get",
        "Referer": "https://kyfw.12306.cn/otn/resources/login.html",
        "Host": "kyfw.12306.cn",
        "Content-Type": 1,
        "re_try": 10,
        "re_time": 1,
        "s_time": 0.1,
        "is_logger": True,
        "is_cdn": True,
        "is_json": False,
    },
    "codeCheck": {  # 验证码校验
        "req_url": "/passport/captcha/captcha-check",
        "req_type": "post",
        "Referer": "https://kyfw.12306.cn/otn/resources/login.html",
        "Host": "kyfw.12306.cn",
        "Content-Type": 1,
        "re_try": 10,
        "re_time": 1,
        "s_time": 0.1,
        "is_logger": True,
        "is_cdn": True,
        "is_json": False,
    },
    "codeCheck1": {  # 验证码校验
        "req_url": "/passport/captcha/captcha-check?callback=jQuery19108016482864806321_1554298927290&answer={0}&rand=sjrand&login_site=E&_={1}",
        "req_type": "get",
        "Referer": "https://kyfw.12306.cn/otn/resources/login.html",
        "Host": "kyfw.12306.cn",
        "Content-Type": 1,
        "re_try": 10,
        "re_time": 1,
        "s_time": 0.1,
        "is_cdn": True,
        "is_logger": True,
        "is_json": False,
    },
    "loginInit": {  # 登录页面
        "req_url": "/otn/login/init",
        "req_type": "get",
        "Referer": "https://kyfw.12306.cn/otn/index/init",
        "Host": "kyfw.12306.cn",
        "re_try": 1,
        "re_time": 1,
        "s_time": 0.1,
        "is_logger": False,
        "is_cdn": True,
        "is_json": False,
    },
    "loginInitCdn": {  # 登录页面
        "req_url": "/otn/login/init",
        "req_type": "get",
        "Referer": "https://kyfw.12306.cn/otn/index/init",
        "Host": "kyfw.12306.cn",
        "re_try": 1,
        "re_time": 1,
        "s_time": 0.1,
        "is_logger": False,
        "is_test_cdn": True,
        "is_cdn": True,
        "is_json": False,
    },
    "loginInitCdn1": {  # 登录页面
        "req_url": "/otn/resources/login.html",
        "req_type": "get",
        "Referer": "https://kyfw.12306.cn/otn/view/index.html",
        "Host": "kyfw.12306.cn",
        "re_try": 1,
        "re_time": 1,
        "s_time": 0.1,
        "is_logger": False,
        "is_test_cdn": False,
        "is_cdn": True,
        "is_json": False,
    },
    "getDevicesId": {  # 获取用户信息
        "req_url": "/otn/HttpZF/logdevice",
        "req_type": "get",
        "Referer": "https://kyfw.12306.cn/otn/passport?redirect=/otn/",
        "Host": "kyfw.12306.cn",
        "re_try": 10,
        "re_time": 1,
        "s_time": 0.01,
        "is_cdn": True,
        "is_logger": True,
        "is_json": False,
    },
    "getUserInfo": {  # 获取用户信息
        "req_url": "/otn/index/initMy12306",
        "req_type": "get",
        "Referer": "https://kyfw.12306.cn/otn/passport?redirect=/otn/login/userLogin",
        "Host": "kyfw.12306.cn",
        "re_try": 10,
        "re_time": 1,
        "s_time": 0.01,
        "is_cdn": True,
        "is_logger": False,
        "is_json": False,
    },
    "userLogin": {  # 用户登录
        "req_url": "/otn/login/userLogin",
        "req_type": "get",
        "Referer": "https://kyfw.12306.cn/otn/passport?redirect=/otn/login/userLogin",
        "Host": "kyfw.12306.cn",
        "re_try": 10,
        "re_time": 1,
        "s_time": 0.1,
        "is_logger": True,
        "is_cdn": True,
        "is_json": True,
    },
    "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": 1,
        "s_time": 0.1,
        "is_cdn": True,
        "is_logger": True,
        "is_json": True,
    },
    "initdc_url": {  # 生成订单页面
        "req_url": "/otn/confirmPassenger/initDc",
        "req_type": "get",
        "Referer": "https://kyfw.12306.cn/otn/leftTicket/init",
        "Host": "kyfw.12306.cn",
        "re_try": 10,
        "re_time": 0.1,
        "s_time": 1,
        "is_logger": False,
        "is_cdn": True,
        "is_json": False,
    },
    "GetJS": {   # 订单页面js
        "req_url": "/otn/HttpZF/GetJS",
        "req_type": "get",
        "Referer": "https://kyfw.12306.cn/otn/confirmPassenger/initDc",
        "Host": "kyfw.12306.cn",
        "re_try": 10,
        "re_time": 0.1,
        "s_time": 0.1,
        "is_logger": False,
        "is_cdn": True,
        "is_json": False,
    },
    "odxmfwg": {  # 订单页面js
        "req_url": "/otn/dynamicJs/odxmfwg",
        "req_type": "get",
        "Referer": "https://kyfw.12306.cn/otn/confirmPassenger/initDc",
        "Host": "kyfw.12306.cn",
        "re_try": 10,
        "re_time": 0.1,
        "s_time": 0.1,
        "is_logger": False,
        "is_cdn": True,
        "is_json": False,
    },
    "get_passengerDTOs": {  # 获取乘车人
        "req_url": "/otn/confirmPassenger/getPassengerDTOs",
        "req_type": "post",
        "Referer": "https://kyfw.12306.cn/otn/confirmPassenger/initDc",
        "Host": "kyfw.12306.cn",
        "re_try": 10,
        "re_time": 0.1,
        "s_time": 0.1,
        "is_cdn": True,
        "is_logger": True,
        "is_json": True,
    },
    "select_url": {  # 查询余票
        "req_url": "/otn/{3}?leftTicketDTO.train_date={0}&leftTicketDTO.from_station={1}&leftTicketDTO.to_station={2}&purpose_codes=ADULT",
        "req_type": "get",
        "Referer": "https://kyfw.12306.cn/otn/leftTicket/init",
        "Host": "kyfw.12306.cn",
        "re_try": 1,
        "re_time": 0.01,
        "s_time": 0.01,
        "is_logger": False,
        "is_json": True,
        "is_cdn": True,
    },
    "check_user_url": {  # 检查用户登录
        "req_url": "/otn/login/checkUser",
        "req_type": "post",
        "Referer": "https://kyfw.12306.cn/otn/leftTicket/init",
        "Host": "kyfw.12306.cn",
        "re_try": 1,
        "re_time": 1,
        "s_time": 1,
        "is_cdn": True,
        "is_logger": True,
        "is_json": True,
    },
    "submit_station_url": {  # 提交订单
        "req_url": "/otn/leftTicket/submitOrderRequest",
        "req_type": "post",
        "Referer": "https://kyfw.12306.cn/otn/leftTicket/init",
        "Host": "kyfw.12306.cn",
        "re_try": 10,
        "re_time": 0.01,
        "s_time": 0.1,
        "is_cdn": True,
        "is_logger": True,
        "is_json": True,
    },
    "checkOrderInfoUrl": {  # 检查订单信息规范
        "req_url": "/otn/confirmPassenger/checkOrderInfo",
        "req_type": "post",
        "Referer": "https://kyfw.12306.cn/otn/confirmPassenger/initDc",
        "Host": "kyfw.12306.cn",
        "re_try": 10,
        "re_time": 0.01,
        "s_time": 0.1,
        "is_logger": True,
        "is_cdn": True,
        "is_json": True,
    },
    "getQueueCountUrl": {  # 剩余余票数
        "req_url": "/otn/confirmPassenger/getQueueCount",
        "req_type": "post",
        "Referer": "https://kyfw.12306.cn/otn/confirmPassenger/initDc",
        "Host": "kyfw.12306.cn",
        "re_try": 10,
        "re_time": 0.01,
        "s_time": 0.1,
        "is_logger": True,
        "is_cdn": True,
        "is_json": True,
    },
    "checkQueueOrderUrl": {  # 订单队列排队
        "req_url": "/otn/confirmPassenger/confirmSingleForQueue",
        "req_type": "post",
        "Referer": "https://kyfw.12306.cn/otn/confirmPassenger/initDc",
        "Host": "kyfw.12306.cn",
        "re_try": 10,
        "re_time": 0.01,
        "s_time": 0.1,
        "is_logger": True,
        "is_cdn": True,
        "is_json": True,
    },
    "checkRandCodeAnsyn": {  # 暂时没用到
        "req_url": "/otn/passcodeNew/checkRandCodeAnsyn",
        "req_type": "post",
        "Referer": "https://kyfw.12306.cn/otn/confirmPassenger/initDc",
        "Host": "kyfw.12306.cn",
        "re_try": 10,
        "re_time": 0.01,
        "s_time": 0.1,
        "is_cdn": True,
        "is_logger": True,
        "is_json": True,
    },
    "codeImgByOrder": {  # 订单页面验证码
        "req_url": "/otn/passcodeNew/getPassCodeNew?module=passenger&rand=randp&{}",
        "req_type": "post",
        "Referer": "https://kyfw.12306.cn/otn/confirmPassenger/initDc",
        "Host": "kyfw.12306.cn",
        "re_try": 10,
        "re_time": 0.01,
        "s_time": 0.1,
        "is_logger": False,
        "is_cdn": True,
        "is_json": False,
    },
    "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,
        "re_time": 0.01,
        "s_time": 0.1,
        "is_logger": True,
        "is_cdn": True,
        "is_json": True,
    },
    "queryMyOrderNoCompleteUrl": {  # 订单查询页面
        "req_url": "/otn/queryOrder/queryMyOrderNoComplete",
        "req_type": "post",
        "Referer": "https://kyfw.12306.cn/otn/queryOrder/initNoComplete",
        "Host": "kyfw.12306.cn",
        "re_try": 10,
        "re_time": 0.01,
        "s_time": 0.1,
        "is_logger": True,
        "is_cdn": True,
        "is_json": True,
    },
    "initNoCompleteUrl": {  # 获取订单列表
        "req_url": "/otn/queryOrder/initNoComplete",
        "req_type": "post",
        "Referer": "https://kyfw.12306.cn/otn/queryOrder/initNoComplete",
        "Host": "kyfw.12306.cn",
        "re_try": 10,
        "re_time": 0.01,
        "s_time": 0.1,
        "is_logger": False,
        "is_cdn": True,
        "is_json": False,
    },
    "cancelNoCompleteMyOrder": {  # 取消订单
        "req_url": "/otn/queryOrder/cancelNoCompleteMyOrder",
        "req_type": "post",
        "Referer": "https://kyfw.12306.cn/otn/queryOrder/initNoComplete",
        "Host": "kyfw.12306.cn",
        "re_try": 10,
        "re_time": 0.01,
        "s_time": 0.1,
        "is_cdn": True,
        "is_logger": True,
        "is_json": True,
    },
    "autoSubmitOrderRequest": {  # 快速自动提交订单
        "req_url": "/otn/confirmPassenger/autoSubmitOrderRequest",
        "req_type": "post",
        "Referer": "https://kyfw.12306.cn/otn/leftTicket/init",
        "Host": "kyfw.12306.cn",
        "Content-Type": 1,
        "re_try": 10,
        "re_time": 0.01,
        "s_time": 0.1,
        "is_logger": True,
        "is_cdn": True,
        "is_json": True,
    },
    "getQueueCountAsync": {  # 快速获取订单数据
        "req_url": "/otn/confirmPassenger/getQueueCountAsync",
        "req_type": "post",
        "Referer": "https://kyfw.12306.cn/otn/leftTicket/init",
        "Host": "kyfw.12306.cn",
        "Content-Type": 1,
        "re_try": 10,
        "re_time": 0.01,
        "s_time": 0.1,
        "is_logger": True,
        "is_cdn": True,
        "is_json": True,
    },
    "confirmSingleForQueueAsys": {  # 快速订单排队
        "req_url": "/otn/confirmPassenger/confirmSingleForQueueAsys",
        "req_type": "post",
        "Referer": "https://kyfw.12306.cn/otn/leftTicket/init",
        "Content-Type": 1,
        "Host": "kyfw.12306.cn",
        "re_try": 10,
        "re_time": 0.01,
        "s_time": 0.1,
        "is_logger": True,
        "is_cdn": True,
        "is_json": True,
    },
    "Pushbear": {  # push通知
        "req_url": "/sub",
        "req_type": "post",
        "Referer": "",
        "Content-Type": 1,
        "Host": "pushbear.ftqq.com",
        "re_try": 10,
        "re_time": 0.01,
        "s_time": 0.1,
        "is_logger": False,
        "is_json": True,
    },
    "ServerChan": {  # Server酱 push通知
        "req_url": "/",
        "req_type": "get",
        "Referer": "",
        "Content-Type": 1,
        "Host": "sc.ftqq.com",
        "re_try": 10,
        "re_time": 0.01,
        "s_time": 0.1,
        "is_logger": True,
        "is_json": True,
    },
    "loginHtml": {  # 登录接口2
        "req_url": "/otn/resources/login.html",
        "req_type": "get",
        "Referer": "https://kyfw.12306.cn/otn/leftTicket/init",
        "Host": "kyfw.12306.cn",
        "re_try": 10,
        "re_time": 0.3,
        "s_time": 0.1,
        "is_cdn": True,
        "is_logger": True,
        "is_json": True,
    },
    "loginConf": {  # 登录接口2
        "req_url": "/otn/login/conf",
        "req_type": "post",
        "Referer": "https://kyfw.12306.cn/otn/leftTicket/init",
        "Host": "kyfw.12306.cn",
        "re_try": 10,
        "re_time": 0.3,
        "s_time": 0.1,
        "is_cdn": True,
        "is_logger": True,
        "is_json": True,
    },
    "loginAysnSuggest": {  # 登录接口2
        "req_url": "/otn/login/loginAysnSuggest",
        "req_type": "post",
        "Referer": "https://kyfw.12306.cn/otn/leftTicket/init",
        "Host": "kyfw.12306.cn",
        "re_try": 10,
        "re_time": 0.3,
        "is_cdn": True,
        "s_time": 0.1,
        "is_logger": True,
        "is_json": True,
    },



    # 候补订单接口

    "chechFace": {  # 人脸识别
        "req_url": "/otn/afterNate/chechFace",
        "req_type": "post",
        "Referer": "https://kyfw.12306.cn/otn/leftTicket/init",
        "Host": "kyfw.12306.cn",
        "re_try": 10,
        "re_time": 0.01,
        "s_time": 0.01,
        "is_cdn": True,
        "is_logger": True,
        "is_json": True,
    },
    "getSuccessRate": {  # 成功信息
        "req_url": "/otn/afterNate/getSuccessRate",
        "req_type": "post",
        "Referer": "https://kyfw.12306.cn/otn/leftTicket/init",
        "Host": "kyfw.12306.cn",
        "re_try": 10,
        "re_time": 0.01,
        "s_time": 0.01,
        "is_cdn": True,
        "is_logger": True,
        "is_json": True,
    },
    "SubmitOrderRequestRsp": {  # 提交候补订单准备
        "req_url": "/otn/afterNate/submitOrderRequest",
        "req_type": "post",
        "Referer": "https://kyfw.12306.cn/otn/leftTicket/init",
        "Host": "kyfw.12306.cn",
        "re_try": 10,
        "re_time": 0.01,
        "s_time": 0.01,
        "is_cdn": True,
        "is_logger": True,
        "is_json": True,
    },
    "confirmHB": {  # 设置订单信息
        "req_url": "/otn/afterNate/confirmHB",
        "req_type": "post",
        "Referer": "https://kyfw.12306.cn/otn/leftTicket/init",
        "Host": "kyfw.12306.cn",
        "re_try": 10,
        "re_time": 0.01,
        "s_time": 0.01,
        "is_cdn": True,
        "is_logger": True,
        "is_json": True,
    },
    "queryQueue": {  # 排队
        "req_url": "/otn/afterNate/queryQueue",
        "req_type": "post",
        "Referer": "https://kyfw.12306.cn/otn/leftTicket/init",
        "Host": "kyfw.12306.cn",
        "re_try": 10,
        "re_time": 0.01,
        "s_time": 0.01,
        "is_cdn": True,
        "is_logger": True,
        "is_json": True,
    },
    "passengerInitApi": {  # 排队
        "req_url": "/otn/afterNate/passengerInitApi",
        "req_type": "post",
        "Referer": "https://kyfw.12306.cn/otn/leftTicket/init",
        "Host": "kyfw.12306.cn",
        "re_try": 10,
        "re_time": 0.01,
        "s_time": 0.01,
        "is_cdn": True,
        "is_logger": True,
        "is_json": True,
    },

    "autoVerifyImage": {  # 云打码接口
        "req_url": TickerConfig.REQ_URL,
        "req_type": "post",
        "Referer": "",
        "Host": TickerConfig.HOST,
        "re_try": 6,
        "re_time": 10,
        "s_time": 0.001,
        "is_logger": True,
        "is_json": True,
        "httpType": TickerConfig.HTTP_TYPE
    },
}


File: agency/__init__.py




File: agency/cdn_utils.py

# encoding=utf8
import datetime
import operator
import os
import requests
from config import urlConf
import threading
from config.urlConf import urls

from myUrllib.httpUtils import HTTPClient

cdn_list = []


class CDNProxy(threading.Thread):
    def __init__(self, cdns):
        super().__init__()
        self.cdns = cdns
        self.urlConf = urlConf.urls
        self.httpClint = requests
        self.city_list = []
        self.timeout = 5

    def run(self):
        for cdn in self.cdns:
            http = HTTPClient(0)
            url = urls["loginInitCdn"]
            http._cdn = cdn.replace("\n", "")
            start_time = datetime.datetime.now()
            rep = http.send(url)
            retTime = (datetime.datetime.now() - start_time).microseconds / 1000
            if rep and "message" not in rep and retTime < 3000:
                if cdn.replace("\n", "") not in cdn_list:  # 如果有重复的cdn，则放弃加入
                    print(f"加入cdn: {cdn}")
                    cdn_list.append({"ip": cdn.replace("\n", ""), "time": retTime})


def open_cdn_file(cdnFile):
    cdn = []
    path = os.path.join(os.path.dirname(__file__), f'../{cdnFile}')
    try:
        with open(path, "r", encoding="utf-8") as f:
            for i in f.readlines():
                if i and "kyfw.12306.cn:443" not in i:
                    cdn.append(i.replace("\n", ""))
            return cdn
    except Exception:
        with open(path, "r") as f:
            for i in f.readlines():
                if i and "kyfw.12306.cn:443" not in i:
                    cdn.append(i.replace("\n", ""))
            return cdn


def sortCdn():
    """
    对cdn进行排序
    :return:
    """
    ips = []
    cs = sorted(cdn_list, key=operator.itemgetter('time'))
    for c in cs:
        print(f"当前ip: {c['ip']}, 延时: {c['time']}")
        ips.append(c["ip"])
    return ips


def filterCdn():
    """
    过滤cdn, 过滤逻辑为当前cdn响应值小于1000毫秒
    过滤日志:
        加入cdn: 116.77.75.146
    :return:
    """
    cdns = open_cdn_file("cdn_list")
    cdnss = [cdns[i:i + 50] for i in range(0, len(cdns), 50)]
    cdnThread = []
    for cdn in cdnss:
        t = CDNProxy(cdn)
        cdnThread.append(t)
    for cdn_t in cdnThread:
        cdn_t.start()

    for cdn_j in cdnThread:
        cdn_j.join()

    print(f"当前有效cdn个数为: {len(cdn_list)}")
    if cdn_list:
        ips = sortCdn()
        path = os.path.join(os.path.dirname(__file__), f'../filter_cdn_list')
        f = open(path, "a+")
        f.seek(0)
        f.truncate()
        f.writelines("")
        for ip in ips:
            f.writelines(f"{ip}\n")
        f.close()


if __name__ == '__main__':
    filterCdn()



File: agency/agency_tools.py

# encoding=utf8
import os
import random
import socket
import time

import requests
from bs4 import BeautifulSoup


class proxy:
    def __init__(self):
        self.proxy_list = []
        self.proxy_filter_list = []

    def get_proxy(self):
        """
        获取未加工代理列表
        :return: 
        """
        User_Agent = 'Mozilla/5.0 (Windows NT 6.3; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0'
        header = dict()
        header['User-Agent'] = User_Agent

        for i in range(1, 5):
            time.sleep(1)
            url = 'http://www.xicidaili.com/nn/' + str(i)
            res = requests.get(url=url, headers=header).content

            soup = BeautifulSoup(res, "html.parser")
            ips = soup.findAll('tr')

            for x in range(1, len(ips)):
                ip = ips[x]
                tds = ip.findAll("td")
                ip_temp = tds[1].contents[0] + ":" + tds[2].contents[0]
                print(ip_temp)
                self.proxy_list.append(ip_temp)

    def filter_proxy(self):
        """
        将不可用IP剔除
        :return: 
        """
        socket.setdefaulttimeout(1)
        path = os.path.join(os.path.dirname(__file__), './proxy_list')
        f = open(path, "w")
        head = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36',
            'Connection': 'keep-alive'}
        url = "http://icanhazip.com"
        proxy_num = 0
        for proxy in self.proxy_list:
            proxy_temp = {"https": "https://{}".format(proxy)}
            try:
                req = requests.get(url, proxies=proxy_temp, timeout=2, headers=head).content
                print(req)
                write_proxy = proxy + "\n"
                f.write(write_proxy)
                proxy_num += 1
            except Exception:
                print ("代理链接超时，去除此IP：{0}".format(proxy))
                continue
        print("总共可使用ip量为{}个".format(proxy_num))

    def get_filter_proxy(self):
        """
        读取该可用ip文件
        :return: 可用ip文件list
        """
        path = os.path.join(os.path.dirname(__file__), './proxy_list')
        try:
            with open(path, "r", encoding="utf-8") as f:
                lins = f.readlines()
                for i in lins:
                    p = i.strip("\n")
                    self.proxy_filter_list.append(p)
        except Exception:
            with open(path, "r", ) as f:
                lins = f.readlines()
                for i in lins:
                    p = i.strip("\n")
                    self.proxy_filter_list.append(p)
        return self.proxy_filter_list

    def main(self):
        # self.get_proxy()
        self.filter_proxy()

    def setProxy(self):
        """
        开启此功能的时候请确保代理ip是否可用
        查询的时候设置代理ip,ip设置格式是ip地址+端口，推荐可用的ip代理池：https://github.com/jhao104/proxy_pool
        :return:
        """
        ip = self.get_filter_proxy()
        setIp = ip[random.randint(0, len(ip) - 1)]
        proxie = {
            'http': 'http://{}'.format(setIp),
            'https': 'http://{}'.format(setIp),
        }
        return proxie


if __name__ == "__main__":
    a = proxy()
    print(a.get_filter_proxy())



File: myUrllib/httpUtils.py

# -*- coding: utf8 -*-
import json
import random
import socket
from collections import OrderedDict
from time import sleep
import requests
from fake_useragent import UserAgent
import TickerConfig
from agency.agency_tools import proxy
from config import logger


def _set_header_default():
    header_dict = OrderedDict()
    # header_dict["Accept"] = "application/json, text/plain, */*"
    header_dict["Accept-Encoding"] = "gzip, deflate"
    header_dict[
        "User-Agent"] = _set_user_agent()
    header_dict["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8"
    header_dict["Origin"] = "https://kyfw.12306.cn"
    header_dict["Connection"] = "keep-alive"
    return header_dict


def _set_user_agent():
    # try:
    #     user_agent = UserAgent(verify_ssl=False).random
    #     return user_agent
    # except:
    #     print("请求头设置失败，使用默认请求头")
    #     return 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.' + str(
    #         random.randint(5000, 7000)) + '.0 Safari/537.36'
    return "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"


class HTTPClient(object):

    def __init__(self, is_proxy, cdnList=None):
        """
        cdnList试试切换不包括查询的cdn，防止查询cdn污染登陆和下单cdn
        :param method:
        :param headers: Must be a dict. Such as headers={'Content_Type':'text/html'}
        """
        self.initS()
        self._cdn = None
        self.cdnList = cdnList
        self._proxies = None
        if is_proxy is 1:
            self.proxy = proxy()
            self._proxies = self.proxy.setProxy()
            # print(u"设置当前代理ip为 {}, 请注意代理ip是否可用！！！！！请注意代理ip是否可用！！！！！请注意代理ip是否可用！！！！！".format(self._proxies))

    def initS(self):
        self._s = requests.Session()
        self._s.headers.update(_set_header_default())
        return self

    def set_cookies(self, kwargs):
        """
        设置cookies
        :param kwargs:
        :return:
        """
        for kwarg in kwargs:
            for k, v in kwarg.items():
                self._s.cookies.set(k, v)

    def get_cookies(self):
        """
        获取cookies
        :return:
        """
        return self._s.cookies.values()

    def del_cookies(self):
        """
        删除所有的key
        :return:
        """
        self._s.cookies.clear()

    def del_cookies_by_key(self, key):
        """
        删除指定key的session
        :return:
        """
        self._s.cookies.set(key, None)

    def setHeaders(self, headers):
        self._s.headers.update(headers)
        return self

    def resetHeaders(self):
        self._s.headers.clear()
        self._s.headers.update(_set_header_default())

    def getHeadersHost(self):
        return self._s.headers["Host"]

    def setHeadersHost(self, host):
        self._s.headers.update({"Host": host})
        return self

    def setHeadersUserAgent(self):
        self._s.headers.update({"User-Agent": _set_user_agent()})

    def getHeadersUserAgent(self):
        return self._s.headers["User-Agent"]

    def getHeadersReferer(self):
        return self._s.headers["Referer"]

    def setHeadersReferer(self, referer):
        self._s.headers.update({"Referer": referer})
        return self

    @property
    def cdn(self):
        return self._cdn

    @cdn.setter
    def cdn(self, cdn):
        self._cdn = cdn

    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.get("is_logger", False)
        req_url = urls.get("req_url", "")
        re_try = urls.get("re_try", 0)
        s_time = urls.get("s_time", 0)
        is_cdn = urls.get("is_cdn", False)
        is_test_cdn = urls.get("is_test_cdn", False)
        error_data = {"code": 99999, "message": u"重试次数达到上限"}
        if data:
            method = "post"
            self.setHeaders({"Content-Length": "{0}".format(len(data))})
        else:
            method = "get"
            self.resetHeaders()
        if TickerConfig.RANDOM_AGENT is 1:
            self.setHeadersUserAgent()
        self.setHeadersReferer(urls["Referer"])
        if is_logger:
            logger.log(
                u"url: {0}\n入参: {1}\n请求方式: {2}\n".format(req_url, data, method))
        self.setHeadersHost(urls["Host"])
        if is_test_cdn:
            url_host = self._cdn
        elif is_cdn:
            if self._cdn:
                # print(u"当前请求cdn为{}".format(self._cdn))
                url_host = self._cdn
            else:
                url_host = urls["Host"]
        else:
            url_host = urls["Host"]
        http = urls.get("httpType") or "https"
        for i in range(re_try):
            try:
                # sleep(urls["s_time"]) if "s_time" in urls else sleep(0.001)
                sleep(s_time)
                try:
                    requests.packages.urllib3.disable_warnings()
                except:
                    pass
                response = self._s.request(method=method,
                                           timeout=5,
                                           proxies=self._proxies,
                                           url=http + "://" + url_host + req_url,
                                           data=data,
                                           allow_redirects=allow_redirects,
                                           verify=False,
                                           **kwargs)
                if response.status_code == 200 or response.status_code == 302:
                    if urls.get("not_decode", False):
                        return response.content
                    if response.content:
                        if is_logger:
                            logger.log(
                                u"出参：{0}".format(response.content.decode()))
                        if urls["is_json"]:
                            return json.loads(
                                response.content.decode() if isinstance(response.content, bytes) else response.content)
                        else:
                            return response.content.decode("utf8", "ignore") if isinstance(response.content,
                                                                                           bytes) else response.content
                    else:
                        print(f"url: {urls['req_url']}返回参数为空, 接口状态码: {response.status_code}")

                        logger.log(
                            u"url: {} 返回参数为空".format(urls["req_url"]))
                        if self.cdnList:
                            # 如果下单或者登陆出现cdn 302的情况，立马切换cdn
                            url_host = self.cdnList.pop(random.randint(0, 4))
                        continue
                else:
                    sleep(urls["re_time"])
            except (requests.exceptions.Timeout, requests.exceptions.ReadTimeout, requests.exceptions.ConnectionError):
                pass
            except socket.error:
                pass
        return error_data



File: myUrllib/__init__.py




File: myUrllib/MySocketUtils.py

# coding=utf-8
import json
import socket
import re
# s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# s.connect(('183.232.189.31', 80))
# get_str = 'GET {0} HTTP/1.1\r\nConnection: close\r\n' \
#           'Host: %s\r\n' \
#           'User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.125 Safari/537.36' \
#           '\r\nAccept: */*\r\n' \
#           '\r\n'
# post_str = "POST {0} HTTP/1.1\r\n" \
#            "Host: kyfw.12306.cn\r\n" \
#            "Connection: close\r\n"\
#            "Origin: https://kyfw.12306.cn\r\n" \
#            "X-Requested-With: XMLHttpRequest\r\n" \
#            "Referer: https://kyfw.12306.cn/otn/leftTicket/init\r\n" \
#            "Accept-Language: zh-CN,zh;q=0.9,en;q=0.8\r\n" \
#            "Content-Type: application/x-www-form-urlencoded; charset=UTF-8\r\n" \
#            "Accept: application/json, text/javascript, */*; q=0.01\r\n" \
#            "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\r\n" \
#            "Content-Length: 9\r\n"\
#            "Cookie: _passport_session=a459aba69761497eb31de76c27795e999613; _passport_ct=9116b2cb0bf443e1a01d22ac8c1ae449t5007; route=9036359bb8a8a461c164a04f8f50b252; BIGipServerpool_passport=200081930.50215.0000; BIGipServerotn=484704778.64545.0000\r\n\n"\
#            "appid=otn\r\n"
# # s.sendall(get_str.format("https://kyfw.12306.cn/otn/resources/login.html"))
# s.sendall(post_str.format("https://kyfw.12306.cn/passport/web/auth/uamtk"))
from config.urlConf import urls


def default_get_data():
    """
    get请求默认组装字符串
    需要拼接的字符串
    -- url 发送请求的全连接
    :return:
        """
    return 'GET {0} HTTP/1.1\r\nConnection: close\r\n' \
           'Host: kyfw.12306.cn\r\n' \
           "Referer: {1}\r\n" \
           'User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.125 Safari/537.36' \
           '\r\nAccept: */*\r\n' \
           "Cookie: {2}\r\n\n"\
           '\r\n'
    # return 'GET {0} HTTP/1.1\r\nConnection: close\r\n' \
    #       'Host: kyfw.12306.cn\r\n' \
    #       'User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.125 Safari/537.36' \
    #       '\r\nAccept: */*\r\n' \
    #       '\r\n'


def default_post_data():
    """
    post请求默认组装字符串
    需要拼接的字符串
    -- url 发送请求的全连接
    -- Referer 请求页面来源
    -- Content-Length: body 长度
    -- Cookie 页面请求的身份认证
    -- appid 接口请求报文
    :return:
    """
    return "POST https://kyfw.12306.cn{0} HTTP/1.1\r\n" \
           "Host: kyfw.12306.cn\r\n" \
           "Connection: close\r\n"\
           "Origin: https://kyfw.12306.cn\r\n" \
           "X-Requested-With: XMLHttpRequest\r\n" \
           "Referer: {3}\r\n" \
           "Accept-Language: zh-CN,zh;q=0.9,en;q=0.8\r\n" \
           "Content-Type: application/x-www-form-urlencoded; charset=UTF-8\r\n" \
           "Accept: application/json, text/javascript, */*; q=0.01\r\n" \
           "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\r\n" \
           "Content-Length: {2}\r\n"\
           "Cookie: {4}\r\n\n"\
           "{1}\r\n"\
           # "\r\n"


class socketUtils:
    def __init__(self, host, port=80):
        self.host = host
        self.port = port
        self.s = self.connect_socket(self.host, self.port)

    def connect_socket(self, host, port):
        """
        连接socket
        :param host:
        :param port:
        :return:
        """
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((host if isinstance(host, str) else str(host),
                   port if isinstance(port, int) else int(port)))
        return s

    def close_s(self):
        self.s.close()

    # def send(self, urls, Cookie=None, data=None):
    #     """
    #     发送请求
    #     :param urls:
    #     :param data:
    #     :param cookie:
    #     :return:
    #     """
    #     url = urls.get("req_url", "")
    #     Referer = urls.get("Referer", "")
    #     if urls.get("req_type", "get") == "post":
    #         Content_Length = len(data)
    #     Cookie = "tk=pnidlCoFy2B7wxO_X_pESbrkZFSq3OtVA_xzXwuba2a0; JSESSIONID=C6144324BFCE36AC5082E543E934E8B3; current_captcha_type=Z; _jc_save_fromDate=2018-08-03; _jc_save_fromStation=%u6DF1%u5733%2CSZQ; _jc_save_toDate=2018-08-03; _jc_save_toStation=%u957F%u6C99%2CCSQ; _jc_save_wfdc_flag=dc; ten_key=b5L6aMWfnzBm8CgQe8pcAKQsmVBS2PYH; BIGipServerpool_passport=166527498.50215.0000; BIGipServerotn=165937674.50210.0000; route=c5c62a339e7744272a54643b3be5bf64; RAIL_DEVICEID=fC-yepiUqNjsBiRvtLBXW4JqQmabCfB9QxI3FifJZK9YDRsImhJLSz4sAQ4HiGF7uQAFdFyISg6jA7KAhtpEldJV9ZMNsn6Dzm_psA5CBDwSNfiORf42w-LIRvkeGvdKFtegZwWGlkA2fVuEWKu-1xAYdCXRnsMD; RAIL_EXPIRATION=1533420302032; _jc_save_detail=true"
    #     if data:
    #         send_value = default_post_data().format(url,
    #                                                 data,
    #                                                 Content_Length,
    #                                                 Referer,
    #                                                 Cookie
    #                                                 )
    #         print("send_value: " + send_value)
    #         self.s.sendall(send_value)
    #     else:
    #         self.s.sendall(default_get_data().format(url,
    #                                                  Referer,
    #                                                  Cookie))
    #     total_data = ""
    #     while 1:
    #         data = self.s.recv(1024)
    #         total_data += data
    #         if not data:
    #             break
    #     self.close_s()
    #     print(total_data)
    #     return self.recv_data(total_data)

    def recv_data(self, r_data):
        cookie = self.get_cookie(r_data)
        status_code = self.get_status_code(r_data)
        r_body = self.get_rep_body(r_data)
        return {
            "cookie": cookie,
            "status_code": status_code,
            "r_body": r_body
        }

    @staticmethod
    def get_cookie(recv_data):
        """
        提取cookie
        :param recv_data:
        :return:
        """
        if not isinstance(recv_data, str):
            recv_data = str(recv_data)
        cookies_re = re.compile(r"Set-Cookie: (\S+);")
        cookies = re.findall(cookies_re, recv_data)
        return "; ".join(cookies)

    @staticmethod
    def get_status_code(recv_data):
        """
        获取状态码
        :return:
        """
        if not isinstance(recv_data, str):
            recv_data = str(recv_data)
        http_code_re = re.compile(r"HTTP/1.1 (\S+) ")
        status_code = re.search(http_code_re, recv_data).group(1)
        return status_code

    @staticmethod
    def get_rep_body(recv_data):
        """
        获取返回值
        :param recv_data:
        :return:
        """
        if not isinstance(recv_data, str):
            recv_data = str(recv_data)
        if recv_data.find("{") != -1 and recv_data.find("}") != -1:
            data = json.loads(recv_data.split("\n")[-1])
            return data
        else:
            print(recv_data)


if __name__ == "__main__":
    so = socketUtils('183.232.189.31', 80)
    train_date = "2018-08-03"
    from_station = "SZQ"
    to_station = "CSQ"
    urls["select_url"]["req_url"] = "https://kyfw.12306.cn" + urls["select_url"]["req_url"].format(train_date, from_station, to_station)
    result = so.send(urls=urls["select_url"])
    print(result)

    so = socketUtils('183.232.189.31', 80)

    data = "secretStr=Vgo534nDZiCH8NCvyEPcGepzJoRCjvYr34gKFv5CW1K1XtM6mtKHoiFPjUYvaVKoe06SMhUUpT%2FK%0AxIEIsBD4zHgJPpVyKiTPx80y6OCWhNgcKjib2LLMXMJfgTgh0RKPISjkDjVFmO9p905O%2FegDeKjp%0A1fhIeqCuYraHjNhI0PjQY39BAY4AHLzW0iGgDq8b%2FtpyOY8Td2XfIWNZJCWzgyPkNXOk0HUguB2G%0AKh2T8nlko6zb5ra%2B%2BA%3D%3D&train_date=2018-08-03&back_train_date=2018-08-03&tour_flag=dc&purpose_codes=ADULT&query_from_station_name=深圳&query_to_station_name=长沙&undefined"
    result1 = so.send(urls=urls["submit_station_url"], data=data)
    print(result1)
    # so = socketUtils('183.232.189.31', 80)
    # result = so.send(url="https://kyfw.12306.cn/passport/web/login", s_data="")
    # print(result)


File: inter/CheckOrderInfo.py

# coding=utf-8
from collections import OrderedDict
from inter.GetQueueCount import getQueueCount
from inter.GetRepeatSubmitToken import getRepeatSubmitToken


class checkOrderInfo:

    def __init__(self, session, train_no, set_type, passengerTicketStrList, oldPassengerStr, station_dates, ticket_peoples):
        self.train_no = train_no
        self.set_type = set_type
        self.passengerTicketStrList = passengerTicketStrList
        self.oldPassengerStr = oldPassengerStr
        self.station_dates = station_dates
        self.ticket_peoples = ticket_peoples
        self.RepeatSubmitToken = getRepeatSubmitToken(session)
        self.getTicketInfoForPassengerForm = self.RepeatSubmitToken.sendGetRepeatSubmitToken()
        self.ticketInfoForPassengerForm = self.getTicketInfoForPassengerForm.get("ticketInfoForPassengerForm", "")
        self.token = self.getTicketInfoForPassengerForm.get("token", "")
        self.session = self.getTicketInfoForPassengerForm.get("session", "")

    def data_par(self):
        """
        参数结构
        :return:
        """
        data = OrderedDict()
        data['bed_level_order_num'] = "000000000000000000000000000000"
        data['passengerTicketStr'] = self.passengerTicketStrList.rstrip("_{0}".format(self.set_type))
        data['oldPassengerStr'] = self.oldPassengerStr
        data['tour_flag'] = 'dc'
        data['randCode'] = ""
        data['cancel_flag'] = 2
        data['_json_att'] = ""
        data['REPEAT_SUBMIT_TOKEN'] = self.token
        return data

    def sendCheckOrderInfo(self):
        """
        检查支付订单，需要提交REPEAT_SUBMIT_TOKEN
        passengerTicketStr : 座位编号,0,票类型,乘客名,证件类型,证件号,手机号码,保存常用联系人(Y或N)
        oldPassengersStr: 乘客名,证件类型,证件号,乘客类型
        :return:
        """
        CheckOrderInfoUrls = self.session.urls["checkOrderInfoUrl"]
        data = self.data_par()
        checkOrderInfoRep = self.session.httpClint.send(CheckOrderInfoUrls, data)
        data = checkOrderInfoRep.get("data", {})
        if data and data.get("submitStatus", False):
            print (u'车票提交通过，正在尝试排队')
            ifShowPassCodeTime = int(checkOrderInfoRep["data"]["ifShowPassCodeTime"]) / float(1000)
            if "ifShowPassCode" in checkOrderInfoRep["data"] and checkOrderInfoRep["data"]["ifShowPassCode"] == "Y":
                is_need_code = True
            elif "ifShowPassCode" in checkOrderInfoRep["data"] and checkOrderInfoRep['data']['submitStatus'] is True:
                is_need_code = False
            else:
                is_need_code = False
            QueueCount = getQueueCount(self.session,
                                       is_need_code,
                                       ifShowPassCodeTime,
                                       self.set_type,
                                       self.station_dates,
                                       self.train_no,
                                       self.ticket_peoples,
                                       self.ticketInfoForPassengerForm,
                                       self.token,
                                       self.oldPassengerStr,
                                       self.passengerTicketStrList,
                                       )
            QueueCount.sendGetQueueCount()
        elif "errMsg" in data and data["errMsg"]:
            print(checkOrderInfoRep['data']["errMsg"])
        elif 'messages' in checkOrderInfoRep and checkOrderInfoRep['messages']:
            print (checkOrderInfoRep['messages'][0])


File: inter/Query.py

# coding=utf-8
import copy
import random
import wrapcache
from config import urlConf
from config.TicketEnmu import ticket
from myUrllib.httpUtils import HTTPClient
from config.configCommon import seat_conf_2
import TickerConfig


class query:
    """
    查询接口
    """

    def __init__(self, selectObj, from_station, to_station, from_station_h, to_station_h, _station_seat, station_trains,
                 ticke_peoples_num, station_dates=None, ):
        self.session = selectObj
        self.httpClint = HTTPClient(TickerConfig.IS_PROXY)
        self.httpClint.set_cookies(self.session.cookies)
        self.urls = urlConf.urls
        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.ticke_peoples_num = ticke_peoples_num

    def station_seat(self, index):
        """
        获取车票对应坐席
        :return:
        """
        seat = {'商务座': 32,
                '一等座': 31,
                '二等座': 30,
                '特等座': 25,
                '软卧': 23,
                '硬卧': 28,
                '硬座': 29,
                '无座': 26,
                '动卧': 33,
                }
        return seat[index]

    def check_is_need_train(self, ticket_info):
        """
        判断车次是否为想要的车次，如果ticket_info为空，那么就不校验车次，直接返回True
        :param ticket_info:
        :return:
        """
        if self.station_dates and self.station_trains:
            return ticket_info[3] in self.station_trains
        else:
            return True

    def sendQuery(self):
        """
        查询
        :return:
        """
        if TickerConfig.IS_CDN == 1 and self.session.cdn_list:
            self.httpClint.cdn = self.session.cdn_list[random.randint(4, len(self.session.cdn_list) - 1)]
        for station_date in self.station_dates:
            select_url = copy.copy(self.urls["select_url"])
            select_url["req_url"] = select_url["req_url"].format(station_date, self.from_station, self.to_station,
                                                                 self.session.queryUrl)
            station_ticket = self.httpClint.send(select_url)
            value = station_ticket.get("data", "")
            if not value:
                print(u'{0}-{1} 车次坐席查询为空，查询url: https://kyfw.12306.cn{2}, 可以手动查询是否有票'.format(
                    self.from_station_h,
                    self.to_station_h,
                    select_url["req_url"]))
            else:
                result = value.get('result', [])
                if result:
                    for i in value['result']:
                        ticket_info = i.split('|')
                        if self.session.flag:
                            print(f"车次：{ticket_info[3]} 出发站：{self.from_station_h} 到达站：{self.to_station_h} 历时：{ticket_info[10]}"
                                  f" 商务/特等座：{ticket_info[32] or '--'}"
                                  f" 一等座：{ticket_info[31] or '--'}"
                                  f" 二等座：{ticket_info[30] or '--'}"
                                  f" 动卧：{ticket_info[33] or '--'}"
                                  f" 硬卧：{ticket_info[28] or '--'}"
                                  f" 软座：{ticket_info[23] or '--'}"
                                  f" 硬座：{ticket_info[29] or '--'}"
                                  f" 无座：{ticket_info[26] or '--'}"
                                  f" {ticket_info[1] or '--'}")
                        if ticket_info[1] == "预订" and self.check_is_need_train(ticket_info):  # 筛选未在开始时间内的车次
                            for j in self._station_seat:
                                is_ticket_pass = ticket_info[j]
                                if ticket_info[11] == "Y":
                                    if is_ticket_pass != '' and is_ticket_pass != '无' 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]
                                        leftTicket = ticket_info[12]
                                        start_time = ticket_info[8]
                                        arrival_time = ticket_info[9]
                                        distance_time = ticket_info[10]
                                        print(start_time, arrival_time, distance_time)
                                        seat = j
                                        try:
                                            ticket_num = int(ticket_info[j])
                                        except ValueError:
                                            ticket_num = "有"
                                        print(u'车次: {0} 始发车站: {1} 终点站: {2} {3}: {4}'.format(ticket_info[3],
                                                                                            self.from_station_h,
                                                                                            self.to_station_h,
                                                                                            seat_conf_2[j],
                                                                                            ticket_num))
                                        if seat_conf_2[j] == "无座" and ticket_info[3][0] in ["G", "D", "C"]:
                                            seat = 30  # GD开头的无座直接强制改为二等座车次
                                        if wrapcache.get(train_no):
                                            print(ticket.QUERY_IN_BLACK_LIST.format(train_no))
                                            continue
                                        else:
                                            if ticket_num != "有" and self.ticke_peoples_num > ticket_num:
                                                if TickerConfig.IS_MORE_TICKET:
                                                    print(
                                                        u"余票数小于乘车人数，当前余票数: {}, 删减人车人数到: {}".format(ticket_num, ticket_num))
                                                    is_more_ticket_num = ticket_num
                                                else:
                                                    print(u"余票数小于乘车人数，当前设置不提交，放弃此次提交机会")
                                                    continue
                                            else:
                                                print(u"设置乘车人数为: {}".format(self.ticke_peoples_num))
                                                is_more_ticket_num = self.ticke_peoples_num
                                            print(ticket.QUERY_C)
                                            return {
                                                "secretStr": secretStr,
                                                "train_no": train_no,
                                                "stationTrainCode": stationTrainCode,
                                                "train_date": station_date,
                                                "query_from_station_name": query_from_station_name,
                                                "query_to_station_name": query_to_station_name,
                                                "seat": seat,
                                                "leftTicket": leftTicket,
                                                "train_location": train_location,
                                                "code": ticket.SUCCESS_CODE,
                                                "is_more_ticket_num": is_more_ticket_num,
                                                "cdn": self.httpClint.cdn,
                                                "status": True,
                                            }
                                elif is_ticket_pass == '无' and ticket_info[37] == "1" and TickerConfig.TICKET_TYPE is 2:
                                    """
                                    is_ticket_pass如果有别的显示，但是可以候补，可以提issues提出来，附上query log，我将添加上
                                    判断车次是否可以候补
                                    目前的候补机制是只要一有候补位置，立马提交候补
                                    """
                                    # 如果最后一位为1，则是可以候补的，不知道这些正确嘛？
                                    nate = list(ticket_info[38])
                                    if wrapcache.get(f"hb{ticket_info[2]}"):
                                        continue
                                    for set_type in TickerConfig.SET_TYPE:
                                        if TickerConfig.PASSENGER_TICKER_STR[set_type] not in nate:
                                            if ticket_info[3][0] in ["G", "D", "C"] and set_type in ["一等座", "特等座", "二等座", "商务座", "无座"]:
                                                return {
                                                    "secretList": ticket_info[0],
                                                    "seat": [set_type],
                                                    "train_no": ticket_info[2],
                                                    "status": True,
                                                    "cdn": self.httpClint.cdn,
                                                }
                                            elif ticket_info[3][0] in ["T", "Z", "K"] and set_type in ["硬卧", "硬座", "无座", "软座", "软卧"]:
                                                return {
                                                    "secretList": ticket_info[0],
                                                    "seat": [set_type],
                                                    "train_no": ticket_info[2],
                                                    "status": True,
                                                    "cdn": self.httpClint.cdn,
                                                }
                else:
                    print(u"车次配置信息有误，或者返回数据异常，请检查 {}".format(station_ticket))
        self.session.flag = False
        return {"code": ticket.FAIL_CODE, "status": False, "cdn": self.httpClint.cdn, }


if __name__ == "__main__":
    q = query()



File: inter/CheckUser.py

# coding=utf-8
import datetime
import random
import time
import wrapcache
from config import configCommon
from config.TicketEnmu import ticket


class checkUser:
    def __init__(self, session):
        self.session = session

    def sendCheckUser(self):
        """
        检查用户登录, 检查间隔为2分钟
        :return:
        """
        CHENK_TIME = 1
        while 1:
            time.sleep(3)  # 防止cpu占用过高
            configCommon.checkSleepTime(self.session)  # 修复晚上查询线程休眠时，检查登录线程为休眠，造成快豆迅速消耗
            if wrapcache.get("user_time") is None:
                check_user_url = self.session.urls["check_user_url"]
                data = {"_json_att": ""}
                check_user = self.session.httpClint.send(check_user_url, data)
                if check_user.get("data", False):
                    check_user_flag = check_user["data"]["flag"]
                    if check_user_flag is True:
                       wrapcache.set("user_time", datetime.datetime.now(), timeout=random.randint(60, 80) * CHENK_TIME)
                    else:
                        if check_user['messages']:
                            print(ticket.LOGIN_SESSION_FAIL.format(check_user['messages']))
                            self.session.call_login()
                            wrapcache.set("user_time", datetime.datetime.now(), timeout=random.randint(60, 80) * CHENK_TIME)
                        else:
                            print(ticket.LOGIN_SESSION_FAIL.format(check_user['messages']))
                            self.session.call_login()
                            wrapcache.set("user_time", datetime.datetime.now(), timeout=random.randint(60, 80) * CHENK_TIME)



File: inter/LoginConf.py

# coding=utf-8
from config.urlConf import urls


def loginConf(session):
    """
    判断登录是否需要验证码
    :param session:
    :return:
    """
    loginConfUrl = urls.get("loginConf")
    loginConfRsp = session.httpClint.send(urls=loginConfUrl, data={})
    if loginConfRsp and loginConfRsp.get("data", {}).get("is_login_passCode") == "N":
        print(u"不需要验证码")
        return False
    else:
        print(u"需要验证码")
        return True


if __name__ == '__main__':
    pass


File: inter/ConfirmSingleForQueueAsys.py

# coding=utf-8
import json
import urllib
from collections import OrderedDict

from inter.QueryOrderWaitTime import queryOrderWaitTime


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):
                qwt = queryOrderWaitTime(session=self.session)
                qwt.sendQueryOrderWaitTime()
            else:
                print(queueData.get("errMsg", ""))



File: inter/LiftTicketInit.py

# coding=utf-8
import re


class liftTicketInit:
    def __init__(self, session):
        self.session = session

    def reqLiftTicketInit(self):
        """
        请求抢票页面
        :return:
        """
        urls = self.session.urls["left_ticket_init"]
        # 获取初始化的结果
        result = self.session.httpClint.send(urls)
        # 用正则表达式查出CLeftTicketUrl的值
        matchObj = re.search('var CLeftTicketUrl = \'(.*)\'', result, re.M|re.I);
        if matchObj:
            # 如果有值，替换queryUrl
            self.session.queryUrl = matchObj.group(1)
        return {
            "status": True
        }



File: inter/__init__.py




File: inter/PassengerInitApi.py

import datetime
import wrapcache
import TickerConfig
from config.urlConf import urls
from inter.ConfirmHB import confirmHB


class passengerInitApi:
    def __init__(self, session, secretList, tickerNo):
        """
        获取候补信息
        """
        self.secretList = secretList
        self.tickerNo = tickerNo
        self.session = session

    def sendPassengerInitApi(self):
        passengerInitApiRsp = self.session.httpClint.send(urls.get("passengerInitApi"))
        if not passengerInitApiRsp.get("status"):
            print("".join(passengerInitApiRsp.get("messages")) or passengerInitApiRsp.get("validateMessages"))
            return
        data = passengerInitApiRsp.get("data", {})
        jzdhDateE = data.get("jzdhDateE")
        if not data.get("jzdhHourE"):
            wrapcache.set(key=f"hb{self.tickerNo}", value=datetime.datetime.now(),
                          timeout=TickerConfig.TICKET_BLACK_LIST_TIME * 60)
            print(f"获取当前候补日期失败，原因: {data.get('jzdhHourE')}")
            return
        jzdhHourE = data.get("jzdhHourE").replace(":", "#")
        jzdhDate = f"{jzdhDateE}#{jzdhHourE}"
        print(f"当前候补日期为:{jzdhDateE} {jzdhHourE}")
        confirm = confirmHB(self.secretList, self.session, self.tickerNo, jzdhDate)
        confirm.sendChechFace()







File: inter/SubmitOrderRequest.py

# coding=utf-8
import datetime
import urllib
from collections import OrderedDict
import TickerConfig
from config.urlConf import urls
from inter.CheckOrderInfo import checkOrderInfo
from inter.ConfirmHB import confirmHB
from inter.PassengerInitApi import passengerInitApi
from myException.ticketIsExitsException import ticketIsExitsException


def time():
    """
    获取日期
    :return:
    """
    today = datetime.date.today()
    return today.strftime('%Y-%m-%d')


class submitOrderRequest:
    def __init__(self, selectObj, secretStr, from_station, to_station, train_no, set_type,
                 passengerTicketStrList, oldPassengerStr, train_date, ticke_peoples):
        self.session = selectObj
        # self.secretStr = secretStr
        try:
            self.secretStr = urllib.unquote(secretStr)
        except AttributeError:
            self.secretStr = urllib.parse.unquote(secretStr)
        self.from_station = from_station
        self.to_station = to_station
        self.to_station = to_station
        self.train_no = train_no
        self.set_type = set_type
        self.passengerTicketStrList = passengerTicketStrList
        self.oldPassengerStr = oldPassengerStr
        self.train_date = train_date
        self.ticke_peoples = ticke_peoples

    def data_apr(self):
        """
        :return:
        """
        data = [('secretStr', self.secretStr),  # 字符串加密
                ('train_date', self.train_date),  # 出发时间
                ('back_train_date', time()),  # 返程时间
                ('tour_flag', 'dc'),  # 旅途类型
                ('purpose_codes', 'ADULT'),  # 成人票还是学生票
                ('query_from_station_name', TickerConfig.FROM_STATION),  # 起始车站
                ('query_to_station_name', TickerConfig.TO_STATION),  # 终点车站
                ('undefined', ''),
                ]
        return data

    def sendSubmitOrderRequest(self):
        """
        提交车次
        预定的请求参数，注意参数顺序
        注意这里为了防止secretStr被urllib.parse过度编码，在这里进行一次解码
        否则调用HttpTester类的post方法将会将secretStr编码成为无效码,造成提交预定请求失败
        :param secretStr: 提交车次加密
        :return:
        """
        submit_station_url = self.session.urls["submit_station_url"]
        submitResult = self.session.httpClint.send(submit_station_url, self.data_apr())
        if 'data' in submitResult and submitResult['data']:
            if submitResult['data'] == 'N':
                coi = checkOrderInfo(self.session, self.train_no, self.set_type, self.passengerTicketStrList,
                                     self.oldPassengerStr,
                                     self.train_date, self.ticke_peoples)
                coi.sendCheckOrderInfo()
            else:
                print (u'出票失败')
        elif 'messages' in submitResult and submitResult['messages']:
            raise ticketIsExitsException(submitResult['messages'][0])


class submitOrderRequestByAfterNate:
    def __init__(self, session, secretList, tickerNo):
        """
        提交候补订单
        :param secretList:
        :param session:
        """
        self.secretList = secretList
        self.session = session
        self.tickerNo = tickerNo

    def data_apr(self):
        """
        secretList	9vqa9%2B%2F%2Fsdozmm22hpSeDTGqRUwSuA2D0r%2BmU%2BLZj7MK7CDuf5Ep1xpxl4Dyxfmoah%2BaB9TZSesU%0AkxBbo5oNgR1vqMfvq66VP0T7tpQtH%2BbVGBz1FolZG8jDD%2FHqnz%2FnvdBP416Og6WGS14O%2F3iBSwT8%0AkRPsNF0Vq0U082g0tlJtP%2BPn7TzW3z7TDCceMJIjFcfEOA%2BW%2BuK%2Bpy6jCQMv0TmlkXf5aKcGnE02%0APuv4I8nF%2BOWjWzv9CrJyiCZiWaXd%2Bi7p69V3a9dhF787UgS660%2BqKRFB4RLwAfic3MkAlfpGWhMY%0ACfARVQ%3D%3D#O|
        _json_att
        候补一次只能补一个座位，默认取TICKET_TYPE第一个
        :return:
        """

        ticker = TickerConfig.PASSENGER_TICKER_STR.get(TickerConfig.SET_TYPE[0])
        data = OrderedDict()
        data["secretList"] = f"{self.secretList}#{ticker}|"
        data["_json_att"] = ""
        return data

    def sendSubmitOrderRequest(self, ):
        submitOrderRequestRsp = self.session.httpClint.send(urls.get("SubmitOrderRequestRsp"), self.data_apr())
        if not submitOrderRequestRsp.get("status") or not submitOrderRequestRsp.get("data", {}).get("flag"):
            print("".join(submitOrderRequestRsp.get("messages")) or submitOrderRequestRsp.get("validateMessages"))
            return
        pApi = passengerInitApi(self.session, self.secretList, self.tickerNo)
        pApi.sendPassengerInitApi()




File: inter/ChechFace.py

import datetime
import urllib
from collections import OrderedDict
from config.urlConf import urls
import TickerConfig
from inter.GetSuccessRate import getSuccessRate
from myException.ticketConfigException import ticketConfigException
import wrapcache


class chechFace:
    def __init__(self, selectObj, secretList, train_no):
        """
        人脸识别
        """
        self.secretList = secretList
        self.session = selectObj
        self.train_no = train_no

    def data_apr(self):
        """
        secretList	9vqa9%2B%2F%2Fsdozmm22hpSeDTGqRUwSuA2D0r%2BmU%2BLZj7MK7CDuf5Ep1xpxl4Dyxfmoah%2BaB9TZSesU%0AkxBbo5oNgR1vqMfvq66VP0T7tpQtH%2BbVGBz1FolZG8jDD%2FHqnz%2FnvdBP416Og6WGS14O%2F3iBSwT8%0AkRPsNF0Vq0U082g0tlJtP%2BPn7TzW3z7TDCceMJIjFcfEOA%2BW%2BuK%2Bpy6jCQMv0TmlkXf5aKcGnE02%0APuv4I8nF%2BOWjWzv9CrJyiCZiWaXd%2Bi7p69V3a9dhF787UgS660%2BqKRFB4RLwAfic3MkAlfpGWhMY%0ACfARVQ%3D%3D#O|
        _json_att
        候补一次只能补一个座位，默认取TICKET_TYPE第一个
        :return:
        """
        ticker = TickerConfig.PASSENGER_TICKER_STR.get(TickerConfig.SET_TYPE[0])
        data = OrderedDict()
        data["secretList"] = f"{self.secretList}#{ticker}|"
        data["_json_att"] = ""
        return data

    def sendChechFace(self):
        chechFaceRsp = self.session.httpClint.send(urls.get("chechFace"), self.data_apr())
        if not chechFaceRsp.get("status"):
            print("".join(chechFaceRsp.get("messages")) or chechFaceRsp.get("validateMessages"))
            wrapcache.set(key=f"hb{self.train_no}", value=datetime.datetime.now(),
                          timeout=TickerConfig.TICKET_BLACK_LIST_TIME * 60)
            return
        data = chechFaceRsp["data"]
        if not data.get("face_flag"):
            print("".join(chechFaceRsp.get("messages")) or chechFaceRsp.get("validateMessages"))
            if data.get("face_check_code") == "14":
                """
                未通过人脸核验
                """
                raise ticketConfigException("通过人证一致性核验的用户及激活的“铁路畅行”会员可以提交候补需求，请您按照操作说明在铁路12306app.上完成人证核验")
            elif data.get("face_check_code") in ["12", "02"]:
                """
                系统忙，请稍后再试！
                """
                print("系统忙，请稍后再试！")
                wrapcache.set(key=f"hb{self.train_no}", value=datetime.datetime.now(),
                              timeout=TickerConfig.TICKET_BLACK_LIST_TIME * 60)
            elif data.get("face_check_code") in ["03", "13"]:
                """
                证件信息审核失败，请检查所填写的身份信息内容与原证件是否一致。
                """
                raise ticketConfigException("证件信息审核失败，请检查所填写的身份信息内容与原证件是否一致。")
            elif data.get("face_check_code") in ["01", "11"]:
                """
                证件信息正在审核中，请您耐心等待，审核通过后可继续完成候补操作。
                """
                print("证件信息正在审核中，请您耐心等待，审核通过后可继续完成候补操作。")
                wrapcache.set(key=f"hb{self.train_no}", value=datetime.datetime.now(),
                              timeout=TickerConfig.TICKET_BLACK_LIST_TIME * 60)
        g = getSuccessRate(self.session, self.secretList)
        g.sendSuccessRate()



File: inter/ConfirmSingleForQueue.py

# coding=utf-8
import datetime
import time

from inter.CheckRandCodeAnsyn import checkRandCodeAnsyn
from inter.GetPassengerDTOs import getPassengerDTOs
from inter.GetRandCode import getRandCode
from inter.QueryOrderWaitTime import queryOrderWaitTime


class confirmSingleForQueue:
    def __init__(self, session, ifShowPassCodeTime, is_node_code, token, set_type, ticket_peoples, ticketInfoForPassengerForm,
                 oldPassengerStr, passengerTicketStrList):
        self.session = session
        self.ifShowPassCodeTime = ifShowPassCodeTime
        self.is_node_code = is_node_code
        self.token = token
        self.set_type = set_type
        self.ticket_peoples = ticket_peoples
        self.ticketInfoForPassengerForm = ticketInfoForPassengerForm
        self.passengerTicketStrList = passengerTicketStrList
        self.oldPassengerStr = oldPassengerStr

    def data_par(self):
        """
        模拟提交订单是确认按钮，参数获取方法还是get_ticketInfoForPassengerForm 中获取
        :return:
        """
        if not self.passengerTicketStrList and not self.oldPassengerStr:
            s = getPassengerDTOs(session=self.session, ticket_peoples=self.ticket_peoples, set_type=self.set_type)
            getPassengerDTOsResult = s.getPassengerTicketStrListAndOldPassengerStr()
            if getPassengerDTOsResult.get("status", False):
                self.passengerTicketStrList = getPassengerDTOsResult.get("passengerTicketStrList", "")
                self.oldPassengerStr = getPassengerDTOsResult.get("oldPassengerStr", "")
        data = {
            "passengerTicketStr": self.passengerTicketStrList.rstrip("_{0}".format(self.set_type)),
            "oldPassengerStr": "".join(self.oldPassengerStr),
            "purpose_codes": self.ticketInfoForPassengerForm["purpose_codes"],
            "key_check_isChange": self.ticketInfoForPassengerForm["key_check_isChange"],
            "leftTicketStr": self.ticketInfoForPassengerForm["leftTicketStr"],
            "train_location": self.ticketInfoForPassengerForm["train_location"],
            "seatDetailType": "",  # 开始需要选择座位，但是目前12306不支持自动选择作为，那这个参数为默认
            "roomType": "00",  # 好像是根据一个id来判断选中的，两种 第一种是00，第二种是10，但是我在12306的页面没找到该id，目前写死是00，不知道会出什么错
            "dwAll": "N",
            "whatsSelect": 1,
            "_json_at": "",
            "randCode": "",
            "choose_seats": "",
            "REPEAT_SUBMIT_TOKEN": self.token,
        }
        return data

    def sendConfirmSingleForQueue(self):
        """
        # 模拟查询当前的列车排队人数的方法
        # 返回信息组成的提示字符串
        :return:
        """
        data = self.data_par()
        checkQueueOrderUrl = self.session.urls["checkQueueOrderUrl"]
        try:
            if self.is_node_code:
                print(u"正在使用自动识别验证码功能")
                for i in range(3):
                    randCode = getRandCode(is_auto_code=True, auto_code_type=2)
                    checkcode = checkRandCodeAnsyn(self.session, randCode, self.token)
                    if checkcode == 'TRUE':
                        print(u"验证码通过,正在提交订单")
                        data['randCode'] = randCode
                        break
                    else:
                        print (u"验证码有误, {0}次尝试重试".format(i + 1))
                print(u"验证码超过限定次数3次，放弃此次订票机会!")
            else:
                print(u"不需要验证码")
            time.sleep(self.ifShowPassCodeTime)
            checkQueueOrderResult = self.session.httpClint.send(checkQueueOrderUrl, data)
            if "status" in checkQueueOrderResult and checkQueueOrderResult["status"]:
                c_data = checkQueueOrderResult["data"] if "data" in checkQueueOrderResult else {}
                if 'submitStatus' in c_data and c_data['submitStatus'] is True:
                    qow = queryOrderWaitTime(self.session)
                    qow.sendQueryOrderWaitTime()
                else:
                    if 'errMsg' in c_data and c_data['errMsg']:
                        print(u"提交订单失败，{0}".format(c_data['errMsg']))
                    else:
                        print(c_data)
                        print(u'订票失败!很抱歉,请重试提交预订功能!')
            elif "messages" in checkQueueOrderResult and checkQueueOrderResult["messages"]:
                print(u"提交订单失败,错误信息: " + checkQueueOrderResult["messages"])
            else:
                print(u"提交订单中，请耐心等待：" + checkQueueOrderResult["message"])
        except ValueError:
            print(u"接口 {} 无响应".format(checkQueueOrderUrl))


File: inter/QueryOrderWaitTime.py

# coding=utf-8
import copy
import time

from config.TicketEnmu import ticket
from config.emailConf import sendEmail
from config.serverchanConf import sendServerChan
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", "")))
                        sendServerChan(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):
                return queryMyOrderNoCompleteResult["data"]
            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:
            return False

    def initNoComplete(self):
        """
        获取订单前需要进入订单列表页，获取订单列表页session
        :return:
        """
        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))
            return False



File: inter/GetRepeatSubmitToken.py

# coding=utf-8
import json
import re


class getRepeatSubmitToken:
    def __init__(self, session):
        self.session = session

    def sendGetRepeatSubmitToken(self):
        """
        获取提交车票请求token
        :return: token
        """
        initdc_url = self.session.urls["initdc_url"]
        initdc_result = self.session.httpClint.send(initdc_url, )
        token_name = re.compile(r"var globalRepeatSubmitToken = '(\S+)'")
        ticketInfoForPassengerForm_name = re.compile(r'var ticketInfoForPassengerForm=(\{.+\})?')
        order_request_params_name = re.compile(r'var orderRequestDTO=(\{.+\})?')
        token = re.search(token_name, initdc_result).group(1)
        re_tfpf = re.findall(ticketInfoForPassengerForm_name, initdc_result)
        re_orp = re.findall(order_request_params_name, initdc_result)
        if re_tfpf:
            ticketInfoForPassengerForm = json.loads(re_tfpf[0].replace("'", '"'))
        else:
            ticketInfoForPassengerForm = ""
        if re_orp:
            order_request_params = json.loads(re_orp[0].replace("'", '"'))
        else:
            order_request_params = ""
        return {
            "token": token,
            "ticketInfoForPassengerForm": ticketInfoForPassengerForm,
            "order_request_params": order_request_params,
            "session": self.session
        }


File: inter/GetPassCodeNewOrderAndLogin.py

# coding=utf-8
import base64
import copy
import random


def getPassCodeNewOrderAndLogin(session, imgType):
    """
    下载验证码
    :param session:
    :param imgType: 下载验证码类型，login=登录验证码，其余为订单验证码
    :return:
    """
    if imgType == "login":
        codeImgUrl = copy.deepcopy(session.urls["getCodeImg"])
        codeImgUrl["req_url"] = codeImgUrl["req_url"].format(random.random())
    else:
        codeImgUrl = copy.deepcopy(session.urls["codeImgByOrder"])
        codeImgUrl["req_url"] = codeImgUrl["req_url"].format(random.random())
    print(u"下载验证码...")
    img_path = './tkcode.png'
    result = session.httpClint.send(codeImgUrl)
    try:
        if isinstance(result, dict):
            print(u"下载验证码失败, 请手动检查是否ip被封，或者重试，请求地址：https://kyfw.12306.cn{}".format(codeImgUrl.get("req_url")))
            return False
        else:
            print(u"下载验证码成功")
            try:
                with open(img_path, 'wb', encoding="utf-8") as img:
                    img.write(result)
            except Exception:
                with open(img_path, 'wb') as img:
                    img.write(result)
            return result
    except OSError:
        print(u"验证码下载失败，可能ip被封，确认请手动请求: {0}".format(codeImgUrl))


def getPassCodeNewOrderAndLogin1(session, imgType):
    """
    获取验证码2
    :param session:
    :param imgType:
    :return:
    """
    if imgType == "login":
        codeImgUrl = copy.deepcopy(session.urls["getCodeImg1"])
        codeImgUrl["req_url"] = codeImgUrl["req_url"].format(random.random())
    else:
        codeImgUrl = copy.deepcopy(session.urls["codeImgByOrder"])
        codeImgUrl["req_url"] = codeImgUrl["req_url"].format(random.random())
    print(u"下载验证码...")
    img_path = './tkcode.png'
    codeImgUrlRsp = session.httpClint.send(codeImgUrl)
    if not isinstance(codeImgUrlRsp, str):
        print("验证码获取失败")
        return
    result = eval(codeImgUrlRsp.split("(")[1].split(")")[0]).get("image")
    try:
        if isinstance(result, dict):
            print(u"下载验证码失败, 请手动检查是否ip被封，或者重试，请求地址：https://kyfw.12306.cn{}".format(codeImgUrl.get("req_url")))
            return False
        else:
            print(u"下载验证码成功")
            try:
                with open(img_path, 'wb', encoding="utf-8") as img:
                    img.write(result)
            except Exception:
                with open(img_path, 'wb') as img:
                    img.write(base64.b64decode(result))
            return result
    except OSError:
        print(u"验证码下载失败，可能ip被封或者文件写入没权限")


if __name__ == '__main__':
    pass



File: inter/GetQueueCount.py

# coding=utf-8
import datetime
import sys
import time
from collections import OrderedDict
import wrapcache

import TickerConfig
from config.TicketEnmu import ticket
from config.emailConf import sendEmail
from config.serverchanConf import sendServerChan
from config.urlConf import urls
from inter.ConfirmSingleForQueue import confirmSingleForQueue
from myException.ticketIsExitsException import ticketIsExitsException


def conversion_int(str):
    return int(str)


class getQueueCount:
    def __init__(self, session, is_need_code, ifShowPassCodeTime, set_type, station_dates, train_no, ticket_peoples,
                 ticketInfoForPassengerForm, token, oldPassengerStr, passengerTicketStrList):
        self.station_dates = station_dates
        self.session = session
        self.is_need_code = is_need_code
        self.ifShowPassCodeTime = ifShowPassCodeTime
        self.set_type = set_type
        self.train_no = train_no
        self.ticket_peoples = ticket_peoples
        self.ticket_black_list = {}
        self.ticketInfoForPassengerForm = ticketInfoForPassengerForm
        self.token = token
        self.oldPassengerStr = oldPassengerStr
        self.passengerTicketStrList = passengerTicketStrList

    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:
        """

        if sys.version_info.major is 2:
            new_train_date = filter(None, str(time.asctime(time.strptime(self.station_dates, "%Y-%m-%d"))).split(" "))
        else:
            new_train_date = list(filter(None, str(time.asctime(time.strptime(self.station_dates, "%Y-%m-%d"))).split(" ")))
        data = OrderedDict()
        data['train_date'] = "{0} {1} {2} {3} 00:00:00 GMT+0800 (中国标准时间)".format(
            new_train_date[0],
            new_train_date[1],
            new_train_date[2] if len(new_train_date[2]) is 2 else f"0{new_train_date[2]}",
            new_train_date[4],
        ),
        data['train_no'] = self.ticketInfoForPassengerForm['queryLeftTicketRequestDTO']['train_no'],
        data['stationTrainCode'] = self.ticketInfoForPassengerForm['queryLeftTicketRequestDTO'][
                                       'station_train_code'],
        data['seatType'] = self.set_type,
        data['fromStationTelecode'] = self.ticketInfoForPassengerForm['queryLeftTicketRequestDTO'][
                                          'from_station'],
        data['toStationTelecode'] = self.ticketInfoForPassengerForm['queryLeftTicketRequestDTO']['to_station'],
        data['leftTicket'] = self.ticketInfoForPassengerForm['leftTicketStr'],
        data['purpose_codes'] = self.ticketInfoForPassengerForm['purpose_codes'],
        data['train_location'] = self.ticketInfoForPassengerForm['train_location'],
        data['REPEAT_SUBMIT_TOKEN'] = self.token,
        return data

    def sendGetQueueCount(self):
        """
        # 模拟查询当前的列车排队人数的方法
        # 返回信息组成的提示字符串
        :return:
        """
        getQueueCountResult = self.session.httpClint.send(self.session.urls["getQueueCountUrl"], self.data_par())
        if "status" in getQueueCountResult and getQueueCountResult["status"] is True:
            if "countT" in getQueueCountResult["data"]:
                ticket = getQueueCountResult["data"]["ticket"]
                ticket_split = sum(map(conversion_int, ticket.split(","))) if ticket.find(",") != -1 else ticket
                countT = getQueueCountResult["data"]["countT"]
                if int(ticket_split) is 0:
                    wrapcache.set(key=self.train_no, value=datetime.datetime.now(),
                                  timeout=TickerConfig.TICKET_BLACK_LIST_TIME * 60)
                    print(f"排队失败，当前余票数还剩: {ticket_split} 张")
                    return
                print(u"排队成功, 你排在: {1}位, 当前余票还剩余: {0} 张".format(ticket_split, countT))
                csf = confirmSingleForQueue(self.session, self.ifShowPassCodeTime, self.is_need_code, self.token,
                                            self.set_type, self.ticket_peoples, self.ticketInfoForPassengerForm,
                                            self.oldPassengerStr, self.passengerTicketStrList)
                csf.sendConfirmSingleForQueue()
            #     else:
            #         print(u"当前排队人数: {1} 当前余票还剩余:{0} 张，继续排队中".format(ticket_split, countT))
            else:
                print(u"排队发现未知错误{0}，将此列车 {1}加入小黑屋".format(getQueueCountResult, self.train_no))
                wrapcache.set(key=self.train_no, value=datetime.datetime.now(),
                              timeout=TickerConfig.TICKET_BLACK_LIST_TIME * 60)
        elif "messages" in getQueueCountResult and getQueueCountResult["messages"]:
            print(u"排队异常，错误信息：{0}, 将此列车 {1}加入小黑屋".format(getQueueCountResult["messages"][0], self.train_no))
            wrapcache.set(key=self.train_no, value=datetime.datetime.now(),
                          timeout=TickerConfig.TICKET_BLACK_LIST_TIME * 60)
        else:
            if "validateMessages" in getQueueCountResult and getQueueCountResult["validateMessages"]:
                print(str(getQueueCountResult["validateMessages"]))
                wrapcache.set(key=self.train_no, value=datetime.datetime.now(),
                              timeout=TickerConfig.TICKET_BLACK_LIST_TIME * 60)
            else:
                print(u"未知错误 {0}".format("".join(getQueueCountResult)))


class queryQueueByAfterNate:
    def __init__(self, session):
        """
        候补排队
        :param session:
        """
        self.session = session

    def sendQueryQueueByAfterNate(self):
        for i in range(10):
            queryQueueByAfterNateRsp = self.session.httpClint.send(urls.get("queryQueue"))
            if not queryQueueByAfterNateRsp.get("status"):
                print("".join(queryQueueByAfterNateRsp.get("messages")) or queryQueueByAfterNateRsp.get("validateMessages"))
                time.sleep(1)
            else:
                sendEmail(ticket.WAIT_ORDER_SUCCESS)
                sendServerChan(ticket.WAIT_ORDER_SUCCESS)
                raise ticketIsExitsException(ticket.WAIT_AFTER_NATE_SUCCESS)


if __name__ == '__main__':
    new_train_date = list(filter(None, str(time.asctime(time.strptime("2019-10-07", "%Y-%m-%d"))).split(" ")))
    print(new_train_date)
    train_date = "{0} {1} {2} {3} 00:00:00 GMT+0800 (中国标准时间)".format(
        new_train_date[0],
        new_train_date[1],
        new_train_date[2] if len(new_train_date[2]) is 2 else f"0{new_train_date[2]}",
        new_train_date[4],
    )
    print(train_date)


File: inter/AutoSubmitOrderRequest.py

# coding=utf-8
import urllib
from collections import OrderedDict

from config.TicketEnmu import ticket
from inter.CheckRandCodeAnsyn import checkRandCodeAnsyn
from inter.GetQueueCountAsync import getQueueCountAsync
from inter.GetRandCode import getRandCode
import TickerConfig


class autoSubmitOrderRequest:
    """
    快读提交订单通道
    """
    def __init__(self, selectObj,
                 secretStr,
                 train_date,
                 query_from_station_name,
                 query_to_station_name,
                 passengerTicketStr,
                 oldPassengerStr,
                 train_no,
                 stationTrainCode,
                 leftTicket,
                 set_type,):
        self.set_type = set_type
        try:
            self.secretStr = urllib.unquote(secretStr)
        except AttributeError:
            self.secretStr = urllib.parse.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.rstrip("_{0}".format(self.set_type))
        self.oldPassengerStr = oldPassengerStr
        self.session = selectObj
        self.train_no = train_no
        self.stationTrainCode = stationTrainCode
        self.leftTicket = leftTicket

    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"] = TickerConfig.FROM_STATION
        data["query_to_station_name"] = TickerConfig.TO_STATION
        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")
                ifShowPassCodeTime = int(requestResultData.get("ifShowPassCodeTime", "1000")) / float(1000)
                print(ticket.AUTO_SUBMIT_ORDER_REQUEST_C)
                g = getQueueCountAsync(session=self.session,
                                       train_no=self.train_no,
                                       stationTrainCode=self.stationTrainCode,
                                       fromStationTelecode=self.query_from_station_name,
                                       toStationTelecode=self.query_to_station_name,
                                       leftTicket=self.leftTicket,
                                       set_type=self.set_type,
                                       users=len(TickerConfig.TICKET_PEOPLES),
                                       station_dates=self.train_date,
                                       passengerTicketStr=self.passengerTicketStr,
                                       oldPassengerStr=self.oldPassengerStr,
                                       result=result,
                                       ifShowPassCodeTime=ifShowPassCodeTime,
                                       )
                if ifShowPassCode == "Y":  # 如果需要验证码
                    print(u"需要验证码")
                    print(u"正在使用自动识别验证码功能")
                    for i in range(3):
                        randCode = getRandCode(is_auto_code=True, auto_code_type=2)
                        checkcode = checkRandCodeAnsyn(self.session, randCode, "")
                        if checkcode == 'TRUE':
                            print(u"验证码通过,正在提交订单")
                            data['randCode'] = randCode
                            break
                        else:
                            print (u"验证码有误, {0}次尝试重试".format(i + 1))
                    print(u"验证码超过限定次数3次，放弃此次订票机会!")
                g.sendGetQueueCountAsync()
        else:
            print(ticket.AUTO_SUBMIT_ORDER_REQUEST_F)
            if autoSubmitOrderRequestResult.get("messages", ""):
                print("".join(autoSubmitOrderRequestResult.get("messages", "")))
            elif autoSubmitOrderRequestResult.get("validateMessages", ""):
                print("".join(autoSubmitOrderRequestResult.get("validateMessages", "")))





File: inter/GetSuccessRate.py

from collections import OrderedDict


from config.urlConf import urls
import TickerConfig
from inter.SubmitOrderRequest import submitOrderRequestByAfterNate


class getSuccessRate:
    def __init__(self, session, secretList):
        """
        获取成功信息
        """
        self.secretList = secretList
        self.session = session

    def data_apr(self):
        """
        secretList	9vqa9%2B%2F%2Fsdozmm22hpSeDTGqRUwSuA2D0r%2BmU%2BLZj7MK7CDuf5Ep1xpxl4Dyxfmoah%2BaB9TZSesU%0AkxBbo5oNgR1vqMfvq66VP0T7tpQtH%2BbVGBz1FolZG8jDD%2FHqnz%2FnvdBP416Og6WGS14O%2F3iBSwT8%0AkRPsNF0Vq0U082g0tlJtP%2BPn7TzW3z7TDCceMJIjFcfEOA%2BW%2BuK%2Bpy6jCQMv0TmlkXf5aKcGnE02%0APuv4I8nF%2BOWjWzv9CrJyiCZiWaXd%2Bi7p69V3a9dhF787UgS660%2BqKRFB4RLwAfic3MkAlfpGWhMY%0ACfARVQ%3D%3D#O
        _json_att
        候补一次只能补一个座位，默认取TICKET_TYPE第一个
        :return:
        """

        ticker = TickerConfig.PASSENGER_TICKER_STR.get(TickerConfig.SET_TYPE[0])
        data = OrderedDict()
        data["successSecret"] = f"{self.secretList}#{ticker}"
        data["_json_att"] = ""
        return data

    def sendSuccessRate(self):
        successRateRsp = self.session.httpClint.send(urls.get("getSuccessRate"), self.data_apr())
        if not successRateRsp.get("status"):
            print("".join(successRateRsp.get("messages")) or successRateRsp.get("validateMessages"))
            return
        flag = successRateRsp.get("data", {}).get("flag")[0]
        train_no = flag.get("train_no")
        print(f"准备提交候补订单，{flag.get('info')}")
        submit = submitOrderRequestByAfterNate(self.session, self.secretList, train_no)
        submit.sendSubmitOrderRequest()




File: inter/CheckRandCodeAnsyn.py

# coding=utf-8
class checkRandCodeAnsyn:
    def __init__(self, session, randCode, token):
        self.session = session
        self.randCode = randCode
        self.token = token

    def data_par(self):
        """
        :return:
        """
        data = {
            "randCode": self.randCode,
            "rand": "randp",
            "_json_att": "",
            "REPEAT_SUBMIT_TOKEN": self.token
        }
        return data

    def sendCheckRandCodeAnsyn(self):
        """
        下单验证码识别
        :return:
        """
        checkRandCodeAnsynUrl = self.session.urls["checkRandCodeAnsyn"]
        fresult = self.session.httpClint.send(checkRandCodeAnsynUrl, self.data_par())  # 校验验证码是否正确
        return fresult['data']['msg']


File: inter/GetPassengerDTOs.py

# coding=utf-8
import json

from config.TicketEnmu import ticket
from myException.PassengerUserException import PassengerUserException
import wrapcache
import TickerConfig


class getPassengerDTOs:
    """
    获取乘客信息
    :return:
    """
    def __init__(self, selectObj, ticket_peoples=None, set_type=None, is_more_ticket_num=None):
        """
        :param session: 登录实例
        :param ticket_peoples: 乘客
        :param set_type: 坐席
        """
        if ticket_peoples is None:
            ticket_peoples = []
        self.session = selectObj
        self.ticket_peoples = ticket_peoples
        self.is_more_ticket_num = is_more_ticket_num
        self.set_type = set_type

    def sendGetPassengerDTOs(self):
        getPassengerDTOsResult = self.session.httpClint.send(self.session.urls["get_passengerDTOs"], json.dumps({"_json_att": ""}))
        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:
                print(u"警告：您的账号可能买票有问题，获取不到联系人，请测试是否能正常下单，在捡漏或者购票！！！")
                print(u"警告：您的账号可能买票有问题，获取不到联系人，请测试是否能正常下单，在捡漏或者购票！！！")
                print(u"警告：您的账号可能买票有问题，获取不到联系人，请测试是否能正常下单，在捡漏或者购票！！！")
                # raise PassengerUserException(ticket.DTO_NOT_FOUND)

    def getPassengerTicketStr(self, set_type):
        """
        获取getPassengerTicketStr 提交对应的代号码
        :param str: 坐席
        :return:
        """
        passengerTicketStr = {
            '一等座': 'M',
            '特等座': 'P',
            '二等座': 'O',
            '商务座': 9,
            '硬座': 1,
            '无座': 1,
            '软座': 2,
            '软卧': 4,
            '硬卧': 3,
        }
        return str(passengerTicketStr[set_type.replace(' ', '')])

    def getPassengerTicketStrListAndOldPassengerStr(self, secretStr, secretList):
        """
        获取提交车次人内容格式
        passengerTicketStr	O,0,1,文贤平,1,43052419950223XXXX,15618715583,N_O,0,1,梁敏,1,43052719920118XXXX,,N
        oldPassengerStr	文贤平,1,43052719920118XXXX,1_梁敏,1,43052719920118XXXX,1
        ps: 如果is_more_ticket打开了的话，那就是读取联系人列表里面前符合车次数量的前几个联系人
        :return:
        """
        passengerTicketStrList = []
        oldPassengerStr = []
        tickers = []
        set_type = ""
        if wrapcache.get("user_info"):  # 如果缓存中有联系人方式，则读取缓存中的联系人
            user_info = wrapcache.get("user_info")
            print(u"使用缓存中查找的联系人信息")
        else:
            user_info = self.sendGetPassengerDTOs()
            wrapcache.set("user_info", user_info, timeout=9999999)
        if not user_info:
            raise PassengerUserException(ticket.DTO_NOT_IN_LIST)
        if len(user_info) < self.is_more_ticket_num:  # 如果乘车人填错了导致没有这个乘车人的话，可能乘车人数会小于自动乘车人
            self.is_more_ticket_num = len(user_info)
        if secretStr:
            set_type = self.getPassengerTicketStr(self.set_type)
            if self.is_more_ticket_num 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,' + user_info[0]["allEncStr"])
                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 range(self.is_more_ticket_num):
                    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,' + user_info[i]["allEncStr"] + '_' + 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'] + '_')
        elif secretList:
            """
            候补订单有多少个联系人，就候补多少个联系人了，没有优先提交之说
            1#XXXX#1#***************77X#bf6ae40d3655ae7eff005ee21d95876b38ab97a8031b464bc2f74a067e3ec957;
            """
            for user in user_info:
                tickers.append(f"1#{user['passenger_name']}#1#{user['passenger_id_no']}#{user['allEncStr']};")

        return {
            "passengerTicketStrList": set_type + "," + ",".join(passengerTicketStrList),
            "passengerTicketStrByAfterLate": "".join(tickers),
            "oldPassengerStr": "".join(oldPassengerStr),
            "code": ticket.SUCCESS_CODE,
            "set_type": set_type,
            "status": True,
            "user_info": user_info,
        }



File: inter/LoginAysnSuggest.py

# coding=utf-8
from config.urlConf import urls


def loginAysnSuggest(session, username, password):
    """
    登录接口
    ps: 不需要验证码
    :return:
    """
    loginAysnSuggestUrls = urls.get("loginAysnSuggest")
    data = {
        "loginUserDTO.user_name": username,
        "userDTO.password":	password
    }
    loginAysnSuggestRsp = session.httpClint.send(urls=loginAysnSuggestUrls, data=data)
    if loginAysnSuggestRsp and loginAysnSuggestRsp.get("httpstatus") is 200 and loginAysnSuggestRsp.get("data", {}).get("loginCheck") == "Y":
        print(u"登录成功")
    else:
        print(u"登录失败, {0} {1}".format("".join(loginAysnSuggestRsp.get("messages")), loginAysnSuggestRsp.get("validateMessages")))



File: inter/GetRandCode.py

# coding=utf-8
from PIL import Image

from config.urlConf import urls
from myUrllib.httpUtils import HTTPClient
from verify.localVerifyCode import Verify
import TickerConfig
import os


if TickerConfig.AUTO_CODE_TYPE == 2:
    v = Verify()


def getRandCode(is_auto_code, auto_code_type, result):
    """
    识别验证码
    :return: 坐标
    """
    try:
        if is_auto_code:
            if auto_code_type == 1:
                print(u"打码兔已关闭, 如需使用自动识别，请使用如果平台 auto_code_type == 2")
                return
            elif auto_code_type == 2:
                Result = v.verify(result)
                return codexy(Ofset=Result, is_raw_input=False)
            elif auto_code_type == 3:
                print("您已设置使用云打码，但是服务器资源有限，请尽快改为本地打码" if "CAPTCHALOCAL" not in os.environ else "已设置本地打码服务器")
                http = HTTPClient(0)
                Result = http.send(urls.get("autoVerifyImage"), {"imageFile": result})
                if Result and Result.get("code") is 0:
                    return codexy(Ofset=Result.get("data"), is_raw_input=False)
        else:
            img = Image.open('./tkcode.png')
            img.show()
            return codexy()
    except Exception as e:
        print(e)


def codexy(Ofset=None, is_raw_input=True):
    """
    获取验证码
    :return: str
    """
    if is_raw_input:
        print(u"""
            *****************
            | 1 | 2 | 3 | 4 |
            *****************
            | 5 | 6 | 7 | 8 |
            *****************
            """)
        print(u"验证码分为8个，对应上面数字，例如第一和第二张，输入1, 2  如果开启cdn查询的话，会冲掉提示，直接鼠标点击命令行获取焦点，输入即可，不要输入空格")
        print(u"如果是linux无图形界面，请使用自动打码，is_auto_code: True")
        print(u"如果没有弹出验证码，请手动双击根目录下的tkcode.png文件")
        Ofset = input(u"输入对应的验证码: ")
    if isinstance(Ofset, list):
        select = Ofset
    else:
        Ofset = Ofset.replace("，", ",")
        select = Ofset.split(',')
    post = []
    offsetsX = 0  # 选择的答案的left值,通过浏览器点击8个小图的中点得到的,这样基本没问题
    offsetsY = 0  # 选择的答案的top值
    for ofset in select:
        if ofset == '1':
            offsetsY = 77
            offsetsX = 40
        elif ofset == '2':
            offsetsY = 77
            offsetsX = 112
        elif ofset == '3':
            offsetsY = 77
            offsetsX = 184
        elif ofset == '4':
            offsetsY = 77
            offsetsX = 256
        elif ofset == '5':
            offsetsY = 149
            offsetsX = 40
        elif ofset == '6':
            offsetsY = 149
            offsetsX = 112
        elif ofset == '7':
            offsetsY = 149
            offsetsX = 184
        elif ofset == '8':
            offsetsY = 149
            offsetsX = 256
        else:
            pass
        post.append(offsetsX)
        post.append(offsetsY)
    randCode = str(post).replace(']', '').replace('[', '').replace("'", '').replace(' ', '')
    print(u"验证码识别坐标为{0}".format(randCode))
    return randCode



File: inter/ConfirmHB.py

from collections import OrderedDict
from config.urlConf import urls
import TickerConfig
from inter.GetQueueCount import queryQueueByAfterNate


class confirmHB:
    def __init__(self, secretList, session, tickerNo, jzdhDate):
        """
        人脸识别
        """
        self.secretList = secretList
        self.session = session
        self.passengerTicketStrByAfterLate = session.passengerTicketStrByAfterLate
        self.tickerNo = tickerNo
        self.jzdhDate = jzdhDate

    def data_apr(self):
        """
        passengerInfo	1#XXXX#1#***************77X#bf6ae40d3655ae7eff005ee21d95876b38ab97a8031b464bc2f74a067e3ec957;
        jzParam	2019-08-31#19#00
        hbTrain	5l000G177230,O#
        lkParam
        :return:
        """
        ticker = TickerConfig.PASSENGER_TICKER_STR.get(TickerConfig.SET_TYPE[0])
        data = OrderedDict()
        data["passengerInfo"] = self.passengerTicketStrByAfterLate
        data["jzParam"] = self.jzdhDate
        data["hbTrain"] = f"{self.tickerNo},{ticker}#"
        data["lkParam"] = ""
        return data

    def sendChechFace(self):
        ChechFaceRsp = self.session.httpClint.send(urls.get("confirmHB"), self.data_apr())
        if not ChechFaceRsp.get("status"):
            print("".join(ChechFaceRsp.get("messages")) or ChechFaceRsp.get("validateMessages"))
            return
        data = ChechFaceRsp.get("data")
        if not data.get("flag"):
            print(f"错误信息：{data.get('msg')}")
            return
        queue = queryQueueByAfterNate(self.session)
        queue.sendQueryQueueByAfterNate()







File: inter/GetQueueCountAsync.py

import TickerConfig

[]# coding=utf-8
import datetime
import sys
import time
from collections import OrderedDict

import wrapcache

from inter.ConfirmSingleForQueueAsys import confirmSingleForQueueAsys


class getQueueCountAsync:
    """
    排队
    """
    def __init__(self,
                 session,
                 train_no,
                 stationTrainCode,
                 fromStationTelecode,
                 toStationTelecode,
                 leftTicket,
                 set_type,
                 users,
                 station_dates,
                 passengerTicketStr,
                 oldPassengerStr,
                 result,
                 ifShowPassCodeTime):
        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
        self.station_dates = station_dates
        self.passengerTicketStr = passengerTicketStr
        self.oldPassengerStr = oldPassengerStr
        self.result = result
        self.ifShowPassCodeTime=ifShowPassCodeTime

    def data_par(self):
        """
         - 字段说明
            - train_date 时间
            - train_no 列车编号,查询代码里面返回
            - stationTrainCode 列车编号
            - seatType 对应坐席
            - fromStationTelecode 起始城市
            - toStationTelecode 到达城市
            - leftTicket 查询代码里面返回
            - purpose_codes 学生还是成人
            - _json_att 没啥卵用，还是带上吧
        :return:
        """
        if sys.version_info.major is 2:
            new_train_date = filter(None, str(time.asctime(time.strptime(self.station_dates, "%Y-%m-%d"))).split(" "))
        else:
            new_train_date = list(filter(None, str(time.asctime(time.strptime(self.station_dates, "%Y-%m-%d"))).split(" ")))
        data = OrderedDict()
        data['train_date'] = "{0} {1} {2} {3} 00:00:00 GMT+0800 (中国标准时间)".format(
            new_train_date[0],
            new_train_date[1],
            new_train_date[2] if len(new_train_date[2]) is 2 else f"0{new_train_date[2]}",
            new_train_date[4],
            time.strftime("%H:%M:%S", time.localtime(time.time()))
        ),
        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
                    if int(ticket_split) is 0:
                        # 增加余票数为0时，将车次加入小黑屋
                        wrapcache.set(key=self.train_no, value=datetime.datetime.now(),
                                      timeout=TickerConfig.TICKET_BLACK_LIST_TIME * 60)
                        print(f"排队失败，当前余票数为{ticket_split}张")
                        return
                    print(u"排队成功, 当前余票还剩余: {0} 张".format(ticket_split))
                    c = confirmSingleForQueueAsys(session=self.session,
                                                  passengerTicketStr=self.passengerTicketStr,
                                                  oldPassengerStr=self.oldPassengerStr,
                                                  result=self.result,)
                    print(u"验证码提交安全期，等待{}MS".format(self.ifShowPassCodeTime))
                    time.sleep(self.ifShowPassCodeTime)
                    c.sendConfirmSingleForQueueAsys()
                else:
                    print(u"排队发现未知错误{0}，将此列车 {1}加入小黑屋".format(getQueueCountAsyncResult, self.train_no))
                    wrapcache.set(key=self.train_no, value=datetime.datetime.now(),
                                  timeout=TickerConfig.TICKET_BLACK_LIST_TIME * 60)
            elif "messages" in getQueueCountAsyncResult and getQueueCountAsyncResult["messages"]:
                print(u"排队异常，错误信息：{0}, 将此列车 {1}加入小黑屋".format(getQueueCountAsyncResult["messages"][0], self.train_no))
                wrapcache.set(key=self.train_no, value=datetime.datetime.now(),
                              timeout=TickerConfig.TICKET_BLACK_LIST_TIME * 60)
            else:
                if "validateMessages" in getQueueCountAsyncResult and getQueueCountAsyncResult["validateMessages"]:
                    print(str(getQueueCountAsyncResult["validateMessages"]))






File: myException/ticketNumOutException.py

class ticketNumOutException(Exception):
    pass


File: myException/UserPasswordException.py

class UserPasswordException(Exception):
    pass


File: myException/ticketConfigException.py

class ticketConfigException(Exception):
    pass


File: myException/__init__.py




File: myException/ticketIsExitsException.py

class ticketIsExitsException(Exception):
    pass


File: myException/balanceException.py

class balanceException(Exception):
    pass


File: myException/PassengerUserException.py

class PassengerUserException(Exception):
    pass


File: tmp/__init__.py




File: tmp/log/__init__.py




### 12306 购票小助手
#### python版本
  - [ ] 2.7.10 - 2.7.15
  - [x] 3.6 - 3.7.4
  - [ ] 2.7.9

#### 已有功能
  - [x] 自动打码
  - [x] 自动登录
  - [x] 准点预售和捡漏
  - [x] 智能候补
  - [x] 邮件通知
  - [x] server酱通知

#### 依赖库
  - 验证码目前可以本地识别，需要下载模型，放于项目根目录，全部代码来源于此项目 [传送门](https://github.com/zhaipro/easy12306)，表示感谢
    ```
      1. 模型下载链接:https://pan.baidu.com/s/1rS155VjweWVWIJogakechA  密码:bmlm
         群里面也可以下载
      2. git仓库下载：https://github.com/testerSunshine/12306model.git
    ```
  - 自托管云打码服务器搭建：[12306_code_server](https://github.com/YinAoXiong/12306_code_server)
    - 如果大家有空闲的服务器，可搭建之后在这个 [issues](https://github.com/testerSunshine/12306/issues/446) 里面填入自己的服务器(请注意服务器安全！)
  - 项目依赖 [requirements.txt](requirements.txt)
  - 安装方法x:
      - root用户(避免多python环境产生问题): `pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt`
      - 非root用户（避免安装和运行时使用了不同环境）: `pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt`
      - 许多windows的用户装不了tensorflow的话，可以适当降低版本或者升高版本都是可以的
        ```
        1. tensorflow的兼容版本 1.14.0rc\1.14.0rc\1.15.0\1.15.0rc
        以上版本都测试无问题
        2. 如果pip代理的清华源无法下载，可以更换其他源解决此问题
        ```

#### 项目使用说明

  - 服务器启动:
      - 修改[配置](TickerConfig.py)文件
        - 可以配置邮箱,配置邮箱的格式在[配置](TickerConfig.py)里面可以看到ex
            ```
            # 测试邮箱和server酱是否可用， server酱测试的前提是server酱开关开启
            # 可以配置server酱提醒（推荐）[配置教程](https://www.jianshu.com/p/8d10b5b9c4e3)
            # 用python3 还是python 完全取决于安装的时候配置的环境变量是否为python3,以下启动默认环境变量为python3
            python3 run.py t
            ```
        - 配置[配置](TickerConfig.py)文件的时候，需注意空格和遵循python语法格式
      - 启动前请先筛选cdn，这点很`重要`
        ```
        python3 run.py c
        ```
      - 启动服务
        ```
        python3 run.py r
        ```
      - 如果你不知道如何操作，下面的命令可能会帮助你
        ```
        python3 run.py -h

        ——————————————————————————
        sage: run.py [-h] operate

        positional arguments:
          operate     r: 运行抢票程序, c: 过滤cdn, t: 测试邮箱和server酱，server酱
        ```
  - 如果你的服务器安装了docker与docker-compose, 那么你可以忽略上面的**所有**步骤，直接按以下步骤操作，即可开始抢票：
      - 前提条件:
          - 请确认你安装的docker版本为18.09及以上: `docker -v`
          - 请确认你安装的docker-compose版本为1.23.2及以上: `docker-compose -v`
          - 请根据自己需要修改好配置文件:`TickerConfig.py`
          - 请修改配置文件`TickerConfig.py`中的变量`AUTO_CODE_TYPE`和`HOST`，`AUTO_CODE_TYPE`改为`3`, HOST改为`"captcha:80"`（这里很重要，这是本地打码服务器的配置）
      - 运行命令:
          - 开始抢票：`docker-compose up --build -d`
          - 停止抢票：`docker-compose down`
          - 查看抢票log: `docker logs --follow ticket`

#### 目录对应说明
  - agency - cdn代理
  - config - 项目配置
  - verify - 自动打码
  - init - 项目主运行目录
  - inter - 接口
  - myException - 异常
  - myUrllib  request网络请求库

#### 思路图
- ![image](uml/uml.png)

#### 项目声明：
  - 本软件只供学习交流使用，勿作为商业用途，交流群号
    - 1群：286271084(已满)
    - 2群：649992274(已满)
    - 3群：632501142(已满)
    - 4群: 606340519(已满)
    - 5群: 948526733(已满)
    - 7群: 660689659(已满)
    - 8群: 620629239(已满)
    - 6群: 608792930(未满)
    - 9群: 693035807(未满)
  - 请不要重复加群，一个群就可以了，把机会留给更多人
  - **进群先看公告！！！进群先看公告！！！进群先看公告！！！ 重要的事情说三遍**
  - 能为你抢到一张回家的票，是我最大的心愿

#### 日志列子
   - 成功log，如果是购票失败的，请带上失败的log给我，我尽力帮你调，也可加群一起交流，程序只是加速买票的过程，并不一定能买到票
        ```
        正在第355次查询  乘车日期: 2018-02-12  车次G4741,G2365,G1371,G1377,G1329 查询无票  代理设置 无  总耗时429ms
        车次: G4741 始发车站: 上海 终点站: 邵阳 二等座:有
        正在尝试提交订票...
        尝试提交订单...
        出票成功
        排队成功, 当前余票还剩余: 359 张
        正在使用自动识别验证码功能
        验证码通过,正在提交订单
        提交订单成功！
        排队等待时间预计还剩 -12 ms
        排队等待时间预计还剩 -6 ms
        排队等待时间预计还剩 -7 ms
        排队等待时间预计还剩 -4 ms
        排队等待时间预计还剩 -4 ms
        恭喜您订票成功，订单号为：EB52743573, 请立即打开浏览器登录12306，访问‘未完成订单’，在30分钟内完成支付！
        ```
#### 使用帮助(一些安装问题和使用反馈较多的问题)：
   - 测试邮箱是否可用 [邮箱配置问题看issues](https://github.com/testerSunshine/12306/issues/107)
   - 学生票issues [学生票修改](https://github.com/testerSunshine/12306/issues/47)
   - 依赖安装不对的问题（ImportError）[requirements.txt问题](https://github.com/testerSunshine/12306/issues/91)
   - 若快豆子疑问 [点我](https://github.com/testerSunshine/12306/issues/67)
   - IOError: 【Errno 0】 Error 问题 [点我](https://github.com/testerSunshine/12306/issues/159)
    
   - 测试下单接口是否可用，有两个下单接口，随便用哪个都ok
   - 如果下载验证码过期或者下载失败的问题，应该是12306封ip的策略，多重试几次，12306现在封服务器(阿里云和腾讯云)ip比较严重，尽量不要放在服务器里面
   - 目前12306对服务器ip比较敏感，大家还是在自己家里挂着吧
   - 自动更换ip软件目前已支持TPLINK和小米路由器，只限家庭网络[点我跳转](https://github.com/testerSunshine/AutoRouterIP)


#### 感谢一下小伙伴对本项目提供的帮助
   - @sun7127@126.com
   - @ 才
   - @[MonsterTan](https://github.com/MonsterTan)
   - 以及所有为此项目提供pr的同学
#### 更新日志
   - [更新日志](Update.md)
