Python-requests-12306-购票

上一篇《Python-requests-12306-登陆》介绍了下12306登陆的整个过程,这篇文章来介绍下用python模拟购票的全过程。

  • 首先,我们先来用浏览器亲自体验下购票,看看是怎样的一个过程。
    这里打开Fiddler抓包工具,进行抓包

  • 第一步,肯定是要先登陆这个就不介绍了,大家可以参考上一篇文章

  • 第二部,登陆之后进去查票页面,查票
    始发站:青岛
    终点站:哈尔滨
    日期:2018-06-01
    Python-requests-12306-购票_第1张图片

  • 然后买车次为 G1248这躺车
    Python-requests-12306-购票_第2张图片

  • 第四步,点击预定后会出现选择乘客页面,然后选择一个乘客后点击提交订单
    Python-requests-12306-购票_第3张图片

  • 第五步,进去信息核对界面,点击确认
    Python-requests-12306-购票_第4张图片

  • OK 整个手动购票过程结束了

  • 我们再来看下抓包工具的抓取结果,进行简单分析下
    Python-requests-12306-购票_第5张图片
    12306这种网站,大家就不要偷懒了,要每一个都点金去看看,是不是需要进行访问,

  • 写的过程中也吃了不少苦 ,看似不怎么重要的,也没什么重要值返回,就没有去访问,结果购票就失败,
    所以尽可能的多多观察下。

  • 最后给大家上代码,中间说下访问url时遇到的几个需要查找的参数,也很容易,看到需要提交哪个参数大家就ctrl+f 在fiddler里面进行查找就OK
    1) tk:Py0PTvupTTEIM1_Wj4-aOFvzdZDHA0Mg-62W2Qsdp2p0
    Python-requests-12306-购票_第6张图片
    复制这个值查找就,黄色部分就是第一次出现该值的链接
    Python-requests-12306-购票_第7张图片
    2)这个不用说也能看出来
    分别是 出发日期, 始发站简称, 终点站简称, 成人
    下面的访问返回的json数据很重要,用 | 隔开了 一个一个和的值都是很重要的,split之后打印出来,再在前面分别加上序号,再对比下网页上面显示的数据,可得
    [23] 软卧
    [26] 硬卧
    [28] 硬座
    [29] 无座
    [30] 二等座
    [31] 一等座
    [32] 商务
    前面还有一些乱七八糟的英文字符串到后面用到的时候我们再来解释
    Python-requests-12306-购票_第8张图片

3)这里面提交的参数secretStr 我们按照上面说到的复制到fiddler里面查找会发现它就是查票结果里面split之后的第一个,但是我们不能直接提交 要用unquote处理下
Python-requests-12306-购票_第9张图片

4)这里要提交的REPEAT_SUBMIT_TOKEN,复制参数查找
Python-requests-12306-购票_第10张图片
惊不惊喜,激不激动
Python-requests-12306-购票_第11张图片

5)这里面就有一个坑了
cancel_flag 这个2 是乘客在你得到乘客data里面的code
passengerTicketStr 这个一大串的 大家眼睛要看仔细了 O 0 一个是字母一个是数字
REPEAT_SUBMIT_TOKEN同4
Python-requests-12306-购票_第12张图片

6)
Python-requests-12306-购票_第13张图片
train_date :按照格式制作一个就可以
train_no :
Python-requests-12306-购票_第14张图片
leftTicket:
Python-requests-12306-购票_第15张图片
train_location:
Python-requests-12306-购票_第16张图片
REPEAT_SUBMIT_TOKEN:同4
7)这个就不介绍了 基本和6差不多
Python-requests-12306-购票_第17张图片
8)订单号
Python-requests-12306-购票_第18张图片
这里面的orderId 要访问这个链接2次才能得到orderId 中间time.sleep(4)
9)最后 说下全国所有站名的简称
https://kyfw.12306.cn/otn/resources/js/framework/station_name.js
就在这个链接里面 访问下然后进行简单的处理做成个dict给下面用就可以

  • 啰嗦了这么多,上代码,可能看起来比较乱,过了一段时间可能执行可能会很多报错,
    这个网站写起来真的是太麻烦了,再也不想写第二个这样的网站,不过学习下还是很值得的整个过程下来,当你用程序买票成功的那一瞬间,觉得很有成就感…
