From aa6d628c8294ec401eeb311e29419d659ddd5f53 Mon Sep 17 00:00:00 2001 From: wenxianping <931128603@qq.com> Date: Sat, 20 Jan 2018 23:23:51 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81=E6=9B=B4=E6=96=B0=E6=96=B0=E7=89=88?= =?UTF-8?q?=E7=99=BB=E5=BD=95=202=E3=80=81=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 + config/urlConf.py | 89 +++++++ init/login.py | 368 +++++++++++++-------------- init/select_ticket_info.py | 78 +++--- myException/UserPasswordException.py | 2 + myUrllib/httpUtils.py | 106 +++++--- tkcode | Bin 12447 -> 11760 bytes 7 files changed, 391 insertions(+), 258 deletions(-) create mode 100644 config/urlConf.py create mode 100644 myException/UserPasswordException.py diff --git a/README.md b/README.md index 10e2c6b..2c4ac8e 100644 --- a/README.md +++ b/README.md @@ -164,4 +164,10 @@ - 优化提交订单有很大记录无限排队的情况,感谢群里的小伙伴提供的思路 - 修改休眠时间为早上6点 +- 2018.1.20更新,好久没跟新了,群里的小伙伴说登录不行了,今晚抽空改了一版登录,妥妥的 + - 更新新版登录功能,经测试,更稳定有高效 + - 优化手动打码功能 + - 更新请求第三方库 + - 优化若干代码,小伙伴尽情的放肆起来 + diff --git a/config/urlConf.py b/config/urlConf.py new file mode 100644 index 0000000..d2e7651 --- /dev/null +++ b/config/urlConf.py @@ -0,0 +1,89 @@ +import random + +urls = { + "auth": { + "req_url": "https://kyfw.12306.cn/passport/web/auth/uamtk", + "req_type": "post" + }, + "login": { + "req_url": "https://kyfw.12306.cn/passport/web/login", + "req_type": "post" + }, + "getCodeImg": { + "req_url": "https://kyfw.12306.cn/passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand&{0}".format(random.random()), + "req_type": "get" + }, + "codeCheck": { + "req_url": "https://kyfw.12306.cn/passport/captcha/captcha-check", + "req_type": "post" + }, + "loginInit": { + "req_url": "https://kyfw.12306.cn/otn/login/init", + "req_type": "get" + }, + "getUserInfo": { + "req_url": "https://kyfw.12306.cn/otn/index/initMy12306", + "req_type": "get" + }, + "userLogin": { + "req_url": "https://kyfw.12306.cn/otn/login/userLogin", + "req_type": "get" + }, + "uamauthclient": { + "req_url": "https://kyfw.12306.cn/otn/uamauthclient", + "req_type": "post" + }, + "initdc_url": { + "req_url": "https://kyfw.12306.cn/otn/confirmPassenger/initDc", + "req_type": "get" + }, + "get_passengerDTOs": { + "req_url": "https://kyfw.12306.cn/otn/confirmPassenger/getPassengerDTOs", + "req_type": "post" + }, + "select_url": { + "req_url": "https://kyfw.12306.cn/otn/leftTicket/queryZ?leftTicketDTO.train_date={0}&leftTicketDTO.from_station={1}&leftTicketDTO.to_station={2}&purpose_codes=ADULT", + "req_type": "post" + }, + "check_user_url": { + "req_url": "https://kyfw.12306.cn/otn/login/checkUser", + "req_type": "post" + }, + "submit_station_url": { + "req_url": "https://kyfw.12306.cn/otn/leftTicket/submitOrderRequest", + "req_type": "post" + }, + "checkOrderInfoUrl": { + "req_url": "https://kyfw.12306.cn/otn/confirmPassenger/checkOrderInfo", + "req_type": "post" + }, + "getQueueCountUrl": { + "req_url": "https://kyfw.12306.cn/otn/confirmPassenger/getQueueCount", + "req_type": "post" + }, + "checkQueueOrderUrl": { + "req_url": "https://kyfw.12306.cn/otn/confirmPassenger/confirmSingleForQueue", + "req_type": "post" + }, + "checkRandCodeAnsyn": { + "req_url": "https://kyfw.12306.cn/otn/passcodeNew/checkRandCodeAnsyn", + "req_type": "post" + }, + "codeImgByOrder": { + "req_url": "https://kyfw.12306.cn/otn/passcodeNew/getPassCodeNew?module=passenger&rand=randp&%s" % random.random(), + "req_type": "post" + }, + "queryOrderWaitTimeUrl": { + "req_url": "https://kyfw.12306.cn/otn/confirmPassenger/queryOrderWaitTime", + "req_type": "post" + }, + "queryMyOrderNoCompleteUrl": { + "req_url": "https://kyfw.12306.cn/otn/queryOrder/queryMyOrderNoComplete", + "req_type": "post" + }, + "initNoCompleteUrl": { + "req_url": "https://kyfw.12306.cn/otn/queryOrder/initNoComplete", + "req_type": "post" + } + +} \ No newline at end of file diff --git a/init/login.py b/init/login.py index 28ae5d9..6fbb34d 100644 --- a/init/login.py +++ b/init/login.py @@ -9,203 +9,199 @@ from time import sleep from config.ticketConf import _get_yaml from PIL import Image from damatuCode.damatuWeb import DamatuApi +from myException.UserPasswordException import UserPasswordException from myUrllib import myurllib2 -codeimg = 'https://kyfw.12306.cn/otn/passcodeNew/getPassCodeNew?module=login&rand=sjrand&%s' % random.random() +class GoLogin: + def __init__(self, httpClint, urlConf): + self.httpClint = httpClint + self.randCode = "" + self.urlConf = urlConf -def cookietp(): - stoidinput("获取Cookie") - Url = "https://kyfw.12306.cn/otn/login/init" - myurllib2.get(Url) - # for index, c in enumerate(myurllib2.cookiejar): - # stoidinput(c) + def cookietp(self): + print("正在获取cookie") + url = self.urlConf["loginInit"]["req_url"] + self.httpClint.send(url) + # Url = "https://kyfw.12306.cn/otn/login/init" + # myurllib2.get(Url) + # for index, c in enumerate(myurllib2.cookiejar): + # stoidinput(c) - -def readImg(): - """ - 增加手动打码,只是登录接口,完全不用担心提交订单效率 - 思路 - 1.调用PIL显示图片 - 2.图片位置说明,验证码图片中每个图片代表一个下标,依次类推,1,2,3,4,5,6,7,8 - 3.控制台输入对应下标,按照英文逗号分开,即可手动完成打码, - :return: - """ - - global randCode - stoidinput("下载验证码...") - img_path = './tkcode' - result = myurllib2.get(codeimg) - try: - open(img_path, 'wb').write(result) - if _get_yaml()["is_aotu_code"]: - randCode = DamatuApi(_get_yaml()["damatu"]["uesr"], _get_yaml()["damatu"]["pwd"], img_path).main() - else: - img = Image.open('./tkcode') - img.show() - codexy() - except OSError as e: - print (e) - pass - - -def stoidinput(text): - """ - 正常信息输出 - :param text: - :return: - """ - print "\033[34m[*]\033[0m %s " % text - - -def errorinput(text): - """ - 错误信息输出 - :param text: - :return: - """ - print "\033[32m[!]\033[0m %s " % text - return False - - -def codexy(): - """ - 获取验证码 - :return: str - """ - - Ofset = raw_input("[*] 请输入验证码: ") - select = Ofset.split(',') - global randCode - post = [] - offsetsX = 0 # 选择的答案的left值,通过浏览器点击8个小图的中点得到的,这样基本没问题 - offsetsY = 0 # 选择的答案的top值 - for ofset in select: - if ofset == '1': - offsetsY = 46 - offsetsX = 42 - elif ofset == '2': - offsetsY = 46 - offsetsX = 105 - elif ofset == '3': - offsetsY = 45 - offsetsX = 184 - elif ofset == '4': - offsetsY = 48 - offsetsX = 256 - elif ofset == '5': - offsetsY = 36 - offsetsX = 117 - elif ofset == '6': - offsetsY = 112 - offsetsX = 115 - elif ofset == '7': - offsetsY = 114 - offsetsX = 181 - elif ofset == '8': - offsetsY = 111 - offsetsX = 252 - else: + def readImg(self): + """ + 增加手动打码,只是登录接口,完全不用担心提交订单效率 + 思路 + 1.调用PIL显示图片 + 2.图片位置说明,验证码图片中每个图片代表一个下标,依次类推,1,2,3,4,5,6,7,8 + 3.控制台输入对应下标,按照英文逗号分开,即可手动完成打码, + :return: + """ + print ("下载验证码...") + codeimgUrl = self.urlConf["getCodeImg"]["req_url"] + img_path = './tkcode' + result = self.httpClint.send(codeimgUrl) + try: + open(img_path, 'wb').write(result) + if _get_yaml()["is_aotu_code"]: + self.randCode = DamatuApi(_get_yaml()["damatu"]["uesr"], _get_yaml()["damatu"]["pwd"], img_path).main() + else: + img = Image.open('./tkcode') + img.show() + self.codexy() + except OSError as e: + print (e) pass - post.append(offsetsX) - post.append(offsetsY) - randCode = str(post).replace(']', '').replace('[', '').replace("'", '').replace(' ', '') + def codexy(self): + """ + 获取验证码 + :return: str + """ -def go_login(): - """ - 登陆 - :param user: 账户名 - :param passwd: 密码 - :return: - """ - user, passwd = _get_yaml()["set"]["12306count"][0]["uesr"], _get_yaml()["set"]["12306count"][1]["pwd"] - login_num = 0 - while True: - cookietp() - readImg() - login_num += 1 - randurl = 'https://kyfw.12306.cn/otn/passcodeNew/checkRandCodeAnsyn' - logurl = 'https://kyfw.12306.cn/otn/login/loginAysnSuggest' - surl = 'https://kyfw.12306.cn/otn/login/userLogin' - randdata = { - "randCode": randCode, - "rand": "sjrand" + Ofset = raw_input("请输入验证码: ") + select = Ofset.split(',') + post = [] + offsetsX = 0 # 选择的答案的left值,通过浏览器点击8个小图的中点得到的,这样基本没问题 + offsetsY = 0 # 选择的答案的top值 + for ofset in select: + if ofset == '1': + offsetsY = 46 + offsetsX = 42 + elif ofset == '2': + offsetsY = 46 + offsetsX = 105 + elif ofset == '3': + offsetsY = 45 + offsetsX = 184 + elif ofset == '4': + offsetsY = 48 + offsetsX = 256 + elif ofset == '5': + offsetsY = 36 + offsetsX = 117 + elif ofset == '6': + offsetsY = 112 + offsetsX = 115 + elif ofset == '7': + offsetsY = 114 + offsetsX = 181 + elif ofset == '8': + offsetsY = 111 + offsetsX = 252 + else: + pass + post.append(offsetsX) + post.append(offsetsY) + self.randCode = str(post).replace(']', '').replace('[', '').replace("'", '').replace(' ', '') + + def auth(self): + """认证""" + authUrl = self.urlConf["auth"]["req_url"] + authData = {"appid": "otn"} + tk = self.httpClint.send(authUrl, authData) + return tk + + def codeCheck(self): + """ + 验证码校验 + :return: + """ + codeCheck = self.urlConf["codeCheck"]["req_url"] + codeCheckData = { + "answer": self.randCode, + "rand": "sjrand", + "login_site": "E" } - logdata = { - "loginUserDTO.user_name": user, - "userDTO.password": passwd, - "randCode": randCode - } - ldata = { - "_json_att": None - } - fresult = json.loads(myurllib2.Post(randurl, randdata), encoding='utf8') - checkcode = fresult['data']['msg'] - if checkcode == 'FALSE': - errorinput("验证码有误,第{}次尝试重试".format(login_num)) + fresult = self.httpClint.send(codeCheck, codeCheckData) + if "result_code" in fresult and fresult["result_code"] == "4": + print ("验证码通过,开始登录..") + return True else: - stoidinput("验证码通过,开始登录..") + if "result_message" in fresult: + print(fresult["result_message"]) sleep(1) - try: - tresult = json.loads(myurllib2.Post(logurl, logdata), encoding='utf8') - if 'data' not in tresult: - errorinput("登录失败: %s" % tresult['messages'][0]) - # elif "messages" in tresult and tresult["messages"][0].find("密码输入错误") is not -1: - # errorinput("登陆失败:{}".format(tresult["messages"][0])) - # break - elif 'messages' in tresult and tresult['messages']: - messages = tresult['messages'][0] - if messages.find("密码输入错误") is not -1: - errorinput("登陆失败:{}".format(tresult["messages"][0])) + self.httpClint.del_cookies() + + def baseLogin(self, user, passwd): + """ + 登录过程 + :param user: + :param passwd: + :return: 权限校验码 + """ + logurl = self.urlConf["login"]["req_url"] + logData = { + "username": user, + "password": passwd, + "appid": "otn" + } + tresult = self.httpClint.send(logurl, logData) + if 'result_code' in tresult and tresult["result_code"] == 0: + print ("登录成功") + tk = self.auth() + if "newapptk" in tk and tk["newapptk"]: + return tk["newapptk"] + else: + return False + elif 'result_message' in tresult and tresult['result_message']: + messages = tresult['result_message'] + if messages.find("密码输入错误") is not -1: + raise UserPasswordException("{0}".format(messages)) + else: + print ("登录失败: {0}".format("".join(tresult))) + print ("尝试重新登陆") + return False + else: + return False + + def getUserName(self, uamtk): + """ + 登录成功后,显示用户名 + :return: + """ + if not uamtk: + return "权限校验码不能为空" + else: + uamauthclientUrl = self.urlConf["uamauthclient"]["req_url"] + data = {"tk": uamtk} + uamauthclientResult = self.httpClint.send(uamauthclientUrl, data) + if "result_code" in uamauthclientResult and uamauthclientResult["result_code"] == 0: + print("欢迎 {} 登录".format(uamauthclientResult["username"])) + return True + else: + return False + + def go_login(self): + """ + 登陆 + :param user: 账户名 + :param passwd: 密码 + :return: + """ + user, passwd = _get_yaml()["set"]["12306count"][0]["uesr"], _get_yaml()["set"]["12306count"][1]["pwd"] + login_num = 0 + while True: + self.cookietp() + self.httpClint.set_cookies(_jc_save_wfdc_flag="dc", _jc_save_fromStation="%u4E0A%u6D77%u8679%u6865%2CAOH", _jc_save_toStation="%u5170%u5DDE%u897F%2CLAJ", _jc_save_fromDate="2018-02-14", _jc_save_toDate="2018-01-16", RAIL_DEVICEID="EN_3_EGSe2GWGHXJeCkFQ52kHvNCrNlkz9n1GOqqQ1wR0i98WsD8Gj-a3YHZ-XYKeESWgCiJyyucgSwkFOzVHhHqfpidLPcm2vK9n83uzOPuShO3Pl4lCydAtQu4BdFqz-RVmiduNFixrcrN_Ny43135JiEtqLaI") + self.readImg() + login_num += 1 + self.auth() + if self.codeCheck(): + uamtk = self.baseLogin(user, passwd) + if uamtk: + if self.getUserName(uamtk): break - else: - errorinput("登录失败: %s" % tresult['messages'][0]) - stoidinput("尝试重新登陆") - else: - stoidinput("登录成功") - myurllib2.Post(surl, ldata) - getUserinfo() - break - except ValueError as e: - if e.message == "No JSON object could be decoded": - print("12306接口无响应,正在重试") - else: - print(e.message) - except KeyError as e: - print(e.message) - except TypeError as e: - print(e.message) - except socket.error as e: - print(e.message) - sleep(1) + + def logout(self): + url = 'https://kyfw.12306.cn/otn/login/loginOut' + result = myurllib2.get(url) + if result: + print ("已退出") + else: + print ("退出失败") -def getUserinfo(): - """ - 登录成功后,显示用户名 - :return: - """ - url = 'https://kyfw.12306.cn/otn/modifyUser/initQueryUserInfo' - data = dict(_json_att=None) - result = myurllib2.Post(url, data) - userinfo = result - name = r'' - try: - stoidinput("欢迎 %s 登录" % re.search(name, result).group(1)) - except AttributeError: - pass - - -def logout(): - url = 'https://kyfw.12306.cn/otn/login/loginOut' - result = myurllib2.get(url) - if result: - stoidinput("已退出") - else: - errorinput("退出失败") - - -if __name__ == "__main__": - main() - # logout() \ No newline at end of file +# if __name__ == "__main__": +# # main() +# # logout() \ No newline at end of file diff --git a/init/select_ticket_info.py b/init/select_ticket_info.py index 82503cd..8754487 100644 --- a/init/select_ticket_info.py +++ b/init/select_ticket_info.py @@ -4,22 +4,24 @@ import datetime import random import re import socket -import threading import urllib import sys import time from collections import OrderedDict + +from config import urlConf from init import login from config.emailConf import sendEmail from config.ticketConf import _get_yaml from damatuCode.damatuWeb import DamatuApi -from init.login import go_login +from init.login import GoLogin from myException.PassengerUserException import PassengerUserException +from myException.UserPasswordException import UserPasswordException from myException.ticketConfigException import ticketConfigException from myException.ticketIsExitsException import ticketIsExitsException from myException.ticketNumOutException import ticketNumOutException -from myUrllib import myurllib2 +from myUrllib.httpUtils import HTTPClient reload(sys) sys.setdefaultencoding('utf-8') @@ -37,6 +39,8 @@ class select: self.secretStr = "" self.ticket_black_list = dict() self.is_check_user = dict() + self.httpClint = HTTPClient() + self.confUrl = urlConf.urls def get_ticket_info(self): """ @@ -137,12 +141,11 @@ class select: 获取提交车票请求token :return: token """ - initdc_url = 'https://kyfw.12306.cn/otn/confirmPassenger/initDc' - initdc_result = myurllib2.get(initdc_url) + initdc_url = self.confUrl["initdc_url"]["req+url"] + initdc_result = self.httpClint.send(initdc_url) token_name = re.compile(r"var globalRepeatSubmitToken = '(\S+)'") ticketInfoForPassengerForm_name = re.compile(r'var ticketInfoForPassengerForm=(\{.+\})?') order_request_params_name = re.compile(r'var orderRequestDTO=(\{.+\})?') - # if token_name and ticketInfoForPassengerForm_name and order_request_params_name: self.token = re.search(token_name, initdc_result).group(1) re_tfpf = re.findall(ticketInfoForPassengerForm_name, initdc_result) re_orp = re.findall(order_request_params_name, initdc_result) @@ -160,15 +163,14 @@ class select: 获取乘客信息 :return: """ - get_passengerDTOs = 'https://kyfw.12306.cn/otn/confirmPassenger/getPassengerDTOs' + get_passengerDTOs = self.confUrl["get_passengerDTOs"]["req_url"] get_data = { '_json_att': None, 'REPEAT_SUBMIT_TOKEN': self.token } - jsonData = json.loads(myurllib2.Post(get_passengerDTOs, get_data)) + jsonData = self.httpClint.send(get_passengerDTOs, get_data) if 'data' in jsonData and jsonData['data'] and 'normal_passengers' in jsonData['data'] and jsonData['data'][ 'normal_passengers']: - # return jsonData['data']['normal_passengers'] normal_passengers = jsonData['data']['normal_passengers'] _normal_passenger = [normal_passengers[i] for i in range(len(normal_passengers))if normal_passengers[i]["passenger_name"] in self.ticke_peoples] return _normal_passenger if _normal_passenger else normal_passengers[0] # 如果配置乘车人没有在账号,则默认返回第一个用户 @@ -182,9 +184,9 @@ class select: raise PassengerUserException("未查找到常用联系人,请先添加联系人在试试") def submitOrderRequestFunc(self, from_station, to_station, station_date=None): - select_url = 'https://kyfw.12306.cn/otn/leftTicket/queryZ?leftTicketDTO.train_date={0}&leftTicketDTO.from_station={1}&leftTicketDTO.to_station={2}&purpose_codes=ADULT'.format( + select_url = self.confUrl["select_url"]["req_url"].format( self.station_date if station_date is None else station_date, from_station, to_station) - station_ticket = json.loads(myurllib2.get(select_url), encoding='utf-8') + station_ticket = self.httpClint.send(select_url) return station_ticket def submitOrderRequestImplement(self, from_station, to_station,): @@ -246,9 +248,9 @@ class select: 检查用户是否达到订票条件 :return: """ - check_user_url = 'https://kyfw.12306.cn/otn/login/checkUser' + check_user_url = self.confUrl["check_user_url"]["req_url"] data = dict(_json_att=None) - check_user = json.loads(myurllib2.Post(check_user_url, data), encoding='utf-8') + check_user = self.httpClint.send(check_user_url, data) check_user_flag = check_user['data']['flag'] if check_user_flag is True: return True @@ -275,7 +277,7 @@ class select: :return: """ - submit_station_url = 'https://kyfw.12306.cn/otn/leftTicket/submitOrderRequest' + submit_station_url = self.confUrl["submit_station_url"]["req_url"] data = [('secretStr', urllib.unquote(self.secretStr)), # 字符串加密 ('train_date', self.time()), # 出发时间 ('back_train_date', self.time()), # 返程时间 @@ -284,7 +286,7 @@ class select: ('query_from_station_name', self.from_station), # 起始车站 ('query_to_station_name', self.to_station), # 终点车站 ] - submitResult = json.loads(myurllib2.Post(submit_station_url, data), encoding='utf-8') + submitResult = self.httpClint.send(submit_station_url, data) if 'data' in submitResult and submitResult['data']: if submitResult['data'] == 'N': print ('出票成功') @@ -355,7 +357,7 @@ class select: :return: """ passengerTicketStrList, oldPassengerStr = self.getPassengerTicketStrListAndOldPassengerStr() - checkOrderInfoUrl = 'https://kyfw.12306.cn/otn/confirmPassenger/checkOrderInfo' + checkOrderInfoUrl = self.confUrl["checkOrderInfoUrl"]["req_url"] data = OrderedDict() data['cancel_flag'] = 2 data['bed_level_order_num'] = "000000000000000000000000000000" @@ -364,7 +366,7 @@ class select: data['tour_flag'] = 'dc' data['whatsSelect'] = 1 data['REPEAT_SUBMIT_TOKEN'] = self.token - checkOrderInfo = json.loads(myurllib2.Post(checkOrderInfoUrl, data, )) + checkOrderInfo = self.httpClint.send(checkOrderInfoUrl, data) if 'data' in checkOrderInfo: if "ifShowPassCode" in checkOrderInfo["data"] and checkOrderInfo["data"]["ifShowPassCode"] == "Y": is_need_code = True @@ -393,7 +395,7 @@ class select: """ l_time = time.localtime(time.time()) new_train_date = time.strftime("%a %b %d %Y", l_time) - getQueueCountUrl = 'https://kyfw.12306.cn/otn/confirmPassenger/getQueueCount' + getQueueCountUrl = self.confUrl["getQueueCountUrl"]["req_url"] data = { 'train_date': str(new_train_date) + " 00:00:00 GMT+0800 (中国标准时间)", 'train_no': self.get_ticketInfoForPassengerForm()['queryLeftTicketRequestDTO']['train_no'], @@ -406,7 +408,7 @@ class select: 'train_location': self.get_ticketInfoForPassengerForm()['train_location'], 'REPEAT_SUBMIT_TOKEN': self.get_token(), } - getQueueCountResult = json.loads(myurllib2.Post(getQueueCountUrl, data)) + getQueueCountResult = self.httpClint.send(getQueueCountUrl, data) if "status" in getQueueCountResult and getQueueCountResult["status"] is True: if "countT" in getQueueCountResult["data"]: ticket = getQueueCountResult["data"]["ticket"] @@ -441,7 +443,7 @@ class select: """ passengerTicketStrList, oldPassengerStr = self.getPassengerTicketStrListAndOldPassengerStr() - checkQueueOrderUrl = "https://kyfw.12306.cn/otn/confirmPassenger/confirmSingleForQueue" + checkQueueOrderUrl = self.confUrl["checkQueueOrderUrl"]["req_url"] data = { "passengerTicketStr": self.set_type + "," + ",".join(passengerTicketStrList).rstrip("_{0}".format(self.set_type)), "oldPassengerStr": "".join(oldPassengerStr), @@ -460,9 +462,9 @@ class select: for i in range(3): if is_node_code: print("正在使用自动识别验证码功能") - randurl = 'https://kyfw.12306.cn/otn/passcodeNew/checkRandCodeAnsyn' - codeimg = 'https://kyfw.12306.cn/otn/passcodeNew/getPassCodeNew?module=passenger&rand=randp&%s' % random.random() - result = myurllib2.get(codeimg) + checkRandCodeAnsyn = self.confUrl["checkRandCodeAnsyn"]["req_url"] + codeImgByOrder = self.confUrl["codeImgByOrder"]["req_url"] + result = self.httpClint.send(codeImgByOrder) img_path = './tkcode' open(img_path, 'wb').write(result) randCode = DamatuApi(_get_yaml()["damatu"]["uesr"], _get_yaml()["damatu"]["pwd"], @@ -473,7 +475,7 @@ class select: "_json_att": None, "REPEAT_SUBMIT_TOKEN": self.get_token() } - fresult = json.loads(myurllib2.Post(randurl, randData), encoding='utf8') # 校验验证码是否正确 + fresult = self.httpClint.send(checkRandCodeAnsyn, randData) # 校验验证码是否正确 checkcode = fresult['data']['msg'] if checkcode == 'TRUE': print("验证码通过,正在提交订单") @@ -484,8 +486,7 @@ class select: else: print("不需要验证码") break - # print("".join(data)) - checkQueueOrderResult = json.loads(myurllib2.Post(checkQueueOrderUrl, data)) + checkQueueOrderResult = self.httpClint.send(checkQueueOrderUrl, data) if "status" in checkQueueOrderResult and checkQueueOrderResult["status"]: c_data = checkQueueOrderResult["data"] if "data" in checkQueueOrderResult else {} if 'submitStatus' in c_data and c_data['submitStatus'] is True: @@ -509,12 +510,6 @@ class select: 排队获取订单等待信息,每隔3秒请求一次,最高请求次数为20次! :return: """ - # queryOrderWaitTimeUrl = "https://kyfw.12306.cn/otn/confirmPassenger/queryOrderWaitTime" - # data = { - # "random": "{0}{1}".format(int(time.time()), random.randint(1, 9)), - # "tourFlag": "dc", - # "REPEAT_SUBMIT_TOKEN": self.get_token(), - # } num = 1 while True: _random = int(round(time.time() * 1000)) @@ -523,10 +518,9 @@ class select: print("超出排队时间,自动放弃,正在重新刷票") break try: - # queryOrderWaitTimeUrl = "https://kyfw.12306.cn/otn/confirmPassenger/queryOrderWaitTime?random={0}&tourFlag=dc&_json_att=&REPEAT_SUBMIT_TOKEN={1}".format(_random, self.get_token()) data = {"random": _random, "tourFlag": "dc"} - queryOrderWaitTimeUrl = "https://kyfw.12306.cn/otn/confirmPassenger/queryOrderWaitTime" - queryOrderWaitTimeResult = json.loads(myurllib2.Post(queryOrderWaitTimeUrl, data)) + queryOrderWaitTimeUrl = self.confUrl["queryOrderWaitTimeUrl"]["req_url"] + queryOrderWaitTimeResult = self.httpClint.send(queryOrderWaitTimeUrl, data) except ValueError: queryOrderWaitTimeResult = {} if queryOrderWaitTimeResult: @@ -562,10 +556,10 @@ class select: :return: """ self.initNoComplete() - queryMyOrderNoCompleteUrl = "https://kyfw.12306.cn/otn/queryOrder/queryMyOrderNoComplete" + queryMyOrderNoCompleteUrl = self.confUrl["queryMyOrderNoCompleteUrl"]["req_url"] data = {"_json_att": None} try: - queryMyOrderNoCompleteResult = json.loads(myurllib2.Post(queryMyOrderNoCompleteUrl, data)) + queryMyOrderNoCompleteResult = self.httpClint.send(queryMyOrderNoCompleteUrl, data) except ValueError: queryMyOrderNoCompleteResult = {} if queryMyOrderNoCompleteResult: @@ -590,9 +584,9 @@ class select: 获取订单前需要进入订单列表页,获取订单列表页session :return: """ - initNoCompleteUrl = "https://kyfw.12306.cn/otn/queryOrder/initNoComplete" + initNoCompleteUrl = self.confUrl["initNoCompleteUrl"]["req_url"] data = {"_json_att": None} - myurllib2.Post(initNoCompleteUrl, data) + self.httpClint.send(initNoCompleteUrl, data) # def call_submit_ticket(self, function_name=None): # """ @@ -608,7 +602,8 @@ class select: def call_login(self): """登录回调方法""" - go_login() + login = GoLogin(self.httpClint, self.confUrl) + login.go_login() def main(self): self.call_login() @@ -641,6 +636,9 @@ class select: except ticketNumOutException as e: print e.message break + except UserPasswordException as e: + print e.message + break except ValueError as e: if e.message == "No JSON object could be decoded": print("12306接口无响应,正在重试") diff --git a/myException/UserPasswordException.py b/myException/UserPasswordException.py new file mode 100644 index 0000000..290b47f --- /dev/null +++ b/myException/UserPasswordException.py @@ -0,0 +1,2 @@ +class UserPasswordException(Exception): + pass \ No newline at end of file diff --git a/myUrllib/httpUtils.py b/myUrllib/httpUtils.py index e0f6504..45a9c13 100644 --- a/myUrllib/httpUtils.py +++ b/myUrllib/httpUtils.py @@ -1,5 +1,8 @@ # -*- coding: utf8 -*- import datetime +import json +import socket + import requests @@ -10,45 +13,84 @@ class HTTPClient(object): :param method: :param headers: Must be a dict. Such as headers={'Content_Type':'text/html'} """ - self.session = requests.session() - self._set_header() + self.initS() + + def initS(self): + self._s = requests.Session() + self._s.headers.update(self._set_header()) + return self + + def set_cookies(self, **kwargs): + """ + 设置cookies + :param kwargs: + :return: + """ + for k, v in kwargs.items(): + self._s.cookies.set(k, v) + + def del_cookies(self): + """ + 删除所有的key + :return: + """ + self._s.cookies.clear() + + def del_cookies_by_key(self, key): + """ + 删除指定key的session + :return: + """ + self._s.cookies.set(key, None) def _set_header(self): """设置header""" - add_header = { + return { "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", "X-Requested-With": "xmlHttpRequest", - "User-Agent": "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.154 Safari/537.36", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", "Referer": "https://kyfw.12306.cn/otn/login/init", "Accept": "*/*", } - self.session.headers.update(add_header) - def get(self, url, proxy=None, **kwargs): - if proxy: - proxies = {"http": proxy} - else: - proxies = "" - response = self.session.request(method="GET", - url=url, - proxies=proxies, - **kwargs) - if response.status_code == 200: - return response.content - else: - print("请求失败。{0}".format(response)) + def setHeaders(self, headers): + self._s.headers.update(headers) + return self - def post(self, url, data=None, proxy=None, **kwargs): - if proxy: - proxies = {"http": proxy} - else: - proxies = "" - response = self.session.request(method="POST", - url=url, - data=data, - proxies=proxies, - **kwargs) - if response.status_code == 200: - return response.content - else: - print("请求失败。{0}".format(response)) \ No newline at end of file + def getHeadersHost(self): + return self._s.headers["Host"] + + def setHeadersHost(self, host): + self._s.headers.update({"Host": host}) + return self + + def getHeadersReferer(self): + return self._s.headers["Referer"] + + def setHeadersReferer(self, referer): + self._s.headers.update({"Referer": referer}) + return self + + def send(self, url, data=None, **kwargs): + """send request to url.If response 200,return response, else return None.""" + method = "post"if data else "get" + response = self._s.request(method=method, + url=url, + data=data, + **kwargs) + try: + if response.content: + return json.loads(response.content) if method == "post" else response.content + else: + return "" + except ValueError as e: + if e.message == "No JSON object could be decoded": + print("12306接口无响应,正在重试") + else: + print(e.message) + except KeyError as e: + print(e.message) + except TypeError as e: + print(e.message) + except socket.error as e: + print(e.message) \ No newline at end of file diff --git a/tkcode b/tkcode index f252118793bd0a32ed0bfcefffd28ae0483692db..d7eb6d9af360f49606fa45327ec329586336e529 100644 GIT binary patch delta 10680 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 vFOyV_ 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