美文网首页大数据 爬虫Python AI Sql生活不易 我用python我爱编程
Python 12306验证码自动验证、用户登录和查询余票

Python 12306验证码自动验证、用户登录和查询余票

作者: 东东隆东抢 | 来源:发表于2018-08-09 15:38 被阅读6次

    1,查票结果效果

    余票查询效果图.png

    2,本文使用到的库

    • re
    • os
    • time
    • datetime
    • requests
    • prettytable

    3,获取验证码图片

    使用Google浏览器并开启开发者模式(使用F12快捷键),在地址栏里输入如下链接:
    https://kyfw.12306.cn/otn/login/init
    NetworkAll里查看相关请求,下拉发现了验证码图片的请求,查看HeadersRequest URL如下:
    https://kyfw.12306.cn/passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand&0.05013721011282968
    链接里最后一个参数0.05013721011282968每次请求时都不一样。但我发现没有此参数同样能够请求到验证码图片,故可以直接使用如下链接请求验证码图片:
    https://kyfw.12306.cn/passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand

    获取验证码图片
    定义一个名称为Login的类并进行定义其init方法:
    class Login:
        def __init__(self):
            self.headers = {
                "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36"
            }
            self.session = requests.session() #创建session会话
            self.session.headers = self.headers
            self.session.verify = False #跳过SSL验证
            self.loginUrl = 'https://kyfw.12306.cn/passport/web/login'  # 登录Url
            self.captchaCheckUrl = 'https://kyfw.12306.cn/passport/captcha/captcha-check' #验证码验证链接
            self.captchaImgUrl = 'https://kyfw.12306.cn/passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand' #获取验证码图片
            self.captchaFilePath = 'captcha.jpg' #验证码图片路径
    

    定义获取验证码图片的方法,如获取失败则使用递归再次请求:

        def getCaptCha(self):
            response = self.session.get(self.captchaImgUrl)
            if response.status_code == 200:
                print('验证码图片请求成功')
                with open(self.captchaFilePath, 'wb') as f:
                    f.write(response.content) # 验证码图片写入当前文件夹
            else:
                print('验证码下载失败, 正在重试...')
                self.getCaptCha()  # 递归
                return 
    

    在登录页面输入用户名和密码后点击验证码图片,点击登录按钮。在开发者模式下查看验证码验证请求的链接为:https://kyfw.12306.cn/passport/captcha/captcha-check

    Post请求 同时我们发现使用的是Post请求,那么提交的数据Data有哪些呢?通过下图发现提交的有3个数据,分别是answerlogin_siterand
    验证码校验请求参数 其中login_siterand参数值是固定的,而answer是我们点击图片时鼠标在图片上的坐标,坐标基准(0,0)点如下图红色箭头处:
    坐标原点
    将验证码图片按照如下的方式进行分割,按照位置标号为1-8,从每个图里定义一个固定的点坐标用于验证码验证:
    分割图片
    3.1 手动输入验证码位置
     def captchaCheck(self):
            self.getCaptcha()  # 获取验证码图片
            imgLocation = input("请输入验证码图片位置,以英文状态下的分号','分割:\n")
            # 验证码坐标字典
            coordinates = {'1':'35,35',  '2':'105,35',  '3':'175,35', '4':'245,35',
                           '5':'35,105', '6':'105,105', '7':'175,105','8':'245,105'}
            rightImgCoordinates =[] # 创建数组存放正确的验证码坐标
            for i in imgLocation.split(','):
                rightImgCoordinates.append(coordinates[i])
            answer = ','.join(rightImgCoordinates)
            data = {
                'login_site':'E',  # 固定的
                'rand': 'sjrand',  # 固定的
                'answer': answer   # 验证码对应的坐标
            }
            result = self.session.post(self.captchaCheckUrl,data=data).json()
            if result['result_code'] == '4':
                print('验证码验证成功')
            else:
                print('出错啦:{}'.format(result['result_message']))
                self.captchaCheck()
                return
    
    3.2 通过打码平台自动验证

    所谓的打码平台自动验证是指用户给打码平台传入一张验证码图片,平台通过码工去人工识别验证码(码工有出错可能),平台再将其结果返回给用户,这个过程一般也就2-3秒时间。12306验证码是多个坐标拼接成的字符串,因此我们需要平台返回多个坐标字符串。
    百度搜索打码平台关键字能够找到很多相关平台,其中包含打码兔超级鹰等。写本文的时发现打码兔平台已经转型,不再提供打码服务,于是我只能去注册超级鹰账户。平台网站上有如何使用Python进行打码的相关文档,使用时需要注意验证码图片的类型,返回多个坐标对应的codetype9004,具体请参考验证码类型
    如下代码是超级鹰官网提供的,我做了一些改动,原因是平台返回的坐标是以图片的左上角为原点,这与12306坐标基准不一致。

    import requests
    from hashlib import md5
    class Chaojiying_Client:
        def __init__(self, username, password, soft_id):
            #平台账号
            self.username = username
            #平台密码
            self.password = md5(password.encode('utf-8')).hexdigest()
            # 软件ID号,注册创建软件时会自动生成
            self.soft_id = soft_id
            self.base_params = {
                'user'  : self.username,
                'pass2' : self.password,
                'softid': self.soft_id,
            }
            self.headers = {
                'Connection': 'Keep-Alive',
                'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
            }
    
        def PostPic(self, im, codetype):
            params = {
                'codetype': codetype,
            }
            params.update(self.base_params)
            files = {'userfile': ('ccc.jpg', im)}
            result = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers).json()
            #  如下代码是我修改的部分,原来代码请去官网查看
            answerList = result['pic_str'].replace('|',',').split(',')
            for index in range(len(answerList)):
                 #answerList类似为 [10,135,70,200],将纵坐标减去30并返回字符串
                 if index % 2 != 0:
                    answerList[index] = str(int(answerList[index])-30)
                 else:
                    answerList[index] = str(answerList[index])
            answerStr = ','.join(answerList) #用逗号拼接成'10,105,70,170'
            print(answerStr)
            return answerStr  # 返回验证码坐标
    

    将获取的验证码图片提交给打码平台获取验证码坐标字符串:

        def getCaptchaAnswer(self):
            response= self.session.get(self.captchaImgUrl)
            if response.status_code ==200:
                print('验证码图片请求成功')
                with open(self.captchaFilePath,'wb') as f:
                    f.write(response.content)
            else:
                print('验证码下载失败, 正在重试...')
                self.getCaptchaAnswer() #递归
            try:
                img = open(self.captchaFilePath, 'rb').read()
                answerStr = self.chaoJiYing.PostPic(img, 9004)
                return answerStr
            except Exception as e:
                print(e)
    

    然后根据获取的坐标字符串再进行自动验证:

        def captchaCheck(self):
            self.getCaptcha()
            answer = self.getCaptchaAnswer()
            data = {
                'login_site':'E',  # 固定的
                'rand': 'sjrand',  # 固定的
                'answer': answer   # 验证码对应的坐标字符串
            }
            result = self.session.post(self.captchaCheckUrl,data=data).json()
    
            if result['result_code'] == '4':
                print('验证码验证成功')
            else:
                print('出错啦:{}'.format(result['result_message']))
                self.captchaCheck()
                return
    

    4,登录验证

    实际我们使用浏览器进行登录时会先检查用户名和密码是否为空,不为空时才进行验证码验证(为空时直接报错提示),验证码验证通过后再对用户名和密码进行验证。
    登录同样采用的是Post请求,提交用户名密码以及固定的appid 3个数据:

        def login(self):
            self.captchaCheck()  # 先进行验证码验证
            loginData = {
                'username': Config.userName, # 用户名,我自己写了个Config文件,将用户名、密码以及其他常量放里面
                'password': Config.password, # 密码
                'appid': 'otn'  # 固定
            }
            result = self.session.post(self.loginUrl,data=loginData).json()
            if result['result_code'] == 0:
                print('用户登录成功')  # 实际并没有成功,登录过程很复杂,还有很多请求
            else:
                print('登录失败,请重试')
    

    5,工具类

    在写查询余票代码时发现有很多共用的方法在代码里,于是我自己将其抽出来放在一个类文件里。增加复用率,避免大量垃圾代码。

    from datetime import datetime
    import time
    # 工具类
    class Utilitys():
    
       # 反转字典(key与value对调,生成新dict)
        def reversalDict(self, dict):
            return {v: k for k, v in dict.items()}
    
        # 将时间字符串(如38:50)转化为小时和分钟的形式
        def getDuration(self, timeStr):
            duration = timeStr.replace(':', '时') + '分'
            # 如果时间格式是00时25分,则只显示多少分钟
            if duration.startswith('00'):
                return duration[4:]
            return duration
    
        # 获取一个格式为2018-08-01的日期是周几
        def getWeekDay(self, date):
            weekDayDict = {
                0: '周一', 
                1: '周二',
                2: '周三',
                3: '周四',
                4: '周五',
                5: '周六',
                6: '周天',
            }
            day = datetime.strptime(date, '%Y-%m-%d').weekday()
            return weekDayDict[day]
    
        # 转化日期格式,返回 X月X日
        def getDateFormat(self, date):
            # date 格式为2018-08-08
            dateList = date.split('-')
            if dateList[1].startswith('0'):
                month = dateList[1].replace('0', '')
            else:
                month = dateList[1]
            if dateList[2].startswith('0'):
                day = dateList[1].replace('0', '')
            else:
                day = dateList[2]
            return '{}月{}日'.format(month, day)
    
        # 检查购票日期是否合理
        def checkDate(self, date):
            '''
            功能:检测乘车日期的正确性
            :param trainDate: 乘车时间(2018-08-04)
            :return: 返回时间是否为标准的形式的标志
            '''
            localTime = time.localtime()
            localDate = '%04d-%02d-%02d' % (localTime.tm_year, localTime.tm_mon, localTime.tm_mday)
            # 获得当前时间时间戳
            currentTimeStamp = int(time.time())
            # 预售时长的时间戳
            deltaTimeStamp = '2505600'
            # 截至日期时间戳
            deadTimeStamp = currentTimeStamp + int(deltaTimeStamp)
            # 获取预售票的截止日期时间
            deadTime = time.localtime(deadTimeStamp)
            deadDate = '%04d-%02d-%02d' % (deadTime.tm_year, deadTime.tm_mon, deadTime.tm_mday)
            print('请注意合理的乘车日期范围是:{} 至 {}'.format(localDate, deadDate))
            # 判断输入的乘车时间是否在合理乘车时间范围内
            # 将购票日期转换为时间数组
            trainTimeStruct = time.strptime(date, "%Y-%m-%d")
            # 转换为时间戳:
            trainTimeStamp = int(time.mktime(trainTimeStruct))
            # 将购票时间修改为12306可接受格式 ,如用户输入2018-8-7则格式改为2018-08-07
            trainTime = time.localtime(trainTimeStamp)
            trainDate = '%04d-%02d-%02d' % (trainTime.tm_year, trainTime.tm_mon, trainTime.tm_mday)
            # 比较购票日期时间戳与当前时间戳和预售截止日期时间戳
            if currentTimeStamp <= trainTimeStamp and trainTimeStamp <= deadTimeStamp:
                return True, trainDate
            else:
                print('出错啦: 您输入的乘车日期:{}, 当前系统日期:{}, 预售截止日期:{}'.format(trainDate, localDate, deadDate))
                return False, None
    
        # 一个日期转成2018-08-01这样的格式
        def getDate(self,dateStr):
           # date格式为20180801
           year = time.strptime(dateStr,'%Y%m%d').tm_year
           month = time.strptime(dateStr,'%Y%m%d').tm_mon
           day =  time.strptime(dateStr,'%Y%m%d').tm_mday
           return '%04d-%02d-%02d' % (year,month,day)
    

    6,余票查询与打印

    首先明确一点,即用户在不登录情况下也是可以查车票信息的。打开浏览器进入12306余票查询页面查询链接,然后打开开发者模式,在页面上输入出发地为上海,目的地为成都,出发日期为2018-08-22,车票类型选择成人,点击查询按钮。我们发现只有如下的1个Get请求:
    https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2018-08-22&leftTicketDTO.from_station=SHH&leftTicketDTO.to_station=CDW&purpose_codes=ADULT
    此请求包含leftTicketDTO.train_date,leftTicketDTO.from_station,leftTicketDTO.to_stationpurpose_codes几个参数。从参数的英文含义上不难判断它们分别代表出发日期、出发地、目的地和车票类型。
    但出发地怎么是SHH,目的地又怎么是CDW?这些都是什么鬼?我百度了一下,这些字符是指车站的电报码。可这些数据从何而来呢?
    在开发者模式打开的情况下刷新查询页面,发现多了很多请求。仔细查看每个请求都在做些什么操作?服务器又返回了什么?Oh my god,竟然在刚打开查询页面的时候就请求到了。


    我们把数据请求下来并加以保存,保存的原因是这些数据很难变动,请求一次,下次直接使用。
    class Stations:
        def  __init__(self):
            self.headers = {
                "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36"
            }
            self.stationCodeUrl = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js'
            self.queryUrl ='https://kyfw.12306.cn/otn/leftTicket/query'
            self.queryPriceUrl ='https://kyfw.12306.cn/otn/leftTicket/queryTicketPrice'
            self.session = requests.session()
            self.session.verify = False
            self.session.headers = self.headers
            self.filePath = 'stations.txt' # 获取到的电报码存放文件 
            self.color = Colored()
            self.utility = Utilitys()
    

    从服务器上请求电报码,根据正则表达式找出符合条件的所有数据并加以保存:

        def getStationCodes(self):
            # 若文件存在,则直接推出,无需每次都执行请求
            if os.path.exists(self.filePath):
                return
            res = self.session.get(self.stationNameUrl)
            # [\u4e00-\u95fa5]是汉字的头和尾
            stations = re.findall(r'([\u4e00-\u95fa5]+)\|([A-Z]+)',res.text)
            # 注意编码格式utf-8
            with open(self.filePath,'w',encoding='utf-8') as f:
                # ensure_ascii = False 防止乱码
                f.write(json.dumps(dict(stations),ensure_ascii = False))
    
    本地电报码文件部分内容.png
    检查用户输入的日期是否在预售期内,同时检查输入的出发地和目的地是否正确(能否在本地stations.txt文件里查询到)。
        def queryPrompt(self):
            trainDate = input('请输入购票时间,格式为2018-01-01:\n')
            # 工具类文件里定义了checkDate 方法
            timeFlag,trainDate = self.utility.checkDate(trainDate)
            if timeFlag == False:
                print('日期格式错误或者不在预售期内,请从新输入')
                self.queryPrompt()
            fromStation = input('请输入出发站:\n')
            toStation = input('请输入到达站:\n')
    
            #读取文件并转化为字典
            with open(self.filePath, "r", encoding='utf-8') as file:
                stationsDict = json.load(file)
    
            # 判断出发站和达到站是否存在
            if fromStation not in stationsDict.keys() or toStation not in stationsDict.keys():
                print('出错啦: {}或{}不在站点列表中'.format(fromStation, toStation))
    
            # 从字典里获取站点的电报码
            fromStationCode = stationsDict[fromStation]
            toStationCode = stationsDict[toStation]
    
            queryData = {
                'fromStation':fromStation,               # 上海
                'toStation':toStation,                   # 成都
                'trainDate':trainDate,                   # 2018-08-22
                'fromStationCode':fromStationCode,       # SHH
                'toStationCode':toStationCode            # CDW
            }
            return queryData  # 返回查询数据
    
    车票查询返回的结果

    请求查询数据:

        def queryTickets(self):
            # 获取trainDate,fromStationCode,toStationCode,fromStation和toStation
            queryData = self.queryPrompt()
            # print(queryData)
            params = {
                'leftTicketDTO.train_date':queryData['trainDate'],
                'leftTicketDTO.from_station':queryData['fromStationCode'],
                'leftTicketDTO.to_station':queryData['toStationCode'],
                'purpose_codes' : 'ADULT'
            }
    
            res = self.session.get(self.queryUrl,params = params)
            if res.status_code ==200:
                self.getTrainInfo(res.json(),queryData)
    
        def getTrainInfo(self,result,queryData):
            trainDict = {} #单个车次信息字典
            trains = [] # 存放车辆信息
            results = result['data']['result'] #result是查询请求返回的数据(json)
            maps = result['data']['map'] #车站名称与电报码
            for item in results:
                trainInfo = item.split('|')
                # for index, item in enumerate(trainInfo, 0):
                #     print('{}:\t{}'.format(index, item))
                # 已经开始卖票
                if trainInfo[11] =='Y':
                    # 备注
                    # trainDict['remark']          = trainInfo[2]
                    # 车次
                    trainDict['trainName']       = self.color.magenta(trainInfo[3])
                    # 始发站
                    trainDict['fromStation']     = self.color.green(maps[trainInfo[6]])
                    # 终点站
                    trainDict['toStation']       = self.color.red(maps[trainInfo[7]])
                    # 出发时间
                    trainDict['departTime']      = self.color.green(trainInfo[8])
                    # 到达时间
                    trainDict['arriveTime']      = self.color.red(trainInfo[9])
                    # 总用时
                    trainDict['totalTime']       = self.utility.getDuration(trainInfo[10])
                    # 商务特等座
                    trainDict['businessSeat']    = trainInfo[32]
                    # 一等座
                    trainDict['firstClassSeat']  = trainInfo[31]
                    # 二等座
                    trainDict['secondClassSeat'] = trainInfo[30]
                    # 高级软卧
                    trainDict['superSoftBerth']  = trainInfo[21]
                    # 软卧
                    trainDict['softBerth']       = trainInfo[23]
                    # 动卧
                    trainDict['moveBerth']       = trainInfo[33]
                    # 硬卧
                    trainDict['hardBerth']       = trainInfo[26]
                    # 硬座
                    trainDict['hardSeat']        = trainInfo[28]
                    # 无座
                    trainDict['noSeat']          = trainInfo[29]
                    # 其他
                    trainDict['otherSeat']       = trainInfo[22]
                    # 备注
                    trainDict['remark']          = trainInfo[1].replace('<br/>','')
    
                    # 如果值为空,则将值修改为'--',有票则‘有’字显示为绿色,无票‘无’字红色显示
                    for key in trainDict.keys():
                        if trainDict[key] == '':
                            trainDict[key] = '--'
                        if trainDict[key] =='有':
                            trainDict[key] = self.color.green('有')
                        if trainDict[key] =='无':
                            trainDict[key] = self.color.red('无')
                    # 价格字典,可根据‘wuzuo’等 key查询车票价格
                    priceDict = self.getPrice(self.queryPrice(trainInfo))
                     # 凭2代身份证直接进站 trainInfo[18] =1 的话
                    train = [trainDict['trainName']+ self.color.green('[ID]') if trainInfo[18] == '1' else trainDict['trainName'], trainDict['fromStation'] + '\n' + trainDict['toStation'],
                             trainDict['departTime'] + '\n' + trainDict['arriveTime'],
                             trainDict['totalTime'], trainDict['businessSeat']+ '\n' + priceDict['shangwuzuo'] , trainDict['firstClassSeat']+ '\n' + priceDict['yidengzuo'],
                             trainDict['secondClassSeat']+ '\n' + priceDict['erdengzuo'], trainDict['superSoftBerth']+ '\n' + priceDict['gaojiruanwo'], trainDict['softBerth']+ '\n' + priceDict['ruanwo'],
                             trainDict['moveBerth']+ '\n' + priceDict['dongwo'],
                             trainDict['hardBerth']+ '\n' + priceDict['yingwo'], trainDict['hardSeat']+ '\n' + priceDict['yingzuo'], trainDict['noSeat']+ '\n' + priceDict['wuzuo'], trainDict['otherSeat'],
                             trainDict['remark']]
                    # 直接使用append方法将字典添加到列表中,如果需要更改字典中的数据,那么列表中的内容也会发生改变,这是因为dict在Python里是object,不属于primitive
                    # type(即int、float、string、None、bool)。这意味着你一般操控的是一个指向object(对象)的指针,而非object本身。下面是改善方法:使用copy()
                    trains.append(train) # 注意trainDict.copy()
            # 打印查询到的余票信息
            self.prettyPrint(trains,queryData)
    
       def getPrice(self, priceDict):
    
            price ={}
            # 无座票价
            price['wuzuo']       = self.checkPrice('WZ', priceDict)
            # 一等座票价
            price['yidengzuo']   = self.checkPrice('M',  priceDict)
            # 二等座票价
            price['erdengzuo']   = self.checkPrice('O',  priceDict)
            # 高级软卧票价
            price['gaojiruanwo'] = self.checkPrice('A6', priceDict)
            # 硬卧票价
            price['yingwo']      = self.checkPrice('A3', priceDict)
            # 软卧票价
            price['ruanwo']      = self.checkPrice('A4', priceDict)
            # 动卧票价
            price['dongwo']      = self.checkPrice('F',  priceDict)
            # 硬座票价
            price['yingzuo']     = self.checkPrice('A1', priceDict)
            # 商务座票价
            price['shangwuzuo']  = self.checkPrice('A9', priceDict)
    
            return price
    
    
        def checkPrice(self,key,priceDict):
            if key in priceDict.keys():
                  return self.color.green(priceDict[key])
            else:
                  return ''
    
        def queryPrice(self,trainInfo):
            # 注意参数顺序
            parameters = {
            'train_no': trainInfo[2],
            'from_station_no':trainInfo[16],
            'to_station_no': trainInfo[17],
            'seat_types' :trainInfo[35],
            'train_date' : self.utility.getDate(trainInfo[13])
            }
            res = self.session.get(self.queryPriceUrl,params = parameters)
            time.sleep(1)  #此处加个延迟
            # print(res.url)
    
            # 有时候请求一次可能出现失败的情况,然后url会自动跳转到 http://www.12306.cn/mormhweb/logFiles/error.html
            if res.url != 'http://www.12306.cn/mormhweb/logFiles/error.html':
                result = res.json()['data']
                return result
            else:
               self.queryPrice(trainInfo)
               return 
    

    PrettyTable 在terminal 里使用才能看到效果,在IDE里可能会出现|符号不对齐的现象。

        def prettyPrint(self,trains,queryData):
            header = ["车次", "车站", "时间", "历时", "商务座","一等座", "二等座",'高级软卧',"软卧", "动卧", "硬卧", "硬座", "无座",'其他','备注']
            pt = PrettyTable(header)
            date = queryData['trainDate']
            pt.title = self.color.cyan('{}——>{}({} {}),共查询到{}个可购票的车次'.format(queryData['fromStation'],queryData['toStation'],self.utility.getDateFormat(date),self.utility.getWeekDay(date),len(trains)))
            pt.align["车次"] = "l"
            for train in trains:
                pt.add_row(train)
            print(pt)
    

    定义一个Colored

    from colorama import init, Fore, Back, Style
    init(convert=True)
    init(autoreset=False)
    
    class Colored(object):
        #  前景色:红色  背景色:默认
        def red(self, s):
            return Fore.RED + s + Fore.RESET
        #  前景色:绿色  背景色:默认
        def green(self, s):
            return Fore.GREEN + s + Fore.RESET
        #  前景色:黄色  背景色:默认
        def yellow(self, s):
            return Fore.YELLOW + s + Fore.RESET
        #  前景色:蓝色  背景色:默认
        def blue(self, s):
            return Fore.BLUE + s + Fore.RESET
        #  前景色:洋红色  背景色:默认
        def magenta(self, s):
            return Fore.MAGENTA + s + Fore.RESET
        #  前景色:青色  背景色:默认
        def cyan(self, s):
            return Fore.CYAN + s + Fore.RESET
        #  前景色:白色  背景色:默认
        def white(self, s):
            return Fore.WHITE + s + Fore.RESET
        #  前景色:黑色  背景色:默认
        def black(self, s):
            return Fore.BLACK
        #  前景色:白色  背景色:绿色
        def white_green(self, s):
            return Fore.WHITE + Back.GREEN + s + Fore.RESET + Back.RESET
    

    后记

    在实际项目中查询各车次不同类型座位车票价格是没有多大意义的,我们只需要查询到余票信息即可。南京到上海每天大概有280列车次,每列车次都进行一次价格请求,一共进行280次,耗时久。本想用多进程完成的,代码也写了,但在运行过程中发现请求一会儿服务器就拒绝请求了,可能是12306做了反爬的措施。
    另外,登录并没有真正地成功,登录过程中有很多其他请求。后期准备把完整的登录写出来,再增加订票等内容。

    相关文章

      网友评论

      • 920a2cf6e3c7:老哥,能给个联系方式么,有点问题问你
        东东隆东抢:@Dr_Bean 在这边留言即可
      • 忠于人品陷于才华败于社会:可以春运给我来张火车票吗
        东东隆东抢:@忠于人品陷于才华败于社会 天下没有免费的午餐
        忠于人品陷于才华败于社会:@东东隆东抢 还以为免费拿票勒😂
        东东隆东抢:@忠于人品陷于才华败于社会 基本上做不到:grin:这个只是用Python去完成整个订票流程
      • MamBain:6666 那现在这套代码都可以用来抢票了
        东东隆东抢:@MamBain 你会发现其实挺简单的
        MamBain:@东东隆东抢 我刚入门py可以作为我学习的参考
        东东隆东抢:@MamBain 我感觉离实际抢票还是有差距的。只是完成了正常购票,并没有做异常处理。比如购票过程中可能出现二次验证(重新登录输入验证码)等。当然想做是能做出来的,只是效率上不一定能比得上专业软件。毕竟这就是一个学习过程。

      本文标题:Python 12306验证码自动验证、用户登录和查询余票

      本文链接:https://www.haomeiwen.com/subject/sxjqbftx.html