python3.x 购买12306火车票

先看预定结果:


订票结果.png

12306未完成订单显示结果

12306未完成订单显示结果

ps. 说明我们是完完全全的预定成功了的。

还需要说明的是:这次的预定是使用手动输入验证码,我上一篇文章对自定校验验证码做出了处理,但是效率比较低。在知乎上有为朋友说他正在使用机器学习来跑,但是结果不知道如何,但是我下一次会将两部分整合起来。但是只能用于学习而不能实际抢票。

2017年的计划完成了吗?2018年的计划开始写了吗?新的一年,我们大家一起学python。

好吧,不bb了,看我文章的同学都知道,我比较随意,写的东西并不像作品,更像是大家一起吹牛一样。希望能给大家要给简单的,轻松的学习途径。

今天我想谈谈对12306进行火车票的购买,呀,你居然火车票都不会买哦,是的,我以前是不会的。但是现在我会买简单的火车票了。

ps.下面开始吧

分析购买流程

  1. 检查用户是都登陆
  2. 检查用户
  3. 检查车次是否能被下单(通俗易懂)
  4. 初始化购票页面
  5. 获取乘客信息
  6. 确认订单信息
  7. 检查车票预定队列
  8. 下单
    下面就来说说每一步吧,可能你们都不需要这些了,只需要源代码就可以完全看懂。但是,我还是说说我的学习过程吧。

检查用户是否登陆

12306这个检查用户是都登陆,我觉得已经是比较好的,就拿我目前的水平来说,下面分析分析。它经历了三个步骤:

  • login登陆,就是简简单单的带着用户名和密码进行了登陆,但是它返回了一个uuid,我没有仔细的数,不知道是不是uuid,反正就是很长一串字符.我们就姑且叫uuid好了。字段为:uamtk,tk其实就是token的意思。
    登陆接口

  • uamtk进行了认证,后面个单词猜不出来是哪个单词。再次返回一个类似新的uuid。字段为:newapptk
    uamtk接口

  • uamauthclient再次带着第二部的newapptk再去认证一次客户端。
    uamauthclient接口

经过这三部曲,可以说你已经在12306服务器挂上号了。就可以对他干任何可以干的事情。

检查用户

顾名思义就是看看该用户是都进行了登陆,没有参数,就只是简简单单的访问下后台,信息都在cookie和session里面。返回的信息包含在一个flag字段中。
检查用户接口
这是为了防止cookie,session被篡改,但是我觉得也没啥用。呵呵
大家自己仔细想想就应该明白,你就算这次我不篡改,我下个请求改还不是一样的呀。脱裤子放屁-->多此一举
接口返回数据:

{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"flag":true},"messages":[],"validateMessages":{}}

检查下单请求

这个请求的作用是,来判断是的这个车次信息能被你下单么?这个接口的参数就有点多,基本都是跟用户买当前车次的信息。但是会检查是都该用户是否有未完成的订单。
参数信息如下:

   "secretStr" 车次,需要进行解码
     "train_date": 出发日期
     "back_train_date"  返回日期
     "tour_flag": "dc"  单程/ 往返(wc)
     "purpose_codes":  "ADULT"  普通/学生(0X00)
     "query_from_stati":  出发车站 ,可以在查询车次接口中得到
     "query_to_station":  返回车站,  可以在查询车次接口中得到
     "undefined": ""  应该是跟返回数据相关

可以看到,跟哪个用户没有一点信息,只有跟车次的数据。
因为它根本不需要知道谁需要购买这个车次,只需要知道这个车次是都能被人购买即可。
返回结果是要么可以,要么不可以咯,一样的是用一个字段的true/false来表示的。当你有未完成的订单的时候,这里就会被检查出来,提示您有问完成的订单。
接口返回数据:

