python爱好者python3技术总结大数据 爬虫Python AI Sql

python3.x 购买12306火车票

2018-01-01  本文已影响499人  旧城城旧

先看预定结果:


订票结果.png
12306未完成订单显示结果
12306未完成订单显示结果

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

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

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

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

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

ps.下面开始吧

分析购买流程

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

检查用户是否登陆

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

经过这三部曲,可以说你已经在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":{}}

初始化购票页面

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

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

上一篇下一篇

猜你喜欢

热点阅读