python12306买票_Python12306订票

写在前面

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

项目结构

写完整个项目后觉得其实也很简单,无非是使用Session进行多次Get和Post请求,难点在于Post请求时使用的Data从何而来?我们先使用抓包工具(浏览器F12)完成一次12306平台订票之完整过程,对需要进行哪些网络请求心里有个大概印象。使用Session的主要原因是为了避免每次请求数据时都去考虑Cookies,如此可能会方便很多。

我们将整个订票过程中使用到的API 放在一个文件里,原因很简单:一旦某个接口地址改变了,我们只需在此文件里进行修改,无法在代码里到处查找修改,省时省力。我自己之前在写iOS 应用时候也是采用这样的方式。

12306 APIclass 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
#这只是一个半成品,只是实现了,一个完整的过程,对于,如果刷,自己研究 #简单过程 # 第一、getRandAndCookie() 获得cook 和一个随机数用于登录 # 第二、getEnterRandCode() 得到登录时的识别码 # 第三、setuseandpassword(randcode,use,password) 发送随机数、识别码和用户及密码。由于随机数只在内部使用,所以定义成了全局变量, # 第四、GetTrainList() 得到所有车站列表,'@bjb|北京北|VAP|beijingbei|bjb|0' 其中有中文、拼音、拼音缩写、所一个ID(唯一),其主要是可以,通过上面的列表,找到它的唯一ID,TranCityToId('南昌') # 第五、GetTrainNumList(date,fromstationid,tostationid,starttime) 得到哪到哪的所在车次,消息格式如下,其中所以,一下车次的的ID:"id":"650000K1060I" # {"end_station_name":"北京西","end_time":"16:18","id":"650000K1060I","start_station_name":"深圳","start_time":"10:54","value":"K106"} # 通过ChangeToTrainNumId('K106')得到车次ID # 第六、QueryTrain(fromstationid,tostationid,date,stationNum,starttime) 就是点击查询按键,得到是否有能预,格式如下 #       南昌         20:12,    北京西        07:38,11:26,--,--,--,--,10,有,有,--,有,有,--,<a name='btn130_2' class='btn130_2' # 通过choiceSubmitNum(stationNum,trainsubmitinfo)提取出getSelected()消息 # 第七、submitRequest(choiceSubmitNum(stationNum,trainsubmitinfo),date,starttime) 就是点击预按钮 # 第八、getrandCheckCode()得到提交单的识别码 # 第十、CheckInMyTicket(info,randcode,peoples)点击提交,如果成功的话,就会返回{"errMsg":"Y"} # 出于,网络是UTF8格式,所以,必须# -*- coding: utf-8 -*-,(当然,自己转换也是可以的) # 出于这一个控制台信息,所以,识别码的图片在脚本同一目录 #得到头信息

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值