12306自动抢票及自动识别验证码功能(二)

这几天用keras+tensorflow训练了下验证码,弄了一个gpu的显卡,训练速度杠杠的^_^, 准度直接提升了几个档次,(小白阶段只会用框架^_^),图片识别准度基本达到96%,文字识别无限接近100%,但是在12306认证的过程中,很多时候都出现及时图片识别都正确,还是认证失败的问题。目前测试了下帮朋友抢到几张票,感觉有点成就感^_^

12306图片验证测试接口:http://www.xiuler.com/test

12306自动抢票及自动识别验证码功能(二)_第1张图片12306自动抢票及自动识别验证码功能(二)_第2张图片

我在认证失败的图片加上了识别结果拿出来分析,基本上图片识别都是正确的

12306自动抢票及自动识别验证码功能(二)_第3张图片

以上训练框架,我参考了 https://github.com/libowei1213/12306_captcha基于深度学习识别12306验证码,感谢分享的文章,在此中获益良多,其实目前12306抢票脚本已经很多,我随便找了个,然后稍稍改进了下加上图形界面,有些东西可以自行调整,里面用了上面提供的框架keras+tensorflow,里面采用了当前效果最好的卷积网络模型之一:DenseNet,很强大

主要实现代码附上: (目前只是为了研究所写,代码质量可以忽略……)

12306pass.py

#coding:utf-8
import requests
import random,re,time,threading,uuid
import http.cookiejar as HC
import json
from drawPro import *
from push_progress import *
from PySide2.QtWidgets import *
from PySide2.QtCore import *
from PySide2.QtGui import *
from code12306 import *

