python3.x 购买12306火车票

作者: 旧城城旧 | 来源:发表于2018-01-01 13:09 被阅读499次

    先看预定结果:


    订票结果.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

    相关文章

      网友评论

      • 5c3b8ee92814:楼主好~2月12306弄了一个慢速排队机制,请问之后您还有在研究抢票吗~
        5c3b8ee92814:@旧城城旧 啊好的谢谢~
        旧城城旧:@开门查水表_6236 我很久就没弄那个了
      • 048b046b0bf8:感谢楼主!这是我第一次尝试写爬虫,我正则表达式这一块很弱,很多细节的处理和数据提取参照了你项目的思路(包括前面提到的那个secretStr的解码等等),我用python2写的也能跑了,不过还是手动输入验证,接下来想看看自动识别那一块,感谢楼主的分享!
        旧城城旧:@流转我心间_d3c5 能跑就行,慢慢来,我也是一个业余的,有时间就弄弄。忙起来就没办法。
      • 048b046b0bf8:楼主你好,如果一个操作有多个请求发生的话,之前的请求的response显示failed to load response data,这个要怎么弄啊。
        048b046b0bf8:@旧城城旧 那个字符串是动态生成的吗,好像每次都不一样
        048b046b0bf8:@旧城城旧 刚才那个问题可能是浏览器的原因,现在换成火狐就没事了。另外检查下单请求那里secretStr怎么解析呢
        旧城城旧:@流转我心间_d3c5 你是开启多线程来使用么?
      • 50ffc3ade56b:今天中午的时候,12306的登录流程完全变了:joy:
        旧城城旧:@GUC_ 同志,你确定不是在逗我
        50ffc3ade56b:@旧城城旧 刚看了一下,又恢复原来的流程了:joy:
        旧城城旧:@GUC_ 我操,变了啊
      • 陈后海:下订单的时候有没有遇到‘非法订单请求’情况?
        旧城城旧:@陈后海 我没有遇到过
      • 50ffc3ade56b:用代理开了10个线程跑自动验证,跑了半天就被封,两个词组的验证码只验证了十分之一,决定手动建库,从昨天弄到现在,完成了75%,还要三四个小时才能完成,眼睛都要花了:sob:
        旧城城旧:@GUC_ 可以,搞好了教我
        50ffc3ade56b:@旧城城旧 一共80个类目,还差10个,十多分钟一个
        旧城城旧:@GUC_ 现在怎么样了

      本文标题:python3.x 购买12306火车票

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