上一篇《Python-requests-12306-登陆》介绍了下12306登陆的整个过程,这篇文章来介绍下用python模拟购票的全过程。
首先,我们先来用浏览器亲自体验下购票,看看是怎样的一个过程。
这里打开Fiddler抓包工具,进行抓包
第一步,肯定是要先登陆这个就不介绍了,大家可以参考上一篇文章
OK 整个手动购票过程结束了
我们再来看下抓包工具的抓取结果,进行简单分析下
12306这种网站,大家就不要偷懒了,要每一个都点金去看看,是不是需要进行访问,
写的过程中也吃了不少苦 ,看似不怎么重要的,也没什么重要值返回,就没有去访问,结果购票就失败,
所以尽可能的多多观察下。
最后给大家上代码,中间说下访问url时遇到的几个需要查找的参数,也很容易,看到需要提交哪个参数大家就ctrl+f 在fiddler里面进行查找就OK
1) tk:Py0PTvupTTEIM1_Wj4-aOFvzdZDHA0Mg-62W2Qsdp2p0
复制这个值查找就,黄色部分就是第一次出现该值的链接
2)这个不用说也能看出来
分别是 出发日期, 始发站简称, 终点站简称, 成人
下面的访问返回的json数据很重要,用 | 隔开了 一个一个和的值都是很重要的,split之后打印出来,再在前面分别加上序号,再对比下网页上面显示的数据,可得
[23] 软卧
[26] 硬卧
[28] 硬座
[29] 无座
[30] 二等座
[31] 一等座
[32] 商务
前面还有一些乱七八糟的英文字符串到后面用到的时候我们再来解释
3)这里面提交的参数secretStr 我们按照上面说到的复制到fiddler里面查找会发现它就是查票结果里面split之后的第一个,但是我们不能直接提交 要用unquote处理下
4)这里要提交的REPEAT_SUBMIT_TOKEN,复制参数查找
惊不惊喜,激不激动
5)这里面就有一个坑了
cancel_flag 这个2 是乘客在你得到乘客data里面的code
passengerTicketStr 这个一大串的 大家眼睛要看仔细了 O 0 一个是字母一个是数字
REPEAT_SUBMIT_TOKEN同4
6)
train_date :按照格式制作一个就可以
train_no :
leftTicket:
train_location:
REPEAT_SUBMIT_TOKEN:同4
7)这个就不介绍了 基本和6差不多
8)订单号
这里面的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写了很多抢票的程序,来学习
代码需要优化的地方有很多,
比如:
1.添加个买票成功后发送短信提醒等等.
2.无票时,刷票的过程等等…
想想就觉得麻烦,就这样吧。