class SignWidget(QDialog):
    txtInfo=Signal(str)
    def __init__(self, parent = None):
        super(SignWidget, self).__init__(parent)
        self.setAttribute(Qt.WA_QuitOnClose, True)
        # 禁用安全请求警告
        requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
        self.session = requests.session()
        self.session.cookies = HC.LWPCookieJar(filename='cookies')
        try:
            self.session.cookies.load(ignore_discard=True)
        except:
            print('未找到cookies文件')
        self.session.headers = {
            'Host': 'kyfw.12306.cn',
            'Origin': 'https://kyfw.12306.cn',
            'X-Requested-With': 'XMLHttpRequest',
            'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
            'Referer': 'https://kyfw.12306.cn/otn/login/init',
            'Accept': '*/*',
            'Accept-Encoding': 'gzip, deflate, br',
            'Accept-Language': 'zh-CN,zh;q=0.8',
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.73 Safari/537.36',
        }
        self.session.verify = False

        self.stationNameToCode = dict()
        self.stationCodeToName = dict()
        self.stationDownloadName="stationCode.txt"
        self.trainDate = ''
        self.fromStationName = ''
        self.fromStationCode = ''
        self.toStationName = ''
        self.toStationCode = ''
        self.fromStationTelecode = ''
        self.toStationTelecode = ''
        self.trainSecretStr = ''
        self.trainNo = ''
        self.trainCode = ''
        self.leftTicket = ''
        self.seatType = ''
        self.trainLocation = ''
        self.submitToken = ''
        self.passengerTicketStr = ''
        self.oldPassengerStr = ''
        self.orderId = ''
        self.stoppiao=False
        self.seatMap = {'无座': '1', '硬座': '1', '硬卧': '3', '软卧': '4', '高级软卧': '6', '动卧': 'F', '二等座': 'O', '一等座': 'M',
                     '商务座': '9'}
        self.canChooseSeat = dict()
        self.textcon=""
        self.captchaDownloadName="temp/"
        self.proxy={
             'http': self.getnewip()
        }
        self.initStations()
        # 批量待签章按钮
        self.login_button = QPushButton()
        self.login_button.setStyleSheet(
            'QPushButton{border:1px solid #3678b5;color:#ffffff;background:#3678b5}'
            'QPushButton:hover{background:#5098ca}'
        )
        self.login_button.setText(u'登陆')
        self.login_button.setFixedSize(50, 30)
        self.login_button.setCursor(Qt.PointingHandCursor)
        self.login_button.clicked.connect(self.loginthread)

        # 测试按钮
        self.test_button = QPushButton()
        self.test_button.setStyleSheet(
            'QPushButton{border:1px solid #3678b5;color:#ffffff;background:#3678b5}'
            'QPushButton:hover{background:#5098ca}'
        )
        self.test_button.setText(u'测试')
        self.test_button.setFixedSize(50, 30)
        self.test_button.setCursor(Qt.PointingHandCursor)
        self.test_button.clicked.connect(self.test)
        # 开始抢票按钮
        self.sub_button = QPushButton()
        self.sub_button.setStyleSheet(
            'QPushButton{border:1px solid #3678b5;color:#ffffff;background:#3678b5}'
            'QPushButton:hover{background:#5098ca}'
        )
        self.sub_button.setText(u'开始抢票')
        self.sub_button.setFixedSize(50, 30)
        self.sub_button.setCursor(Qt.PointingHandCursor)
        self.sub_button.clicked.connect(self.submitpaker)

        # 停止抢票
        self.stop_button = QPushButton()
        self.stop_button.setStyleSheet(
            'QPushButton{border:1px solid #3678b5;color:#ffffff;background:#3678b5}'
            'QPushButton:hover{background:#5098ca}'
        )
        self.stop_button.setText(u'停止')
        self.stop_button.setFixedSize(50, 30)
        self.stop_button.setCursor(Qt.PointingHandCursor)
        self.stop_button.clicked.connect(self.stoptrain)

        self.textprint = QTextEdit()
        self.textprint.setStyleSheet(
            "QTextEdit{border:1px solid #e1e1e1;}"
        )
        self.textprint.setText(self.textcon)
        self.textprint.setReadOnly(True)

        usernamelabel = QLabel()
        usernamelabel.setText("用户名: ")

        self.usernameedit = QLineEdit()
        self.usernameedit.setStyleSheet(
            "QLineEdit{border:1px solid #e1e1e1;}"
        )
        self.usernameedit.setText("")
        self.usernameedit.setFixedSize(120, 30)

        pwdlabel = QLabel()
        pwdlabel.setText("密码: ")
        self.pwdedit = QLineEdit()
        self.pwdedit.setStyleSheet(
            "QLineEdit{border:1px solid #e1e1e1;}"
        )
        self.pwdedit.setText("")
        self.pwdedit.setEchoMode(QLineEdit.Password)
        self.pwdedit.setAttribute(Qt.WA_InputMethodEnabled, False)
        self.pwdedit.setFixedSize(100, 30)

        pushlabel = QLabel()
        pushlabel.setText("刷新频率: ")

        self.pushedit = QLineEdit()
        self.pushedit.setStyleSheet(
            "QLineEdit{border:1px solid #e1e1e1;}"
        )
        self.pushedit.setText("0.5")
        self.pushedit.setFixedSize(50, 30)

        beginStationlabel= QLabel()
        beginStationlabel.setText("起点站: ")
        self.beginStationedit = QLineEdit()
        self.beginStationedit.setStyleSheet(
            "QLineEdit{border:1px solid #e1e1e1;}"
        )
        self.beginStationedit.setText("深圳北")
        self.beginStationedit.setFixedSize(100, 30)

        endStationlabel = QLabel()
        endStationlabel.setText("终点站: ")
        self.endStationedit = QLineEdit()
        self.endStationedit.setStyleSheet(
            "QLineEdit{border:1px solid #e1e1e1;}"
        )
        self.endStationedit.setText("长沙南")
        self.endStationedit.setFixedSize(100, 30)

        begintimelabel = QLabel()
        begintimelabel.setText("坐车时间: ")
        self.begintimeedit = QLineEdit()
        self.begintimeedit.setStyleSheet(
            "QLineEdit{border:1px solid #e1e1e1;}"
        )
        self.begintimeedit.setText("2019-01-29")
        self.begintimeedit.setFixedSize(100, 30)
        seatlabel = QLabel()
        seatlabel.setText("座位: ")
        self.seatedit = QLineEdit()
        self.seatedit.setStyleSheet(
            "QLineEdit{border:1px solid #e1e1e1;}"
        )
        self.seatedit.setText("二等座")
        self.seatedit.setFixedSize(100, 30)

        userlabel = QLabel()
        userlabel.setText("乘车人: ")
        self.useredit = QLineEdit()
        self.useredit.setStyleSheet(
            "QLineEdit{border:1px solid #e1e1e1;}"
        )
        self.useredit.setText("李大厨")
        self.useredit.setFixedSize(100, 30)

        self.notseat=QCheckBox()
        self.notseat.setText("支持无座")
        # 加载列表
        self.createtable()

        # 脚部信息
        self.footlabel = QLabel()
        self.footlabel.setFixedHeight(14)
        self.footlabel.setText("欢迎进入")
        self.footlabel.setStyleSheet(
            'QLabel{color:#999;}'
        )

        self.trainlistnums=QListWidget()
        self.trainlistnums.setStyleSheet(
            "QListWidget{border:1px solid #e1e1e1;}"
        )
        self.trainlistnums.setFixedWidth(150)
        self.trainlistnums.itemDoubleClicked.connect(self.delitem)

        main_button_lyout = QHBoxLayout()
        main_button_lyout.addWidget(usernamelabel)
        main_button_lyout.addWidget(self.usernameedit)
        main_button_lyout.addWidget(pwdlabel)
        main_button_lyout.addWidget(self.pwdedit)
        main_button_lyout.addWidget(self.login_button)
        main_button_lyout.addWidget(pushlabel)
        main_button_lyout.addWidget(self.pushedit)
        main_button_lyout.addStretch(1)
        main_button_lyout.addWidget(self.test_button)
        main_button_lyout.addWidget(self.sub_button)
        main_button_lyout.addWidget(self.stop_button)

        main_two_lyout=QHBoxLayout()
        main_two_lyout.addWidget(beginStationlabel)
        main_two_lyout.addWidget(self.beginStationedit)
        main_two_lyout.addWidget(endStationlabel)
        main_two_lyout.addWidget(self.endStationedit)
        main_two_lyout.addWidget(begintimelabel)
        main_two_lyout.addWidget(self.begintimeedit)
        main_two_lyout.addWidget(seatlabel)
        main_two_lyout.addWidget(self.seatedit)
        main_two_lyout.addWidget(userlabel)
        main_two_lyout.addWidget(self.useredit)
        main_two_lyout.addWidget(self.notseat)
        main_two_lyout.addStretch(1)

        main_three_lyout=QHBoxLayout()
        main_three_lyout.addWidget(self.trainlistnums)
        main_three_lyout.addWidget(self.textprint)

        self.pro = PushProgress()
        self.pro.setFixedHeight(8)

        self.pro.setValue(0)
        self.pro.hide()

        main_lyout = QVBoxLayout()
        main_lyout.addLayout(main_button_lyout)
        main_lyout.addLayout(main_two_lyout)
        main_lyout.addWidget(self.pro)
        main_lyout.addWidget(self.MyTable)
        main_lyout.addLayout(main_three_lyout)
        main_lyout.addWidget(self.footlabel)

        self.main_content = QVBoxLayout()
        self.main_content.addLayout(main_lyout)
        self.main_content.setContentsMargins(5, 5, 5, 5)

        self.main_layout = QVBoxLayout()
        self.main_layout.addLayout(self.main_content)
        self.setLayout(self.main_layout)
        self.resize(1000,600)

        self.txtInfo.connect(self.printinfo)

        self.code12306=code12306()
        self.text_model,self.image_model=self.code12306.load_model()
        self.label_dict=self.code12306.load_label_dict()

    #停止抢票
    def stoptrain(self):
        self.stoppiao=True
    # 打印信息
    def printinfo(self, txt):
        print("txt:",txt)
        self.textcon = txt + "\r\n" + self.textcon
        self.textprint.setText(self.textcon)

    # 创建TABLE
    def createtable(self):
        self.MyTable = QTableView()
        self.MyTable.setStyleSheet(
            'QTableView{selection-color:white;}QTableView:item:selected{background:#fff;color:#000;}QTableView:item:selected:!enabled{ background:transparent;}QTableView:item{border-bottom:1px solid #ddd;padding:10px;}')
        self.MyTable.horizontalHeader().setStyleSheet(
            'QHeaderView:section{background:#f2f2f2;border:none; height:40px; border-right:1px solid #dddddd;border-bottom:1px solid #dddddd}')
        self.MyTable.setShowGrid(False)

        self.MyTable.setFocusPolicy(Qt.NoFocus)
        self.MyTable.setFrameShape(QFrame.NoFrame)
        self.MyTable.horizontalHeader().setHighlightSections(False)
        self.MyTable.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.MyTable.verticalHeader().setVisible(False)
        self.MyTable.verticalHeader().setDefaultSectionSize(40)
        self.MyTable.horizontalHeader().setStretchLastSection(True)

        model = QStandardItemModel(self.MyTable)
        model.setHorizontalHeaderItem(0, QStandardItem(u"车次"))
        model.setHorizontalHeaderItem(1, QStandardItem(u"出发站\n到达站"))
        model.setHorizontalHeaderItem(2, QStandardItem(u"出发时间\n到达时间"))
        model.setHorizontalHeaderItem(3, QStandardItem(u"历时"))
        model.setHorizontalHeaderItem(4, QStandardItem(u"商务座\n特等座"))
        model.setHorizontalHeaderItem(5, QStandardItem(u"一等座"))
        model.setHorizontalHeaderItem(6, QStandardItem(u"二等座"))
        model.setHorizontalHeaderItem(7, QStandardItem(u"软卧\n一等卧"))
        model.setHorizontalHeaderItem(8, QStandardItem(u"硬卧\n二等卧"))
        model.setHorizontalHeaderItem(9, QStandardItem(u"硬座"))
        model.setHorizontalHeaderItem(10, QStandardItem(u"无座"))
        model.setHorizontalHeaderItem(11, QStandardItem(u"操作"))

        self.tableOperation = OperationDelegate(self.MyTable)
        self.MyTable.setItemDelegateForColumn(11, self.tableOperation)
        self.MyTable.setModel(model)
        # 信号与槽
        self.tableOperation.addtrain.connect(self.addtrainnum)
        # self.MyTable.horizontalHeader().resizeSection(0, 220)
        # self.MyTable.horizontalHeader().resizeSection(1, 280)
        # self.MyTable.horizontalHeader().resizeSection(2, 60)
        # self.MyTable.horizontalHeader().resizeSection(5, 90)
        # self.MyTable.horizontalHeader().resizeSection(3, 150)
        # self.MyTable.setColumnHidden(6, True)
        # self.MyTable.setColumnHidden(7, True)
        # self.MyTable.setColumnHidden(8, True)
    def delitem(self,item):
        index=self.trainlistnums.currentIndex().row()
        self.trainlistnums.takeItem(index)

    def addtrainnum(self,i):
        print(i)
        filterTrain=self.filterTrainList[i]
        trainDetailSplit = filterTrain.split('|')
        trainnum=trainDetailSplit[3]
        if not self.checknum(trainnum):
            self.trainlistnums.addItem(trainnum)

    def checknum(self,trainnum):
        ishave=False
        for i in range(self.trainlistnums.count()):
            if trainnum == self.trainlistnums.item(i).text():
                ishave=True
                break
        return ishave

    #获取抢票车次
    def gettrainnums(self):
        tnums=[]
        for i in range(self.trainlistnums.count()):
            trainnum = self.trainlistnums.item(i).text()
            tnums.append(trainnum)
        return tnums

    def test(self):
        row_count = self.MyTable.model().rowCount()
        self.MyTable.model().removeRows(0, row_count)
        self.MyTable.model().setRowCount(0)
        self.findTicket()

        # url = 'https://www.12306.cn/index/'
        # proxy = {
        #     'http': '111.181.54.207:9999'
        # }
        # response = self.session.get(url, data=None, proxies=proxy)
        # result = response.text
        # print(result)

    #读取iptxt文件
    def loadtxt(self):
        f = open("ip.txt")  # 返回一个文件对象
        realips = []
        line = f.readline()  # 调用文件的 readline()方法
        while line:
            line = f.readline()
            line = re.sub('\n', '', line)
            if (line != ''):
                realips.append(line)
        f.close()
        return realips
    # def loadtxt(self):
    #     realips = []
    #     try:
    #         r = urllib.request.urlopen(self.ipurl,timeout=30)
    #         result = r.read().decode('utf-8')
    #         for p in result.split('\n'):
    #             if (p != ''):
    #                 realips.append(p)
    #         r.close()
    #     except Exception as e:
    #         print(e)
    #     return realips

    def initStations(self):
        errorCount = 0
        while True:
            if errorCount > 3:
                print('读取车站代码失败,退出程序')
                sys.exit()
            try:
                with open(self.stationDownloadName, 'r', encoding='utf8') as f:
                    stationsStr = f.read()
                stations = stationsStr.split('@')
                for s in stations:
                    tempStationSplit = s.split('|')
                    self.stationNameToCode[tempStationSplit[1]] = tempStationSplit[2]
                    self.stationCodeToName[tempStationSplit[2]] =tempStationSplit[1]
                return
            except:
                print('车站代码读取失败,正在重试...')
                errorCount += 1
                self.downloadStations()

    def downloadStations(self):
        print('正在下载城市代码...')
        stationUrl = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9035'
        response = self.session.get(stationUrl)
        pattern = re.compile('\'(.*?)\'')
        with open(self.stationDownloadName, 'w', encoding='utf8') as f:
            f.write(pattern.findall(response.text)[0].lstrip('@'))
        print('城市代码下载完毕')

    #获取新Ip
    def getnewip(self):
        ips = self.loadtxt()
        pip = random.choice(ips)
        print("新ip:",pip)
        return pip

    def create_captcha_answser(self,nlist):
        # 根据手动计算,得出点击图片中心得大致位置(基本准确)
        answer_list_all = [(35, 39), (106, 39), (173, 39), (240, 39), (39, 106), (106, 106), (173, 106), (240, 106)]
        # 根据打码函数返回的json格式的图片序号进行遍历,找到其对应的坐标,并加入到列表中
        cLists=[]
        for num in nlist:
            x, y=answer_list_all[num]
            result="{0},{1}".format(str(x + random.randint(-2, 3)), str(y + random.randint(-3, 2)))
            cLists.append(result)
        cLists = ','.join(cLists)
        return cLists

    def getCoordinate(self,picurl):
        txtresult = self.code12306.online_test(picurl,self.text_model,self.image_model,self.label_dict)
        print(txtresult)
        self.txtInfo.emit(str(txtresult))
        # coordinates = ['8,44','108,46','186,43','249,44','26,120','107,120','185,125','253,119']
        # cLists=[]
        # for num in txtresult:
        #     cList = coordinates[num]
        #     cLists.append(cList)
        cLists=self.create_captcha_answser(txtresult)
        return cLists

    def getcodeurl(self):
        # 获取验证码
        captchaRes = self.session.get(
            'https://kyfw.12306.cn/passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand&0.46630622142659206',proxies=self.proxy)
        captcha = captchaRes.content
        filename = "%s%s.png" % (self.captchaDownloadName,uuid.uuid4())
        with open(filename, 'wb') as f:
            f.write(captcha)
        return filename

    #验证码验证
    def captchaCheck(self):
        captchaErrorCount = 0
        print('正在识别验证码...')
        self.txtInfo.emit('正在识别验证码...')
        while True:
            if captchaErrorCount > 10:
                print('验证码失败次数超过限制,登录失败,重新抢票')
                break
            #下载验证码图片
            codeimg=self.getcodeurl()
            captchaStr=""
            try:
                captchaStr = self.getCoordinate(codeimg)
            except Exception as e:
                print(e)
            print("captchaStr:",captchaStr)
            self.txtInfo.emit("captchaStr:"+captchaStr)
            #captchaStr = captchaStr.replace('|', ',')
            captchaStr = requests.utils.requote_uri(captchaStr)
            data = {
                'answer': captchaStr,
                'login_site' :'E',
                'rand': 'sjrand'
            }
            #验证验证码
            response = self.session.post('https://kyfw.12306.cn/passport/captcha/captcha-check', data = data,proxies=self.proxy)
            print(response.text)
            result = response.json()
            if result['result_code'] == '4':
                print('识别验证码成功')
                break
            else:
                #print('识别验证码失败')
                captchaErrorCount += 1

    def secLoginVerify(self,newapptk):
        print('第二次验证')
        newAppTkErrorCount = 0
        url = 'https://kyfw.12306.cn/otn/uamauthclient'
        data = {
            'tk': newapptk
        }
        while True:
            if newAppTkErrorCount > 5:
                print('newAppTk获取失败,退出程序')
                sys.exit()
            response = self.session.post(url, data = data)
            try:
                verifyResult = response.json()
                print(verifyResult)
                return verifyResult
            except json.decoder.JSONDecodeError:
                newAppTkErrorCount += 1
    #登录线程
    def loginthread(self):
        self.login()
    #登录
    def login(self):
        # 1 伪装cookie++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        setCookieCountError = 0

        self.proxy = {
            'http': self.getnewip()
        }
        try:
            url = 'https://kyfw.12306.cn/otn/HttpZF/logdevice?algID=WYEdoc45yu&hashCode=EhTtj7Znzyie6I21jpgekYReLAnA8fyGEB4VlIGbF0g&FMQw=0&q4f3=zh-CN&VPIf=1&custID=133&VEek=unknown&dzuS=20.0%20r0&yD16=0&EOQP=895f3bf3ddaec0d22b6f7baca85603c4&lEnu=3232235778&jp76=e8eea307be405778bd87bbc8fa97b889&hAqN=Win32&platform=WEB&ks0Q=2955119c83077df58dd8bb7832898892&TeRS=728x1366&tOHY=24xx768x1366&Fvje=i1l1o1s1&q5aJ=-8&wNLf=99115dfb07133750ba677d055874de87&0aew={}&E3gR=abfdbb80598e02f8aa71b2b330daa098×tamp={}'.format(
                self.session.headers['User-Agent'], str(round(time.time() * 1000)))
            response = self.session.get(requests.utils.requote_uri(url),proxies=self.proxy)
            pattern = re.compile('\(\'(.*?)\'\)')
            userVerify3 = eval(pattern.findall(response.text)[0])
            # print('设置cookie')
            # print(userVerify3)
            railExpiration = userVerify3['exp']
            railDeviceId = userVerify3['dfp']

            #self.session.cookies['RAIL_EXPIRATION'] = railExpiration
            #self.session.cookies['RAIL_DEVICEID'] = railDeviceId
        except Exception as e:
            print(e)


        # 2 做验证码验证++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        self.captchaCheck()

        # 3 用户名密码登陆++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        print('用户名密码登录')
        loginUrl = 'https://kyfw.12306.cn/passport/web/login'
        data = {
            'username': self.usernameedit.text(),
            'password': self.pwdedit.text(),
            'appid': 'otn'
        }
        response = self.session.post(loginUrl, data=data,proxies=self.proxy)
        loginResult = response.json()
        if loginResult['result_code'] != 0:
            print('用户名密码错误(loginCheck) {}'.format(loginResult['result_code']))
            self.txtInfo.emit('用户名密码错误(loginCheck) {}'.format(loginResult['result_code']))
            return False
        #self.session.cookies['uamtk'] = loginResult['uamtk']

        # 4 用户登录第一次验证+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        url = 'https://kyfw.12306.cn/passport/web/auth/uamtk'
        data = {
            'appid': 'otn'
        }
        response = self.session.post(url, data=data,proxies=self.proxy)
        self.userVerify = response.json()
        print('第一次验证')
        self.txtInfo.emit('第一次验证')
        if self.userVerify['result_code'] != 0:
            print('验证失败(uamtk) code:{}'.format(self.userVerify['result_code']))

        # 5 用户登录第二次验证++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        newapptk = self.userVerify['newapptk']
        userVerify2 = self.secLoginVerify(newapptk)
        print('验证通过,用户为:{}'.format(userVerify2['username']))
        self.txtInfo.emit('验证通过,用户为:{}'.format(userVerify2['username']))
        self.session.cookies.save(ignore_discard=True, ignore_expires=True)

    def findTicket(self,isshow=True):
        status = False
        self.curtrainName=""
        self.proxy = {
            'http': self.getnewip()
        }
        #1 输入站名坐车时间++++++++++++++++++++++++++++++++++++++++++++++++

        self.trainDate = self.begintimeedit.text()
        stationNames = []
        stationNames.append(self.beginStationedit.text())
        stationNames.append(self.endStationedit.text())
        try:
            stationCode = list(map(lambda x: self.stationNameToCode[x],stationNames))
            self.fromStationName = stationNames[0]
            self.fromStationCode = stationCode[0]
            self.toStationName = stationNames[1]
            self.toStationCode = stationCode[1]
            print(self.fromStationName,self.fromStationCode,self.toStationName,self.toStationCode)
        except KeyError:
            print('车站名称错误,重新输入')

        #2 查询车次+++++++++++++++++++++++++++++++++++++++++++++++++++++++

        queryUrl = 'https://kyfw.12306.cn/otn/leftTicket/queryZ?leftTicketDTO.train_date={}&leftTicketDTO.from_station={}&leftTicketDTO.to_station={}&purpose_codes=ADULT'.format(
            self.trainDate, self.fromStationCode, self.toStationCode)
        findTicketError = 0
        while True:
            # if findTicketError > 5:
            #     print('查询出现错误,退出程序')
            #     sys.exit()
            try:
                response = self.session.get(queryUrl,proxies=self.proxy)
                trainList = response.json()['data']['result']
                break
            except (json.decoder.JSONDecodeError,KeyError):
                self.proxy = {
                    'http': self.getnewip()
                }
                findTicketError += 1
                self.txtInfo.emit('查询车次失败第{0}次'.format(findTicketError))
        if len(trainList) > 0:
            '''
            secretstr 0
            内容是 预订 1
            不知道什么串加车次 2
            车次 3
            始发站 4
            终点站 5 
            要坐的站 6
            要到的站 7
            出发时间 8
            到达时间 9
            历时 10
            是否可以预订(Y可以 N和IS_TIME_NOT_BUY 不可以)   11  
            leftTicket 12
            日期20171216 13
            trainLocation 15
            软卧 23
            硬卧 28
            硬座 29
            无座 26
            二等座 30
            一等座 31
            商务座 32
            '''
            #3 过滤车次++++++++++++++++++++++++++++++++++++++++++++++++
            filterTrainList = []
            for train in trainList:
                trainDetailSplit = train.split('|')
                #if trainDetailSplit[11] == 'Y' and (not(trainDetailSplit[31] == ''or trainDetailSplit[31] == '无') or not(trainDetailSplit[30] == ''or trainDetailSplit[30] == '无') or not(trainDetailSplit[26] == ''or trainDetailSplit[26] == '无') or not(trainDetailSplit[23] == ''or trainDetailSplit[23] == '无') or not(trainDetailSplit[28] == ''or trainDetailSplit[28] == '无') or not(trainDetailSplit[29] == ''or trainDetailSplit[29] == '无')):
                filterTrainList.append(train)
            if len(filterTrainList) > 0:
               self.filterTrainList=filterTrainList
               if isshow:
                   self.printTrainList(filterTrainList)
               #4 锁定用户输入车次++++++++++++++++++++++++++++++++++++++
               #trainName = input('请输入要预订的列车编号:').upper()
               trainnums=self.gettrainnums()
               print("trainnums:",trainnums)
               if len(trainnums)>0:
                   for trainName in trainnums:
                       if not status:
                           for filterTrain in filterTrainList:
                                trainDetailSplit = filterTrain.split('|')
                                if trainDetailSplit[3] == trainName:
                                    self.seatType=""
                                    self.curtrainName=trainName
                                    self.trainSecretStr = trainDetailSplit[0]
                                    self.trainNo = trainDetailSplit[2]
                                    self.trainCode = trainDetailSplit[3]
                                    self.fromStationTelecode = trainDetailSplit[6]
                                    self.toStationTelecode = trainDetailSplit[7]
                                    self.leftTicket = trainDetailSplit[12]
                                    self.trainLocation = trainDetailSplit[15]
                                    trainseat=self.seatedit.text()
                                    if trainseat=="硬座":
                                        if trainDetailSplit[29] != '' and trainDetailSplit[29] != u'无':
                                            self.seatType = self.seatMap[trainseat]
                                            status = True
                                    elif trainseat=="二等座":
                                        if trainDetailSplit[30] != '' and trainDetailSplit[30] != u'无':
                                            self.seatType = self.seatMap[trainseat]
                                            status = True
                                    elif trainseat=="一等座":
                                        if trainDetailSplit[31] != '' and trainDetailSplit[31] != u'无':
                                            self.seatType = self.seatMap[trainseat]
                                            status = True
                                    elif trainseat=="硬卧":
                                        if trainDetailSplit[28] != '' and trainDetailSplit[28] != u'无':
                                            self.seatType = self.seatMap[trainseat]
                                            status = True
                                    elif trainseat == "商务座":
                                        if trainDetailSplit[32] != '' and trainDetailSplit[32] != u'无':
                                            self.seatType = self.seatMap[trainseat]
                                            status = True
                                    if self.seatType=="":
                                        if self.notseat.isChecked():
                                            if trainDetailSplit[26] != '' and trainDetailSplit[26] != u'无':
                                                self.seatType = self.seatMap["无座"]
                                                status = True
                                    break
                       else:
                           break

               else:
                   print("请选择车次")
            else:
                print('{},{}到{} 没有可买车次(已售完或暂停车次)'.format(self.trainDate, self.fromStationName, self.toStationName))
        else:
            print('{},{}到{} 无车次'.format(self.trainDate,self.fromStationName,self.toStationName))
        return status

    def printTrainList(self,filterTrainList):
        model = self.MyTable.model()
        for k,filterTrain in enumerate(filterTrainList):
            ft = filterTrain.split('|')
            trainnum = ft[3]
            fromStationName = self.stationCodeToName[ft[6]]
            toStationName = self.stationCodeToName[ft[7]]
            fromtime=ft[8]
            totime=ft[9]
            totaltime=ft[10]
            tedeng=(ft[32] if (ft[32]!="") else "--")
            yideng=(ft[31] if (ft[31]!="") else "--")
            erdeng = (ft[30] if (ft[30]!="") else "--")
            ruanwo=(ft[23] if (ft[23]!="") else "--")
            yingwo=(ft[28] if (ft[28]!="") else "--")
            yingzuo=(ft[29] if (ft[29]!="") else "--")
            wuzuo=(ft[26] if (ft[26]!="") else "--")
            fromtoStation=fromStationName+"\n"+toStationName
            fromtotime=fromtime+"\n"+totime
            # status = QStandardItem()
            # status.setText("未完成")
            # status.setTextAlignment(Qt.AlignCenter)
            # status.setForeground(QBrush(QColor(255, 0, 0)))
            model.setItem(k, 0, QStandardItem(str(trainnum)))
            model.setItem(k, 1, QStandardItem(fromtoStation))
            model.setItem(k, 2, QStandardItem(fromtotime))
            model.setItem(k, 3, QStandardItem(totaltime))
            model.setItem(k, 4, QStandardItem(tedeng))
            model.setItem(k, 5, QStandardItem(yideng))
            model.setItem(k, 6, QStandardItem(erdeng))
            model.setItem(k, 7, QStandardItem(ruanwo))
            model.setItem(k, 8, QStandardItem(yingwo))
            model.setItem(k, 9, QStandardItem(yingzuo))
            model.setItem(k, 10, QStandardItem(wuzuo))
            model.setItem(k, 11, QStandardItem(""))
        #self.footlabel.setText("温馨提示: 共获取到 {0} 个车次.".format(len(filterTrainList)))


    #获取用户参数信息
    def choosePassenger(self,message):
        passengerList = message['data']['normal_passengers']
        print('账户可订票乘客: {}'.format(' '.join(list(map(lambda x: x['passenger_name'], passengerList)))))
        pessnames = self.useredit.text()  #输入订票乘客
        pesstotalnames=pessnames.split(",")
        pessDetailList=[]
        for pessengerName in pesstotalnames:
            for p in passengerList:
                if pessengerName == p['passenger_name']:
                    pessengerDetail = {
                        'passenger_flag' : p['passenger_flag'],
                        'passenger_type' : p['passenger_type'],
                        'passenger_name' : p['passenger_name'],
                        'passenger_id_type_code' : p['passenger_id_type_code'],
                        'passenger_id_no' : p['passenger_id_no'],
                        'mobile_no' : p['mobile_no']
                    }
                    pessDetailList.append(pessengerDetail)
        return pessDetailList



    #获取乘客信息
    def getuserlist(self):
        self.session.headers['Referer'] = 'https://kyfw.12306.cn/otn/leftTicket/init'
        # 3 initDC+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        url = 'https://kyfw.12306.cn/otn/confirmPassenger/initDc'
        data = '_json_att='
        response = self.session.post(url, data=data)
        pattern = re.compile('globalRepeatSubmitToken = \'(.*?)\'')
        pattern2 = re.compile("key_check_isChange':'(.*?)'")
        self.submitToken = pattern.findall(response.text)[0]
        self.keyCheckIsChange = pattern2.findall(response.text)[0]
        print('token:{}'.format(self.submitToken))
        print('key_check_isChange:{}'.format(self.keyCheckIsChange))
        # 4 getPassengerDTOs++++++++++++++++++++++++++++++++++++++++++++++++++++++

        print('正在获取乘客信息')
        url = 'https://kyfw.12306.cn/otn/confirmPassenger/getPassengerDTOs'
        data = {
            '_json_att': '',
            'REPEAT_SUBMIT_TOKEN': self.submitToken
        }
        response = self.session.post(url, data=data)
        result = response.json()
        # print(result)
        print('获取信息成功')
        self.pd = self.choosePassenger(result)
        #self.chooseSeat(self.seatedit.text())

    def threadpiao(self):
        trainnums = self.gettrainnums()
        if len(trainnums) > 0:
            searchnum = 0
            while True:
                if not self.stoppiao:
                    mtime=0.1
                    if float(self.pushedit.text())>0 and float(self.pushedit.text())<1:
                        mtime=random.random()
                    elif float(self.pushedit.text())>=1:
                        mtime = random.random()+1
                    searchnum += 1
                    result = self.findTicket(False)
                    if result:
                        self.txtInfo.emit(
                            '当前IP: {0},查询第 {1} 次,刷新速率:{2}, 结果:{3}'.format(self.proxy, searchnum,mtime,self.curtrainName + "找到余票,开始抢票"))
                        self.bookingTicket()
                        break
                    else:
                        self.txtInfo.emit('当前IP: {0},查询第 {1} 次,刷新速率:{2}, 结果:{3}'.format(self.proxy, searchnum,mtime, "此次没有找到余票"))
                    time.sleep(mtime)
                else:
                    break


    def submitpaker(self):
        self.stoppiao=False
        t = threading.Thread(target=self.threadpiao)
        t.setDaemon(True)
        t.start()

    #组合购票人
    def combinuser(self,seatType,plist):
        passengerTicketStr=""
        oldPassengerStr=""
        for pd in plist:
            passengerTicketStr += seatType + ',' + pd['passenger_flag'] + ',' + pd[
                'passenger_type'] + ',' + pd['passenger_name'] + ',' + pd['passenger_id_type_code'] + ',' + \
                                      pd['passenger_id_no'] + ',' + pd['mobile_no'] + ',N_'

            oldPassengerStr += pd['passenger_name'] + ',' + pd['passenger_id_type_code'] + ',' + pd[
                'passenger_id_no'] + ',' + pd['passenger_type'] + '_'
        passengerTicketStr=passengerTicketStr[:-1]
        return passengerTicketStr,oldPassengerStr

    #抢票下单
    def bookingTicket(self):
        # 1 checkUser +++++++++++++++++++++++++++++++++++++++++++++
        self.session.headers['Referer'] = 'https://kyfw.12306.cn/otn/leftTicket/init'
        userCheckError = 0
        while True:
            if userCheckError > 5:
                print('用户登录检测失败,退出程序')
                sys.exit()
            url = 'https://kyfw.12306.cn/otn/login/checkUser'
            try:
                result = self.session.post(url).json()
                print(result)
                if not result['data']['flag'] :
                    print('用户未登录checkUser')
                    userCheckError += 1
                    self.login()
                    continue
                print('验证登录状态成功checkUser')
                break
            except json.decoder.JSONDecodeError:
                userCheckError += 1

        # 2 submitOrderRequest+++++++++++++++++++++++++++++++++++++
        print('正在提交订单...')
        self.txtInfo.emit('正在提交订单...')
        url = 'https://kyfw.12306.cn/otn/leftTicket/submitOrderRequest'
        data = {
            'secretStr':self.trainSecretStr,
            'train_date':self.trainDate,
            'back_train_date':time.strftime("%Y-%m-%d", time.localtime(time.time())),
            'tour_flag':'dc',  # dc 单程
            'purpose_codes':'ADULT',  # adult 成人票
            'query_from_station_name':self.fromStationName,
            'query_to_station_name':self.toStationName
        }
        data = str(data)[1:-1].replace(':','=').replace(',','&').replace(' ','').replace('\'','')
        data = requests.utils.requote_uri(data)
        result = self.session.post(url,data=data).json()
        # print('submitOrderRequest+++++')
        # print(result)
        if not result['status']:
            print('提交订单失败 status = {}'.format(result['status']))
            self.txtInfo.emit('提交订单失败 status = {}'.format(result['status']))
            sys.exit()
        print('提交订单成功')
        self.txtInfo.emit('提交订单成功')

        url = 'https://kyfw.12306.cn/otn/confirmPassenger/initDc'
        data = '_json_att='
        response = self.session.post(url, data=data)
        pattern = re.compile('globalRepeatSubmitToken = \'(.*?)\'')
        pattern2 = re.compile("key_check_isChange':'(.*?)'")
        self.submitToken = pattern.findall(response.text)[0]
        self.keyCheckIsChange = pattern2.findall(response.text)[0]
        # print('token:{}'.format(self.submitToken))
        # print('key_check_isChange:{}'.format(self.keyCheckIsChange))
        # 4 getPassengerDTOs++++++++++++++++++++++++++++++++++++++++++++++++++++++

        print('正在获取乘客信息')
        self.txtInfo.emit('正在获取乘客信息')
        url = 'https://kyfw.12306.cn/otn/confirmPassenger/getPassengerDTOs'
        data = {
            '_json_att': '',
            'REPEAT_SUBMIT_TOKEN': self.submitToken
        }
        response = self.session.post(url, data=data)
        result = response.json()
        # print(result)
        pdlists = self.choosePassenger(result)
        #self.chooseSeat(self.seatedit.text())


        # 5 checkOrderInfo++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        print('正在验证订单...')
        self.txtInfo.emit('正在验证订单...')
        self.passengerTicketStr,self.oldPassengerStr=self.combinuser(self.seatType,pdlists)
        # print("passengerTicketStr:",self.passengerTicketStr)
        # print("oldPassengerStr:", self.oldPassengerStr)
        url = 'https://kyfw.12306.cn/otn/confirmPassenger/checkOrderInfo'
        data = 'cancel_flag=2&bed_level_order_num=000000000000000000000000000000&passengerTicketStr={}&oldPassengerStr={}_&tour_flag=dc&randCode=&whatsSelect=1&_json_att=&REPEAT_SUBMIT_TOKEN={}'.format(
            self.passengerTicketStr,self.oldPassengerStr,self.submitToken
        )
        data = requests.utils.requote_uri(data)
        checkOrderRrrorCount = 0
        while True :
            if checkOrderRrrorCount > 3:
                print('验证订单失败,退出程序')
                sys.exit()
            response = self.session.post(url, data = data)
            result = response.json()
            if result['data']['submitStatus']:
                print('订单验证成功')
                self.txtInfo.emit('订单验证成功')
                break

        print("抢到的车次:",self.trainNo,"座位类型:",self.seatType)
        self.txtInfo.emit("抢到的车次:"+self.trainNo+"座位类型:"+self.seatType)
        # 6 getQueueCount+++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        url = 'https://kyfw.12306.cn/otn/confirmPassenger/getQueueCount'
        dateGMT = time.strftime('%a %b %d %Y %H:%M:%S  GMT+0800', time.strptime(self.trainDate, '%Y-%m-%d'))
        # data = 'train_date={}&train_no={}&stationTrainCode={}&seatType={}&fromStationTelecode={}&toStationTelecode={}&leftTicket={}&purpose_codes=00&train_location={}&_json_att=&REPEAT_SUBMIT_TOKEN={}'.format(
        #     dateGMT,self.trainNo,self.trainCode,self.seatType,self.fromStationTelecode,self.toStationTelecode,self.leftTicket,self.trainLocation,self.submitToken
        # )
        data = {
            'train_date' : dateGMT,
            'train_no' : self.trainNo,
            'stationTrainCode' : self.trainCode,
            'seatType' : self.seatType,
            'fromStationTelecode' : self.fromStationTelecode,
            'toStationTelecode' : self.toStationTelecode,
            'leftTicket' : self.leftTicket,
            'purpose_codes' : '00',
            'train_location' : self.trainLocation,
            '_json_att' : '',
            'REPEAT_SUBMIT_TOKEN' : self.submitToken
        }
        response = self.session.post(url, data = data)
        print('getQueueCount++++++')
        result = response.json()
        print(result)
        self.txtInfo.emit(str(result))

        # 7 confirmSingleForQueue++++++++++++++++++++++++++++++++++++++++++++++++++

        #https://kyfw.12306.cn/otn/confirmPassenger/confirmSingle
        url = 'https://kyfw.12306.cn/otn/confirmPassenger/confirmSingleForQueue'
        data = {
            'passengerTicketStr' : self.passengerTicketStr,
            'oldPassengerStr' : self.oldPassengerStr,
            'randCode' : '',
            'purpose_codes' : '00',
            'key_check_isChange' : self.keyCheckIsChange,
            'leftTicketStr' : self.leftTicket,
            'train_location' : self.trainLocation,
            'choose_seats' : '',
            'seatDetailType' : '000',
            'whatsSelect' : '1',
            'roomType' : '00',
            'dwAll' : 'N',
            '_json_att' : '',
            'REPEAT_SUBMIT_TOKEN' : self.submitToken
        }
        qeueErrorCount = 0
        while True:
            response = self.session.post(url, data = data)
            try:
                result = response.json()
                print('confirmSingleForQueue++++++')
                print(result)
                self.txtInfo.emit('confirmSingleForQueue++++++\n'+str(result))
                if not result['data']['submitStatus']:
                    print('订票失败,重新抢票')
                    self.txtInfo.emit('订票失败,重新抢票')
                    break
                else:
                    break
            except:
                qeueErrorCount += 1

        # 8 queryOrderWaitTime+++++++++++++++++++++++++++++++++++++++++

        waitTimeErrorCount = 0
        while True:
            # if waitTimeErrorCount > 10:
            #     print('请求次数过多,退出程序')
            #     sys.exit()
            url = 'https://kyfw.12306.cn/otn/confirmPassenger/queryOrderWaitTime?random={}&tourFlag=dc&_json_att=&REPEAT_SUBMIT_TOKEN={}'.format(
                str(round(time.time() * 1000)),self.submitToken)
            response = self.session.get(url)
            result = response.json()
            print(result)
            self.txtInfo.emit('等待下票排队时间++++++\n'+str(result))
            resultCode = result['data']['waitTime']
            if resultCode == -1:
                self.orderId = result['data']['orderId']
                break
            elif resultCode == -2:
                print('取消次数过多,今日不能继续订票')
                sys.exit()
            else:
                waitTimeErrorCount += 1
                time.sleep(2.5)

        # 8 resultOrderForDcQueue+++++++++++++++++++++++++++++++++++++++++

        url = 'https://kyfw.12306.cn/otn/confirmPassenger/resultOrderForDcQueue'
        data = 'orderSequence_no={}&_json_att=&REPEAT_SUBMIT_TOKEN={}'.format(self.orderId,self.submitToken)
        resultOrderErrorCount = 0
        while True:
            if resultOrderErrorCount > 3:
                print('查询订单错误')
                sys.exit()
            response = self.session.post(url, data = data)
            try:
                result = response.json()
                print(result)
                if result['data']['submitStatus']:
                    self.txtInfo.emit('订票成功,请登录12306查看支付')
                    print('订票成功,请登录12306查看')
                    break
            except json.decoder.JSONDecodeError:
                resultOrderErrorCount += 1

    def paintEvent(self, event):
        pass


