为了账号安全,请及时绑定邮箱和手机立即绑定

Python12306订票

标签:
Python

写在前面

两周前完成了Python 12306验证码自动验证、用户登录和查询余票一文,后来总觉得写得有点凌乱,于是想进行重构,让整个项目结构看起来更加清晰明了。

webp

项目结构


写完整个项目后觉得其实也很简单,无非是使用Session进行多次GetPost请求,难点在于Post请求时使用的Data从何而来?我们先使用抓包工具(浏览器F12)完成一次12306平台订票之完整过程,对需要进行哪些网络请求心里有个大概印象。使用Session的主要原因是为了避免每次请求数据时都去考虑Cookies,如此可能会方便很多。
我们将整个订票过程中使用到的API 放在一个文件里,原因很简单:一旦某个接口地址改变了,我们只需在此文件里进行修改,无法在代码里到处查找修改,省时省力。我自己之前在写iOS 应用时候也是采用这样的方式。


12306 API
class API(object):
    # 登录链接
    login = 'https://kyfw.12306.cn/passport/web/login'
    # 验证码验证链接
    captchaCheck = 'https://kyfw.12306.cn/passport/captcha/captcha-check'
    # 获取验证码图片
    captchaImage = 'https://kyfw.12306.cn/passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand'
    # 车站Code
    stationCode = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js'
    # 查余票
    queryTicket = 'https://kyfw.12306.cn/otn/leftTicket/query'
    # 查票价
    queryPrice = 'https://kyfw.12306.cn/otn/leftTicket/queryTicketPrice'

    # 检查用户
    checkUser = 'https://kyfw.12306.cn/otn/login/checkUser'
    # 用户登录
    userLogin = 'https://kyfw.12306.cn/otn/login/userLogin'

    uamtk = 'https://kyfw.12306.cn/passport/web/auth/uamtk'

    uamauthclient = 'https://kyfw.12306.cn/otn/uamauthclient'

    initMy12306 = 'https://kyfw.12306.cn/otn/index/initMy12306'

    # 确定订单信息
    submitOrderRequest = 'https://kyfw.12306.cn/otn/leftTicket/submitOrderRequest'
    # initDc,获取globalRepeatSubmitToken
    initDc = 'https://kyfw.12306.cn/otn/confirmPassenger/initDc'
    # 获取曾经用户列表
    getPassengerDTOs = 'https://kyfw.12306.cn/otn/confirmPassenger/getPassengerDTOs'
    # 检查订单信息
    checkOrderInfo = 'https://kyfw.12306.cn/otn/confirmPassenger/checkOrderInfo'
    # 获取队列查询
    getQueueCount = 'https://kyfw.12306.cn/otn/confirmPassenger/getQueueCount'
    # 确认队列
    confirmSingleForQueue = 'https://kyfw.12306.cn/otn/confirmPassenger/confirmSingleForQueue'
常量

将项目里使用到的常量都集中在一个文件里,方便管理。特别需要注意的是座位类型不是固定的,我在写整个项目时发现有几个座位类型是变化的,比如硬座在我写本文的时候是1,但是之前都是A1,其他座位类型变化情况参见具体代码内容。

from  codePlatform import  CJYClient# 12306登录用户名userName = '你的12306账号'# 12306密码password = '你的12306密码'# 超级鹰打码平台chaoJiYing = CJYClient('你的超级鹰平台账户', '你的超级鹰平台密码','896970')# 验证码图片路径captchaFilePath = 'captcha.jpg'# 车站电报码路径stationCodesFilePath = 'stationsCode.txt'# 座位类型,订票下单时需要传入noSeat            = 'WZ' #无座firstClassSeat    = 'M'  #一等座secondClassSeat   = 'O'  #二等座advancedSoftBerth = '6'  #高级软卧 A6hardBerth         = '3'  #硬卧 A3softBerth         = '4'  #软卧 A4moveBerth         = 'F'  #动卧hardSeat          = '1'  #硬座 A1businessSeat      = '9'  #商务座 A9
Utility 工具类