{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":"N","messages":[],"validateMessages":{}}

初始化购票页面

买票的人都知道,我们点了预定之后会跳转到一个选择用户的页面,这就是初始化购票页面,这个页面做的事情可多了去了。

  • globalRepeatSubmitToken 全局token,这一步之后很多请求都会跟这个数据有关
  • key_check_isChange 不知道具体意义是什么,但是跟车次信息相关。

initDc接口
处理的核心代码:

def initDc():
    """
    在进行点击了预定,初始化订票页面
    :return:
    """
    print("初始化订单数据")
    url = "https://kyfw.12306.cn/otn/confirmPassenger/initDc"
    data = {
        "_json_att": ""
    }
    resp = req.post(url, data=data, headers=headers)
    # print(resp.text)
    # 反回一个验证通过信息
    if resp.status_code == requests.codes.ok:
        a1 = re.search(r'globalRepeatSubmitToken.+', resp.text).group()
        globalRepeatSubmitToken = re.sub(r'(globalRepeatSubmitToken)|(=)|(\s)|(;)|(\')', '', a1)

        b1 = re.search(r'key_check_isChange.+', resp.text).group().split(',')[0]
        key_check_isChange = re.sub(r'(key_check_isChange)|(\')|(:)', '', b1)

        return (globalRepeatSubmitToken, key_check_isChange)

这样我们就拿到了这两个需要用到东西。为后面的进行步骤打了基础。

获取乘客信息

其实就目前来说,这个接口我都是没有处理的,因为这个不是图形化的东西,最重要的是抢票要速度,谁tm还去给你输入用户数据,都是写在代码里面了。所以我就多bb这个接口
获取乘客接口
参数:

 data = {
        "_json_att": "",
        "REPEAT_SUBMIT_TOKEN": REPEAT_SUBMIT_TOKEN 这个参数就是我们在初始化页面的时候获取到的。
    }

接口返回:

"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"isExist":true,"exMsg":"","two_isOpenClick":["93","95","97","99"],"other_isOpenClick":["91","93","98","99","95","97"],"normal_passengers":[用户数据],"dj_passengers":[]},"messages":[],"validateMessages":{}}
返回的结果中,用户数据会被使用到。我们这里为了效率,直接先写死的。

确认订单信息

确认订单信息,确认的是用户和车次之间的关联。假如都不知道是那个人和那辆车,那还买个毛线的票是吧。

确认订单和用户接口
参数:

 js信息:https://kyfw.12306.cn/otn/resources/merged/passengerInfo_js.js?scriptVersion=1.9058
所有的分析都来自于该js,有兴趣的同学自己跟踪,我就不一一的讲咯。

    cancel_flag:2  默认
    bed_level_order_num:000000000000000000000000000000  默认
    passengerTicketStr:  用户信息
    oldPassengerStr:用户数据
    tour_flag:dc 
    randCode: 需要重新获取验证码,为空
    whatsSelect:1  是否是常用联系人中选择的需要购买车票的人
    _json_att:
    REPEAT_SUBMIT_TOKEN:初始化initDc接口中得到的数据

检查车票预定队列

检查预定队列,做过多线程开发的都应该知道,多个线程访问统一资源的时候,会出现线程不安全,所以需要加锁,锁定该资源,在这里的车票就是资源,我们必须确定这个车次还有,才能做订票,不然车都没了,还订个啥。
队列接口
参数:

       "train_date": train_data,
        "train_no": trick_data[len(trick_data) - 1][2],
        "stationTrainCode": trick_data[len(trick_data) - 1][3],
        # 1:硬卧 3:硬座
        "seatType": "3",
        "fromStationTelecode": trick_data[len(trick_data) - 1][6],
        "toStationTelecode": trick_data[len(trick_data) - 1][7],
        "leftTicket": trick_data[len(trick_data) - 1][12],
        "purpose_codes": "00",
        "train_location": trick_data[len(trick_data) - 1][15],
        "_json_att": "",
        "REPEAT_SUBMIT_TOKEN": REPEAT_SUBMIT_TOKEN
这些数据其实都是来自于车次信息,所以我就不想过多的解释了。

请求数据和返回结果:

请求参数:
{'train_date': 'Mon Jan  1 2018 00:00:00 GMT+0800 (中国标准时间)', 'train_no': '78000K95180E',
 'stationTrainCode': 'K9518',
 'seatType': '3',
 'fromStationTelecode': 'GIW',
 'toStationTelecode': 'ZIW',
 'leftTicket': 'fRHQaP8JPYKHxyvfihr70ZJYDi2VYncf4DPG%2FWI6ZmA4DYyN', 
'purpose_codes': '00', 
'train_location': 'W2', 
'_json_att': '', 
'REPEAT_SUBMIT_TOKEN': 'a8faf49bca59d540d55662d1692a7dc4'}

返回结果:
{"validateMessagesShowId":"_validatorMessage",
"status":true,
"httpstatus":200,
"data":{"count":"0",
"ticket":"15",
"op_2":"false",
"countT":"0",
"op_1":"false"},"messages":[],"validateMessages":{}}

最后一梭子,下单

在页面上就是一个确定的事情,这张票就是你的了,当然如果没有了余票,就购买失败了呗。
这里有一点我需要说明的是,乘客数据的生成。
这五个参数中,有两个参数需要注意 passengerTicketStroldPassengersStr

passengerTicketStr 是以下划线"_"分隔当每一个乘客信息组成的字符串,对应每个乘客信息字符串组成如下:

  1. 座位编号,0,票类型,乘客名,证件类型,证件号,手机号码,保存常用联系人(Y或N)
    如:1,0,1,姓名,1(身份证),522.。。,187xxx,Y

同样 oldPassengersStr 也是以下划线"_"分隔每个乘客信息组成的字符串,对应每个乘客信息字符串组成如下:

2.. 乘客名,证件类型,证件号,乘客类型
如:姓名,1,522xxx,1(成人)

在上面的信息中座位编号是指是什么座位类型,硬座,软座...
票类型指的是,成人票,学生票等的编码
证件类型指的是二代身份证,学生证,签证等的编码.

注意:在组合 oldPassengersStr 乘客信息字符串时,未尾会多一个下划线,提交请求是一定要补上。

js中说明的以上数据部分来源:

   ticket_type: {adult: "1", child: "2", student: "3", disability: "4"},
    ticket_type_name: {"1": "成人票", "2": "孩票", "3": "学生票", "4": "伤残军人票"},
    tour_flag: {dc: "dc", wc: "wc", fc: "fc", gc: "gc", lc: "lc", lc1: "l1", lc2: "l2"},
    passenger_type: {adult: "1", child: "2", student: "3", disability: "4"},
    passenger_card_type: {two: "1", one: "2", tmp: "3", passport: "B", work: "H", hongkong_macau: "C", taiwan: "G"},
    request_flag: {isAsync: "1"},
    ticket_query_flag: {query_commom: "00", query_student: "0X00"},
    seatType: {yz_type: "1"},
    special_areas: {lso: "LSO", dao: "DAO", ado: "ADO", nqo: "NQO", tho: "THO"}

请求接口
参数:

 data = {
        "passengerTicketStr": passengerTicketStr,
        "oldPassengerStr": oldPassengerStr,
        "randCode": "",
        "purpose_codes": "00",
        "key_check_isChange": key_check_isChange,
        "leftTicketStr": trick_data[len(trick_data) - 1][12],
        "train_location": trick_data[len(trick_data) - 1][15],
        "choose_seats": "",
        "seatDetailType": "000",
        "whatsSelect": "1",
        "roomType": "00",
        "dwAll": "N",
        "_json_att": "",
        "REPEAT_SUBMIT_TOKEN": REPEAT_SUBMIT_TOKEN
    }

参数和返回结果:

传递参数:
{'passengerTicketStr': '1,0,1,姓名,1,522,,N', 'oldPassengerStr': '姓名,1,5221,1_', 'randCode': '', 'purpose_codes': '00', 'key_check_isChange': '675DA8E11714F951FD8026D010742D859C9250E5A20B32604EFF5476', 'leftTicketStr': 'fRHQaP8JPYKHxyvfihr70ZJYDi2VYncf4DPG%2FWI6ZmA4DYyN', 'train_location': 'W2', 'choose_seats': '', 'seatDetailType': '000', 'whatsSelect': '1', 'roomType': '00', 'dwAll': 'N', '_json_att': '', 'REPEAT_SUBMIT_TOKEN': 'a8faf49bca59d540d55662d1692a7dc4'}

返回的结果:
{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"submitStatus":true},"messages":[],"validateMessages":{}}

从上面的结果来看,我们是订票成功了的。

大家一起学习......
有情况简书走一波,小红心的走起来,谢谢!
我必须先给自己点一个,我贱贱的笑了。

最后,奉上我的github地址,该项目在ticket包下,修改conf/conf.ini配置文件之后,直接运行即可。
github

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容