if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    table = SignWidget()
    table.show()
    sys.exit(app.exec_())

code12306.py   识别图片类

from model.densenet import DenseNet
from image_utils import *
import numpy as np
import time,random
import shutil
import os

n_classes = 80
image_shape = (64, 64, 3)
text_model_weight = "saves/DenseNet-BC_k=12_d=40.weight"
image_model_weight = "saves/DenseNet-BC_k=24_d=40.weight"
save_path = "/Users/jylonger/Documents/IMAGE"
save_fail_path = "/Users/jylonger/Documents/FAIL"
class code12306():
    def load_model(self):
        print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())))
        text_model = DenseNet(classes=n_classes, input_shape=image_shape, depth=40, growth_rate=12, bottleneck=True,
                              reduction=0.5, dropout_rate=0.0, weight_decay=1e-4)
        image_model = DenseNet(classes=n_classes, input_shape=image_shape, depth=40, growth_rate=24, bottleneck=True,
                               reduction=0.5, dropout_rate=0.0, weight_decay=1e-4)
        print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())))
        text_model.load_weights(text_model_weight)
        image_model.load_weights(image_model_weight)
        return text_model, image_model

    def load_label_dict(self):
        # 读取类别名称
        label_dict = {}
        with open("labels.txt", encoding="utf-8") as file:
            for line in file:
                class_name, id = line.strip().split()
                label_dict[int(id)] = class_name
        return label_dict

    def online_test(self,image_path,text_model, image_model, label_dict):
        """
        获取验证码图片、模型识别、提交
        :return:
        """
        # 下载验证码图片到本地
        #image_path = image_utils.download_captcha()
        # 切割验证码为文字部分和图片部分
        raw_texts, raw_images = process_raw_images(image_path, (image_shape[0], image_shape[1]))
        # 图像转换为np数组
        texts, images = np.array([np.asarray(image) for image in raw_texts]), np.array(
            [np.asarray(image) for image in raw_images])

        # 模型输出
        text_predict = text_model.predict(texts)
        image_predict = image_model.predict(images)

        # 预测结果
        text_result = np.argmax(text_predict, 1)
        print(text_result)
        image_result = np.argmax(image_predict, 1)

        # 概率
        text_prob = np.max(text_predict, 1)
        image_prob = np.max(image_predict, 1)
        # 类别名
        text_label = [label_dict[r] for r in text_result]
        print(text_label)
        image_label = [label_dict[r] for r in image_result]
        print(image_label)
        ids = set()
        for r1 in text_result:
            for id, r2 in enumerate(image_result):
                if r1 == r2:
                    ids.add(id)
        return ids
        # result = image_utils.submit_captcha(ids)
        # print(result)
        # if "成功" in result:
        #     # if save_path:
        #     #     # 保存图片
        #     #     for id in ids:
        #     #         image_utils.save(raw_images[id], os.path.join(save_path, "IMG"), label_dict[image_result[id]])
        #     #     for id, image in enumerate(raw_texts):
        #     #         image_utils.save(image, os.path.join(save_path, "TXT"), label_dict[text_result[id]])
        #     return True
        # else:
        #     if save_fail_path:
        #         for id,image in enumerate(raw_images):
        #             image_utils.save(image, os.path.join(save_fail_path, "IMG"), image_label[id])
        #         for id, image in enumerate(raw_texts):
        #             image_utils.save(image, os.path.join(save_fail_path, "TXT"), text_label[id])
        #         # if not os.path.exists(save_fail_path):
        #         #     os.mkdir(save_fail_path)
        #         # shutil.move(image_path, save_fail_path)
        #     return False


if __name__ == '__main__':
    code12306=code12306()
    text_model, image_model = code12306.load_model()
    label_dict = code12306.load_label_dict()

    test_result = {True: 0, False: 0}

    try:
        test_result[code12306.online_test(text_model, image_model, label_dict)] += 1
        time.sleep(4)
        true_times, false_times = test_result[True], test_result[False]
        print("%d/%d 准确率:%.3f" % (true_times, true_times + false_times, true_times / (true_times + false_times)))
    except Exception as e:
        time.sleep(4)

先到这里吧,有时间再记录一下

 

你可能感兴趣的:(python)