通常项目中都会有很多共用方法,我们将这些方法抽离出来放在一个工具类文件里,如此可以减少冗余代码。

from datetime import datetimefrom stationCodes import StationCodesfrom color import Coloredimport timeimport requestsclass Utility(object):    @classmethod
    def getSession(self):

        session = requests.session()  # 创建session会话

        session.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"
        }        # session.verify = False  # 跳过SSL验证
        return session    @classmethod
    def redColor(self,str):
        return  Colored.red(str)    @classmethod
    def greenColor(self, str):
        return Colored.green(str)    # 反转字典    @classmethod
    def reversalDict(self, dict):
        return {v: k for k, v in dict.items()}    # 将历时转化为小时和分钟的形式    @classmethod
    def getDuration(self, timeStr):
        duration = timeStr.replace(':', '时') + '分'
        if duration.startswith('00'):            return duration[4:]        return duration    # 获取一个时间是周几    @classmethod
    def getWeekDay(self, date):
        weekDayDict = {            0: '周一',            1: '周二',            2: '周三',            3: '周四',            4: '周五',            5: '周六',            6: '周天',
        }
        day = datetime.strptime(date, '%Y-%m-%d').weekday()        return weekDayDict[day]    # 转化日期格式    @classmethod
    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)    # 检查购票日期是否合理    @classmethod
    def checkDate(self, date):

        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(Colored.red('请注意合理的乘车日期范围是:{} 至 {}'.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(Colored.red('Error:您输入的乘车日期:{}, 当前系统日期:{}, 预售截止日期:{}'.format(trainDate, localDate, deadDate)))            return False, None    @classmethod
    def getDate(self,dateStr):
       # dateStr格式为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)    # 根据车站名获取电报码    @classmethod
    def getStationCode(self, station):
        codesDict = StationCodes().getCodesDict()        if station in codesDict.keys():            return codesDict[station]    # 输入出发地和目的地    @classmethod
    def inputStation(self, str):
        station = input('{}:\n'.format(str))        if not station in StationCodes().getCodesDict().keys():
            print(Colored.red('Error:车站列表里无法查询到{}'.format(station)))
            station = input('{}:\n'.format(str))        return station    # 输入乘车日期    @classmethod
    def inputTrainDate(self):
        trainDate = input('请输入购票时间,格式为2018-01-01:\n')        try:
            trainTimeStruct = time.strptime(trainDate, "%Y-%m-%d")        except:
            print('时间格式错误,请重新输入')
            trainDate = input('请输入购票时间,格式为2018-01-01:\n')
        timeFlag, trainDate = Utility.checkDate(trainDate)        if timeFlag == False:
            trainDate = input('请输入购票时间,格式为2018-01-01:\n')
            timeFlag, trainDate = Utility.checkDate(trainDate)        return trainDate    @classmethod
    def getTrainDate(self,dateStr):
        # 返回格式 Wed Aug 22 2018 00: 00:00 GMT + 0800 (China Standard Time)
        # 转换成时间数组
        timeArray = time.strptime(dateStr, "%Y%m%d")        # 转换成时间戳
        timestamp = time.mktime(timeArray)        # 转换成localtime
        timeLocal = time.localtime(timestamp)        # 转换成新的时间格式
        GMT_FORMAT = '%a %b %d %Y %H:%M:%S GMT+0800 (China Standard Time)'
        timeStr = time.strftime(GMT_FORMAT, timeLocal)        return timeStr

特别要注意一下getTrainDate方法里返回时间字符串格式,我使用Firefox浏览器抓包时发现格式是Wed+Aug+22+2018+00:00:00+GMT+0800+(China+Standard+Time),但是在项目里使用此格式时会发现无法请求到数据。后来使用Google浏览器抓包发后现时间字符串里没有+符号。



作者:东东隆东抢
链接:https://www.jianshu.com/p/5954d7c52d22


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消