# -*- coding: utf-8 -*-
import json
import re
import time
import requests
import logging
logging.captureWarnings(True)
#my_txt我的账号密码等信息文件
from ticket_12306.my_txt import *
from urllib.parse import unquote
#验证码平台文件
from chaojiying.chaojiying_Python.chaojiying import Chaojiying_Client

def station_name():
    url = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js'
    station_data = re.findall(r"'([\s\S]*?)'", requests.get(url).text)[0]
    station_name_dict = {}

    for i in station_data.split('@'):
        if i:
            station_info_list = i.split('|')
            station_name_dict[station_info_list[1]] = station_info_list[2]

    return station_name_dict

def station_code(station):

    station_name_dict = station_name()

    from_station = input("请输入{}站:".format(station))
    if str(station_name_dict.keys()).find(from_station)!=-1:
        return station_name_dict.get(from_station),from_station
    else:
        print('站名输入错误,请输入正确的站名!')
        station_code(station)

def train_date():
    train_dt = input('请输入出行日期:')
    if re.match(r'2018-[01][0-9]-[0-3][0-9]', train_dt):
        return train_dt
    else:
        print('请输入正确格式的日期(例如:2018-06-01)')
        train_date()

class order_ticket_12306:

    def __init__(self):
        #创建session会话
        self.sess = requests.Session()
        #跳过ssl验证
        self.sess.verify = False
        #验证码图片地址
        self.captcha_url = 'https://kyfw.12306.cn/passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand'
        #验证码图片存储路径
        self.captcha_file_path = 'captcha.jpg'
        #打码平台
        self.chaojiying = Chaojiying_Client(chaojiying_user, chaojiying_passwd, '96001')
        #station_names 返回tuple(英文简称,中文)
        self.from_station = station_code('始发站')
        self.to_station = station_code('终点站')
        self.train_code = input('请输入车次:').upper()
        self.train_date = train_date()
        self.passenger_name = input('请输入姓名:')
        self.passenger_idcard = input('请输入身份证号:')




    def captcha_download(self):
        '''
        下载验证码图片
        :return:
        '''
        with open(self.captcha_file_path, 'wb') as f:
            image = self.sess.get(self.captcha_url)
            if image.status_code == 200:
                f.write(image.content)
            else:
                print('验证码下载失败, 正在重试...')
                self.captcha_download()

    def get_captcha(self):
        '''
        提交图片到打码平台,获取坐标
        :return:
        '''
        self.captcha_download()
        im = open(self.captcha_file_path, 'rb').read()
        print('chaojiying111111111',self.chaojiying.PostPic(im, 9004))
        res = self.chaojiying.PostPic(im, 9004)
        return res

    def captcha_check(self):
        '''
        提交答案,进行验证
        会返回一个json数据里面有result_code
        如果result_code==4的话为成功,否则进行重新验证
        :return:
        '''
        captcha_res = self.get_captcha()
        captcha_code = captcha_res.get('pic_str').replace('|', ',')
        check_data = {
                'answer': captcha_code,
                'login_site': 'E',
                'rand': 'sjrand',
        }
        check_url = 'https://kyfw.12306.cn/passport/captcha/captcha-check'
        res = self.sess.post(check_url, data=check_data).text
        print(res)
        check_result = str(json.loads(res).get('result_code'))
        print(check_result)
        if check_result!='4':
            print('验证码校验失败, 正在重试...')
            try:
                print('验证码验证报错....')
                pic_id = captcha_res.get('pic_id')
                #报错到打码平台
                self.chaojiying.ReportError(pic_id)
            except:
                print('验证码验证报错失败...')
            self.captcha_check()
        print(11111, res)


    def login(self):
        '''
        12306登陆
        :return:
        '''
        login_headers = {
            'Accept': 'application/json, text/javascript, */*; q=0.01',
            'Accept-Encoding': 'gzip, deflate, br',
            'Accept-Language': 'zh-CN,zh;q=0.9',
            'Connection': 'keep-alive',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36',
            'X-Requested-With': 'XMLHttpRequest'
        }
        login_data = {
            'username': user_12306,
            'password': passwd_12306,
            'appid': 'otn'
        }
        login_url = 'https://kyfw.12306.cn/passport/web/login'
        res = self.sess.post(login_url, data=login_data, headers = login_headers).text
        print(22222, res)
        return res

    def auth_check(self):
        '''
        登陆状态检查
        :return:
        '''
        check_url_1 = 'https://kyfw.12306.cn/passport/web/auth/uamtk'
        check_data_1 = {
            'appid':'otn'
        }
        res_1 = self.sess.post(check_url_1, data= check_data_1).text
        print('auth_check1111:', res_1)
        tk_params = json.loads(res_1).get('newapptk')

        check_url_2 = 'https://kyfw.12306.cn/otn/uamauthclient'
        check_data_2 = {
            'tk':tk_params
        }
        res_2 = self.sess.post(check_url_2, data=check_data_2).text
        print('auth_check2222:',res_2)

        get_url = 'https://kyfw.12306.cn/otn/index/initMy12306'
        res_test = self.sess.get(get_url).text
        print('auth_check3333:',res_test)
        return res_test

    def check_tickets(self):
        '''
        查票
        :return:
        '''

        self.auth_check()
        '''
        查票
        :param train_date:
        :param from_station:
        :param to_station:
        :return:
        '''

        train_dict = {}
        check_url = 'https://kyfw.12306.cn/otn/leftTicket/query'
        check_params = {
            'leftTicketDTO.train_date': self.train_date,
            'leftTicketDTO.from_station': self.from_station[0],
            'leftTicketDTO.to_station': self.to_station[0],
            'purpose_codes': 'ADULT'
        }
        print(check_params)
        check_res = self.sess.get(check_url, params=check_params).text
        print('check_tickets2222:', check_res)
        train_code_list = [trainno.split('|')[3] for trainno in json.loads(check_res).get('data').get('result')]
        print(88888888888888888888, train_code_list)
        ticket_info = [train_info for train_info in json.loads(check_res).get('data').get('result') if train_info.split('|')[3]==self.train_code][0]
        '''
            [23] 软卧
            [26] 硬卧
            [28] 硬座
            [29] 无座
            [30] 二等座
            [31] 一等座
            [32] 商务
        '''
        ticket_left = ticket_info.split('|')[30]
        if not re.findall(r'(有)|(\d+)', ticket_left):
            print('无票...刷新等待')
            self.check_tickets()
        train_dict['train_no'] = ticket_info.split('|')[2]
        train_dict['leftTicket'] = ticket_info.split('|')[12]
        train_dict['train_location'] = ticket_info.split('|')[15]
        train_dict['to_station'] = ticket_info.split('|')[5]
        train_dict['from_station'] = ticket_info.split('|')[4]
        train_dict['secretstr'] = unquote(ticket_info.split('|')[0])

        return train_dict

    def check_user(self):
        '''
        登陆状态检查
        :return:
        '''
        check_url_1 = 'https://kyfw.12306.cn/otn/login/checkUser'
        check_data_1 = {
            '_json_att':''
        }
        check_res = self.sess.post(check_url_1, data=check_data_1).text
        # if json.loads(check_res).get('status'):
        #     print('check user success...')
        print('check_user:1111', check_res)

    def submit_order(self, secretstr):
        submit_url = 'https://kyfw.12306.cn/otn/leftTicket/submitOrderRequest'
        submit_data = {
            'back_train_date': time.strftime('%Y-%m-%d', time.localtime()),
            'purpose_codes': 'ADULT',
            'query_from_station_name':self.from_station[1],
            'query_to_station_name':self.to_station[1],
            'secretStr':secretstr,
            'tour_flag':'dc',
            'train_date':self.train_date,
            'undefined':''
        }
        res = self.sess.post(submit_url, data=submit_data).text
        print('submit_order:', res)
        return res


    def get_params_dict(self, secretstr):

        self.submit_order(secretstr)

        page_url = 'https://kyfw.12306.cn/otn/confirmPassenger/initDc'
        page_data = {
            '_json_att':''
        }
        res = self.sess.post(page_url, data=page_data).text
        print('get_params_dict:', res)
        time.sleep(10)
        params_dict = {}
        params_dict['repeat_submit_token'] = re.findall(r"globalRepeatSubmitToken = '(.*?)';", res)[0]
        print(params_dict['repeat_submit_token'])
        params_dict['key_check_isChange'] = re.findall(r",'key_check_isChange':'(.*?)',", res)[0]
        print(params_dict['key_check_isChange'])
        print('查票......',params_dict)

        return params_dict


    def get_passenger_data(self, repeat_submit_token):

        self.check_user()

        people_url = 'https://kyfw.12306.cn/otn/confirmPassenger/getPassengerDTOs'
        people_data = {
            '_json_att':'',
            'REPEAT_SUBMIT_TOKEN': repeat_submit_token
        }
        res = self.sess.post(people_url, data=people_data).text
        print('get_passenger_data11111:',res)
        passenger_data_list = json.loads(res).get('data').get('normal_passengers')
        passenger_data = [passenger for passenger in passenger_data_list if passenger['passenger_name']==self.passenger_name and passenger['passenger_id_no']==self.passenger_idcard][0]
        return passenger_data

    def check_order(self, repeat_submit_token, passenger_index):
        check_url = 'https://kyfw.12306.cn/otn/confirmPassenger/checkOrderInfo'
        check_data = {
            'cancel_flag':passenger_index,
            'bed_level_order_num':'000000000000000000000000000000',
            'passengerTicketStr':'O,0,1,{},1,{},,N'.format(self.passenger_name, self.passenger_idcard),
            'oldPassengerStr':'{},1,{},1_'.format(self.passenger_name, self.passenger_idcard),
            'tour_flag':'dc',
            'randCode':'',
            'whatsSelect':1,
            '_json_att':'',
            'REPEAT_SUBMIT_TOKEN':repeat_submit_token
        }
        res = self.sess.post(check_url, data=check_data).text
        print('check_order1111111111111111', res)
        return res

    def get_queue_count(self, repeat_submit_token, train_dict):
        get_url = 'https://kyfw.12306.cn/otn/confirmPassenger/getQueueCount'
        get_data = {
            'train_date':str(time.strftime('%a %b %d %Y', time.strptime(self.train_date, '%Y-%m-%d'))) + ' 00:00:00 GMT+0800 (中国标准时间)',
            'train_no':train_dict.get('train_no'),
            'stationTrainCode':train_dict.get('train_code'),
            'seatType':'O',
            'fromStationTelecode':train_dict.get('from_satation'),
            'toStationTelecode':train_dict.get('to_station'),
            'leftTicket':train_dict.get('leftTicket'),
            'purpose_codes':'00',
            'train_location':train_dict.get('train_location'),
            '_json_att':'',
            'REPEAT_SUBMIT_TOKEN':repeat_submit_token
        }
        res = self.sess.post(get_url, data=get_data).text
        print('get_queue_count111111111111111:', res)
        return res


    def confirmpassenger(self, key_check_isChange, train_dict, repeat_submit_token):
        confirm_url = 'https://kyfw.12306.cn/otn/confirmPassenger/confirmSingleForQueue'
        confirm_data = {
            'passengerTicketStr':'O,0,1,{},1,{},,N'.format(self.passenger_name, self.passenger_idcard),
            'oldPassengerStr':'{},1,{},1_'.format(self.passenger_name, self.passenger_idcard),
            'randCode':'',
            'purpose_codes':'00',
            'key_check_isChange':key_check_isChange,
            'leftTicketStr':train_dict.get('leftTicket'),
            'train_location':train_dict.get('train_location'),
            'choose_seats':'',
            'seatDetailType':'000',
            'whatsSelect':1,
            'roomType':'00',
            'dwAll':'N',
            '_json_att':'',
            'REPEAT_SUBMIT_TOKEN':repeat_submit_token
        }
        res = self.sess.post(confirm_url, data=confirm_data).text
        print('confirmpassenger1111111111111111111:', res)

    def get_orderid(self, repeat_submit_token, passenger_data, train_dict, key_check_isChange):

        passenger_index =passenger_data.get('code')

        self.check_order(repeat_submit_token, passenger_index)

        self.get_queue_count(repeat_submit_token, train_dict)

        self.confirmpassenger(key_check_isChange, train_dict, repeat_submit_token)

        orderid_url = 'https://kyfw.12306.cn/otn/confirmPassenger/queryOrderWaitTime'
        order_params = {
            'random': str(int(time.time()*1000)),
            'tourFlag': 'dc',
            '_json_att': '',
            'REPEAT_SUBMIT_TOKEN': repeat_submit_token
        }
        res = self.sess.get(orderid_url, params=order_params).text
        orderid = json.loads(res).get('data').get('orderId')
        print('get_orderid111111:', orderid)
        if not orderid:
            time.sleep(4)
            orderid_url = 'https://kyfw.12306.cn/otn/confirmPassenger/queryOrderWaitTime'
            order_params = {
                'random': str(int(time.time() * 1000)),
                'tourFlag': 'dc',
                '_json_att': '',
                'REPEAT_SUBMIT_TOKEN': repeat_submit_token
            }
            res = self.sess.get(orderid_url, params=order_params).text
            orderid = json.loads(res).get('data').get('orderId')
            print('get_orderid22222222222', orderid)
            return orderid


    def result_order(self, orderid, repeat_submit_token):
        result_url = 'https://kyfw.12306.cn/otn/confirmPassenger/resultOrderForDcQueue'
        result_data = {
            'orderSequence_no':orderid,
            '_json_att':'',
            'REPEAT_SUBMIT_TOKEN':repeat_submit_token
        }
        res = self.sess.post(result_url, data=result_data).text
        print('result_order1111111111111111111:', res)
        return res

    def pay_order(self, repeat_submit_token):
        pay_url = 'https://kyfw.12306.cn/otn//payOrder/init?random={}'.format(str(int(time.time()*1000)))
        pay_data = {
            '_json_att':'',
            'REPEAT_SUBMIT_TOKEN':repeat_submit_token
        }
        res = self.sess.post(pay_url, data=pay_data).text
        print('pay_order11111111:', res)
        return res


    def order_ticket(self):
        #setp_1 验证码验证
        self.captcha_check()
        #setp_2 login
        self.login()
        #step_3 查票
        train_dict = self.check_tickets()
        # step_4 get_params_dict
        secretstr = train_dict.get('secretstr')
        params_dict = self.get_params_dict(secretstr)
        repeat_submit_token = params_dict.get('repeat_submit_token')
        key_check_isChange = params_dict.get('key_check_isChange')
        # step_5 get_passenger_data
        passenger_data = self.get_passenger_data(repeat_submit_token)
        orderid = self.get_orderid(repeat_submit_token, passenger_data, train_dict, key_check_isChange)
        self.result_order(orderid, repeat_submit_token)
        self.pay_order(repeat_submit_token)



if __name__ == '__main__':
    order_ticekt = order_ticket_12306()

    order_ticekt.order_ticket()
  • 再来看下结果把,

  • 登陆进入未完成的订单
    Python-requests-12306-购票_第19张图片
    Python-requests-12306-购票_第20张图片

  • 支付部分嘛 手动进行就OK了

#总结:
好多人用python写了很多抢票的程序,来学习
代码需要优化的地方有很多,
比如:
1.添加个买票成功后发送短信提醒等等.
2.无票时,刷票的过程等等…
想想就觉得麻烦,就这样吧。

你可能感兴趣的:(python爬虫)