47.测试运行以及完整代码
2020-08-30 本文已影响0人
M_小七
测试运行以及完整代码
学习目标
- 了解 购票的整个逻辑
6.1 测试运行
执行12306.funk12306.py,购票成功最后显示的结果
输入要购票的乘车人的下标0
请输入要购买的坐席类型的拼音,如果输入错误,将强行购买无座,能回家就行了,还要tm什么自行车!:yingwo
{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"ifShowPassCode":"N","canChooseBeds":"N","canChooseSeats":"N","choose_Seats":"MOP9","isCanChooseMid":"N","ifShowPassCodeTime":"1","submitStatus":true,"smokeStr":""},"messages":[],"validateMessages":{}}
{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"count":"0","ticket":"16","op_2":"false","countT":"0","op_1":"false"},"messages":[],"validateMessages":{}}
此时排队买票的人数为:0
此时该车次的余票数量为:16
{'validateMessagesShowId': '_validatorMessage', 'status': True, 'httpstatus': 200, 'data': {'submitStatus': True}, 'messages': [], 'validateMessages': {}}
{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"queryOrderWaitTimeStatus":true,"count":0,"waitTime":4,"requestId":6478993261703780471,"waitCount":1,"tourFlag":"dc","orderId":null},"messages":[],"validateMessages":{}}
{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"errMsg":"网络传输过程中数据丢失,请查看未完成订单,继续支付!","submitStatus":false},"messages":[],"validateMessages":{}}
Process finished with exit code 0
6.2 项目地图
6.2.1-项目代码地图.png6.3 项目文件结构
6.3.1-项目文件结构.png6.3.1 12306.funk12306.py
import re
import os
import time
import json
import base64
import requests
from pprint import pprint
from utils.captcha import getCode
from utils.parse_date import parseDate
from utils.stations_dict import stations_dict
from utils.parse_passenger import parsePassenger
from utils.parse_seat_type import seat_type_dict
from utils.parse_trains_infos import parseTrainsInfos
redis_timeout = 180 # redis中cookies_dict的过期时间 单位秒
class Funk12306():
def __init__(self, username, password):
self.username = username
self.password = password
self.s = requests.session()
self.s.headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36'
def get_cookies(self):
print('获取登录前的cookies')
"""以上都为获取cookies"""
url = 'https://www.12306.cn/index/'
self.s.get(url)
url = 'https://kyfw.12306.cn/otn/login/conf'
self.s.headers['Referer'] = 'https://kyfw.12306.cn/otn/resources/login.html'
self.s.post(url)
url = 'https://kyfw.12306.cn/otn/index12306/getLoginBanner'
resp = self.s.get(url)
print(resp.text)
# 检查用户是否登录
url = 'https://kyfw.12306.cn/passport/web/auth/uamtk-static'
resp = self.s.post(url, data={'appid': 'otn'})
print(resp.text)
# 获取登录二维码
url = 'https://kyfw.12306.cn/passport/web/create-qr64'
resp = self.s.post(url, data={'appid': 'otn'})
print(resp.text)
uuid = json.loads(resp.text)['uuid']
# 查询二维码状态
url = 'https://kyfw.12306.cn/passport/web/checkqr'
resp = self.s.post(url, data={'appid': 'otn', 'uuid': uuid})
print(resp.text)
"""获取图片验证码并手动输入或打码,发送验证请求"""
# 获取验证码图片
temp = str(time.time() * 1000)[:-5] # 15421 76058 853
url = 'https://kyfw.12306.cn/passport/captcha/captcha-image64?login_site=E&module=login&rand=sjrand&_={}'.format(temp)
resp = self.s.get(url)
print(resp.text)
captcha_img_b64 = json.loads(resp.text)['image']
captcha_img = base64.b64decode(captcha_img_b64)
with open('./imgs/{}.png'.format(temp), 'wb') as f:
f.write(captcha_img)
# 选择打码方式并获取结果
create_type = input(' 1 为手动打码 or 2 为收费平台自动打码,请选择:')
if create_type == '1': # 手动打码
answer_num = input('输入图片编号,从1开始:')
elif create_type == '2': # 接入打码平台
answer_num = getCode('./imgs/{}.png'.format(temp)) # 23
else:
raise Exception('输入错误! 1 为手动打码 or 2 为收费平台自动打码')
# 改图片名字并获取点击坐标
answer_dict = { '1': '37,46,',
'2': '110,46,',
'3': '181,46,',
'4': '253,46,',
'5': '37,116,',
'6': '110,116,',
'7': '181,116,',
'8': '253,116,' }
os.rename('./imgs/{}.png'.format(temp),
'./imgs/{}_{}.png'.format(temp, answer_num))
answer = ''
for i in answer_num:
answer += answer_dict[i]
answer = answer[:-1]
# 验证图片验证码
url = 'https://kyfw.12306.cn/passport/captcha/captcha-check?answer={}&rand=sjrand&login_site=E&_={}'.format(
answer, str(time.time() * 1000)[:-4])
resp = self.s.get(url)
print(json.loads(resp.text))
"""发送登陆请求,以及后续登陆验证"""
# 登录
url = 'https://kyfw.12306.cn/passport/web/login'
data = {'username': self.username,
'password': self.password,
'appid': 'otn',
'answer': answer}
resp = self.s.post(url, data=data)
print(resp.text)
uamtk = json.loads(resp.text)['uamtk']
# 进入登录后的页面 发生302跳转
url = 'https://kyfw.12306.cn/otn/login/userLogin' # 302跳转
self.s.get(url)
url = 'https://kyfw.12306.cn/otn/passport?redirect=/otn/login/userLogin'
self.s.get(url)
# 获取newapptk
url = 'https://kyfw.12306.cn/passport/web/auth/uamtk'
self.s.headers['Referer'] = 'https://kyfw.12306.cn/otn/passport?redirect=/otn/login/userLogin'
resp = self.s.post(url, data={'appid': 'otn'})
print(json.loads(resp.text))
newapptk = json.loads(resp.text)['newapptk']
# 登录后验证
url = 'https://kyfw.12306.cn/otn/uamauthclient'
resp = self.s.post(url, data={'tk': newapptk})
print(json.loads(resp.text))
def buy_ticket(self):
self.s.headers.pop('Referer') # 清除referer
# 获取城市(车站)编码
from_station = input('输入出发城市或车站:')
to_station = input('输入到达城市或车站:')
train_date = input('输入出行日期,格式为2018-12-03:')
from_station_code = stations_dict.get(from_station, '')
to_station_code = stations_dict.get(to_station, '')
# 查询车量信息日志get
url = 'https://kyfw.12306.cn/otn/leftTicket/log?leftTicketDTO.train_date=%s&leftTicketDTO.from_station=%s&leftTicketDTO.to_station=%s&purpose_codes=ADULT' % (train_date, from_station_code, to_station_code)
resp = self.s.get(url)
print(resp.text)
# 查询车量具体信息query
url = 'https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=%s&leftTicketDTO.from_station=%s&leftTicketDTO.to_station=%s&purpose_codes=ADULT' % (train_date, from_station_code, to_station_code)
response = self.s.get(url)
# 解析获取trains_list
trains_list = parseTrainsInfos(json.loads(response.content)['data']['result'])
print('查询的列车信息如下:')
pprint(trains_list)
# 获取选择的列车
train_info_dict = trains_list[int(input('请输入选中车次的下标:'))]
print('选中了列车信息为:')
pprint(train_info_dict)
# 列车信息
secretStr = train_info_dict['secretStr']
leftTicket = train_info_dict['leftTicket']
train_location = train_info_dict['train_location']
# 检查用户是否保持登录成功
url = 'https://kyfw.12306.cn/otn/login/checkUser'
data = {'_json_att': ''}
resp = self.s.post(url, data=data)
print(json.loads(resp.text))
# 点击预定
url = 'https://kyfw.12306.cn/otn/leftTicket/submitOrderRequest'
data = {
'secretStr': secretStr,
'train_date': train_date,
'back_train_date': train_date,
'tour_flag': 'dc', # dc 单程 wf 往返
'purpose_codes': 'ADULT', # 成人
'query_from_station_name': from_station,
'query_to_station_name': to_station,
'undefined': ''
}
resp = self.s.post(url, data=data)
print(resp.text)
# 订单初始化 获取REPEAT_SUBMIT_TOKEN key_check_isChange
url = 'https://kyfw.12306.cn/otn/confirmPassenger/initDc'
data = {'_json_att': ''}
response = self.s.post(url, data=data)
repeat_submit_token = re.search(r"var globalRepeatSubmitToken = '([a-z0-9]+)';",
response.content.decode()).group(1)
key_check_isChange = re.search("'key_check_isChange':'([A-Z0-9]+)'", response.content.decode()).group(1)
# 获取用户信息
# 需要 REPEAT_SUBMIT_TOKEN
url = 'https://kyfw.12306.cn/otn/confirmPassenger/getPassengerDTOs'
data = {'_json_att': '',
'REPEAT_SUBMIT_TOKEN': repeat_submit_token}
response = self.s.post(url, data=data)
# 解析并构造乘客信息列表
passenger_list = parsePassenger(json.loads(response.content))
print('获取乘客信息有:')
pprint(passenger_list)
passenger_info_dict = passenger_list[int(input('输入要购票的乘车人的下标'))]
# 坐席类型
try:
seat_type = seat_type_dict[input('请输入要购买的坐席类型的拼音,如果输入错误,将强行购买无座,能回家就行了,还要tm什么自行车!:')]
except:
seat_type = seat_type_dict['wuzuo']
# 构造乘客信息
passengerTicketStr = '%s,0,1,%s,%s,%s,%s,N' % (
seat_type, passenger_info_dict['passenger_name'],
passenger_info_dict['passenger_id_type_code'],
passenger_info_dict['passenger_id_no'],
passenger_info_dict['passenger_mobile_no'])
oldPassengerStr = '%s,%s,%s,1_' % (
passenger_info_dict['passenger_name'],
passenger_info_dict['passenger_id_type_code'],
passenger_info_dict['passenger_id_no'])
# 检查选票人信息
url = 'https://kyfw.12306.cn/otn/confirmPassenger/checkOrderInfo'
data = {
'cancel_flag': '2', # 未知
'bed_level_order_num': '000000000000000000000000000000', # 未知
'passengerTicketStr': passengerTicketStr.encode('utf-8'), # O,0,1,靳文强,1,142303199512240614,18335456020,N
'oldPassengerStr': oldPassengerStr.encode('utf-8'), # 靳文强,1,142303199512240614,1_
'tour_flag': 'dc', # 单程
'randCode': '',
'whatsSelect': '1',
'_json_att': '',
'REPEAT_SUBMIT_TOKEN': repeat_submit_token
}
resp = self.s.post(url, data=data)
print(resp.text)
# 提交订单,并获取排队人数,和车票的真实余数
url = 'https://kyfw.12306.cn/otn/confirmPassenger/getQueueCount'
data = {
'train_date': parseDate(train_date), # Fri Nov 24 2017 00:00:00 GMT+0800 (中国标准时间)
'train_no': train_info_dict['train_no'], # 6c0000G31205
'stationTrainCode': train_info_dict['stationTrainCode'], # G312
'seatType': seat_type, # 席别
'fromStationTelecode': train_info_dict['from_station'], # one_train[6]
'toStationTelecode': train_info_dict['to_station'], # ? one_train[7]
'leftTicket': train_info_dict['leftTicket'], # one_train[12]
'purpose_codes': '00',
'train_location': train_info_dict['train_location'], # one_train[15]
'_json_att': '',
'REPEAT_SUBMIT_TOKEN': repeat_submit_token
}
resp = self.s.post(url, data=data)
print(resp.text)
print('此时排队买票的人数为:{}'.format(json.loads(resp.text)['data']['count']))
ticket = json.loads(resp.text)['data']['ticket']
print('此时该车次的余票数量为:{}'.format(ticket))
if ticket == '0':
print('没有余票,购票失败')
return '没有余票,购票失败'
# 确认订单,进行扣票 需要 key_check_isChange
url = 'https://kyfw.12306.cn/otn/confirmPassenger/confirmSingleForQueue'
data = {
'passengerTicketStr': passengerTicketStr.encode('utf-8'),
'oldPassengerStr': oldPassengerStr.encode('utf-8'),
'randCode': '',
'purpose_codes': '00',
'key_check_isChange': key_check_isChange,
'leftTicketStr': leftTicket,
'train_location': train_location, # one_train[15]
'choose_seats': '', # 选择坐席 ABCDEF 上中下铺 默认为空不选
'seatDetailType': '000',
'whatsSelect': '1',
'roomType': '00',
'dwAll': 'N', # ?
'_json_att': '',
'REPEAT_SUBMIT_TOKEN': repeat_submit_token
}
resp = self.s.post(url, data=data)
print(json.loads(resp.text))
if json.loads(resp.text)['status'] == False or json.loads(resp.text)['data']['submitStatus'] == False:
print('扣票失败')
return '扣票失败'
# 排队等待 返回waittime 获取 requestID 和 orderID
timestamp = str(int(time.time() * 1000)) # str(time.time() * 1000)[:-4]
url = 'https://kyfw.12306.cn/otn/confirmPassenger/queryOrderWaitTime?random=%s&tourFlag=dc&_json_att=&REPEAT_SUBMIT_TOKEN=%s' % (
timestamp, repeat_submit_token)
resp = self.s.get(url)
print(resp.text)
try:
orderID = json.loads(resp.text)['data']['orderId']
except:
# 排队等待 返回waittime 获取 requestID 和 orderID
timestamp = str(int(time.time() * 1000)) # str(time.time() * 1000)[:-4]
url = 'https://kyfw.12306.cn/otn/confirmPassenger/queryOrderWaitTime?random=%s&tourFlag=dc&_json_att=&REPEAT_SUBMIT_TOKEN=%s' % (
timestamp, repeat_submit_token)
resp = self.s.get(url)
print(resp.text)
try:
orderID = json.loads(resp.text)['data']['orderId']
except:
return '购票失败'
# 订单结果
url = 'https://kyfw.12306.cn/otn/confirmPassenger/resultOrderForDcQueue'
data = {
'orderSequence_no': orderID,
'_json_att': '',
'REPEAT_SUBMIT_TOKEN': repeat_submit_token
}
resp = self.s.post(url, data=data)
print(resp.text)
def run(self):
# 登录 获取cookies
self.get_cookies()
# 买票
self.buy_ticket()
if __name__ == '__main__':
username = input('请输入12306账号:')
password = input('请输入12306密码:')
funk = Funk12306(username, password)
funk.run()
6.3.2 12306.get_stations_dict.py
import re
import json
import requests
# 获取车站编号字符串 station_version=1.9076
url = 'https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9076'
resp = requests.get(url)
stations_str = re.search("'(.*)'", resp.text).group(1)
# 获取{城市(车站):编码, ...} 键值对
stations_dict = {}
for station in stations_str.split('@'):
if station == '': # 按@切会切出空字符串
continue
stations_dict[station.split('|')[1]] = station.split('|')[2]
with open('./utils/stations_dict.py', 'w', encoding='utf8') as f:
f.write('stations_dict = ')
json.dump(stations_dict, f, ensure_ascii=False, indent=4)
6.3.3 12306.utils.captcha.py
import hashlib
import requests
from datetime import datetime
RUOUSER = 'xxxx'
RUOPASS = 'xxxx'
# 若快 12306打码 直接传入本地文件路径
def getCode(img):
url = "http://api.ruokuai.com/create.json"
fileBytes = open(img, "rb").read()
paramDict = {
'username': RUOUSER,
'password': RUOPASS,
'typeid': 6113, # 专门用来识别12306图片验证的类型id
'timeout': 90,
'softid': 117157, # 推广用的
'softkey': '70acaa1e477a4374a7736264a24b974b' # 推广用的
}
paramKeys = ['username',
'password',
'typeid',
'timeout',
'softid',
'softkey'
]
result = http_upload_image(url, paramKeys, paramDict, fileBytes)
return result['Result']
# 若快12306打码 上传图片
def http_upload_image(url, paramKeys, paramDict, filebytes):
timestr = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
boundary = '------------' + hashlib.md5(timestr.encode("utf8")).hexdigest().lower()
boundarystr = '\r\n--%s\r\n' % (boundary)
bs = b''
for key in paramKeys:
bs = bs + boundarystr.encode('ascii')
param = "Content-Disposition: form-data; name=\"%s\"\r\n\r\n%s" % (key, paramDict[key])
# print param
bs = bs + param.encode('utf8')
bs = bs + boundarystr.encode('ascii')
header = 'Content-Disposition: form-data; name=\"image\"; filename=\"%s\"\r\nContent-Type: image/gif\r\n\r\n' % ('sample')
bs = bs + header.encode('utf8')
bs = bs + filebytes
tailer = '\r\n--%s--\r\n' % (boundary)
bs = bs + tailer.encode('ascii')
headers = {'Content-Type': 'multipart/form-data; boundary=%s' % boundary,
'Connection': 'Keep-Alive',
'Expect': '100-continue',
}
response = requests.post(url, params='', data=bs, headers=headers)
return response.json()
if __name__ == '__main__':
# 测试
ret = getCode('../imgs/1544627949970.png')
print(ret)
6.3.4 12306.utils.parse_date.py
import datetime
def parseDate(train_date):
"""
:param train_date: '2017-12-12'
:return:
"""
week_name = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
month_name = "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split()
y, m, d = map(int, train_date.split("-"))
weekday = datetime.datetime(y, m, d).weekday()
# Fri Nov 24 2017 00:00:00 GMT+0800 (中国标准时间)
return "{0} {1} {2} {3} 00:00:00 GMT+0800 (中国标准时间)".format(week_name[weekday], month_name[m - 1], d, y)
6.3.5 12306.utils.parse_passenger.py
def parsePassenger(passenger_dict):
passengers_infos_list = passenger_dict['data']['normal_passengers']
passenger_list = []
for passenger_info in passengers_infos_list:
passenger_info_dict = {}
passenger_info_dict['passenger_name'] = passenger_info.get('passenger_name', '')
passenger_info_dict['passenger_gender'] = passenger_info.get('sex_name', '')
passenger_info_dict['passenger_id_type_code'] = passenger_info.get('passenger_id_type_code', '')
passenger_info_dict['passenger_id_no'] = passenger_info.get('passenger_id_no', '')
passenger_info_dict['passenger_mobile_no'] = passenger_info.get('mobile_no', '')
passenger_list.append(passenger_info_dict)
return passenger_list
6.3.6 12306.utils.parse_seat_type.py
seat_type_dict = {
"erdengzuo": "O", # 二等座
"yingwo": "3", # 硬卧
"yingzuo": "1", # 硬座
"wuzuo": "1", # 无座
"ruanwo": "4", # 软卧
"ruanzuo": "2", # 软座
"dongwo": "F", # 动卧
"yidengzuo": "M", # 一等座
"gaojiruanwo": "6", # 高级软座
"shangwuzuo": "9", # 商务座
"tedengzuo": "P", # 特等座
}
6.3.7 12306.utils.parse_trains_infos.py
import urllib.parse
def parseTrainsInfos(trains_list):
"""
解析列车信息列表, 返回列车信息列表
"""
trains_infos_list = []
if trains_list == []:
return []
for train_info in trains_list:
train_info_list = train_info.split('|')
train_info_dict = {}
# 构造列车信息
train_info_dict['secretStr'] = urllib.parse.unquote(train_info_list[0]) # secretStr ;为''时无法购买车票
# train_info_list[1] 预定/列车停运
train_info_dict['train_no'] = urllib.parse.unquote(train_info_list[2]) # train_no
train_info_dict['stationTrainCode'] = urllib.parse.unquote(train_info_list[3]) # stationTrainCode 即车次 # 展示
train_info_dict['start_station'] = urllib.parse.unquote(train_info_list[4]) # 始发站 # 展示
train_info_dict['end_station'] = urllib.parse.unquote(train_info_list[5]) # 终点站 # 展示
train_info_dict['from_station'] = urllib.parse.unquote(train_info_list[6]) # 出发站 # 展示
train_info_dict['to_station'] = urllib.parse.unquote(train_info_list[7]) # 到达站 # 展示
train_info_dict['from_time'] = urllib.parse.unquote(train_info_list[8]) # 出发时间 # 展示
train_info_dict['to_time'] = urllib.parse.unquote(train_info_list[9]) # 到达时间 # 展示
train_info_dict['use_time'] = urllib.parse.unquote(train_info_list[10]) # 时长 # 展示
train_info_dict['buy_able'] = urllib.parse.unquote(train_info_list[11]) # 能否购买 Y 可以购买 N 不可以购买 IS_TIME_NOT_BUY 停运 # 展示
train_info_dict['leftTicket'] = urllib.parse.unquote(train_info_list[12]) # leftTicket
train_info_dict['start_time'] = urllib.parse.unquote(train_info_list[13]) # 车次始发日期 # 展示
train_info_dict['train_location'] = urllib.parse.unquote(train_info_list[15]) # train_location 不知道是啥??
train_info_dict['from_station_no'] = urllib.parse.unquote(train_info_list[16]) # 出发站编号
train_info_dict['to_station_no'] = urllib.parse.unquote(train_info_list[17]) # 到达站编号
# 14,18,19,20,27,34,35未知
train_info_dict['gaojiruanwo'] = urllib.parse.unquote(train_info_list[21]) # 高级软卧 # 展示
train_info_dict['qita'] = urllib.parse.unquote(train_info_list[22]) # 其他 # 展示
train_info_dict['ruanwo'] = urllib.parse.unquote(train_info_list[23]) # 软卧 # 展示
train_info_dict['ruanzuo'] = urllib.parse.unquote(train_info_list[24]) # 软座 # 展示
train_info_dict['tedengzuo'] = urllib.parse.unquote(train_info_list[25]) # 特等座 # 展示
train_info_dict['wuzuo'] = urllib.parse.unquote(train_info_list[26]) # 无座 # 展示
train_info_dict['yingwo'] = urllib.parse.unquote(train_info_list[28]) # 硬卧 # 展示
train_info_dict['yingzuo'] = urllib.parse.unquote(train_info_list[29]) # 硬座 # 展示
train_info_dict['erdengzuo'] = urllib.parse.unquote(train_info_list[30]) # 二等座 # 展示
train_info_dict['yidengzuo'] = urllib.parse.unquote(train_info_list[31]) # 一等座 # 展示
train_info_dict['shangwuzuo'] = urllib.parse.unquote(train_info_list[32]) # 商务座 # 展示
train_info_dict['dongwo'] = urllib.parse.unquote(train_info_list[33]) # 动卧 # 展示
trains_infos_list.append(train_info_dict)
return trains_infos_list
6.3.8 12306.utils.stations_dict.py
该文件由 12306.get_stations_dict.py运行生成
stations_dict = {
"北京北": "VAP",
"北京东": "BOP",
"北京": "BJP",
......
小结
- 了解 购票的整个逻辑