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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