From 499f0fbda7e86a0f5433ea0fc51a1285e315f4d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=87=E8=B4=A4=E5=B9=B3?= Date: Tue, 3 Sep 2019 14:54:01 +0800 Subject: [PATCH] fix login --- TickerConfig.py | 30 ++++---- config/urlConf.py | 2 +- init/login.py | 139 +++++++++++++++++++++++++++++++++++++ init/select_ticket_info.py | 5 +- inter/Query.py | 37 +++++----- myUrllib/httpUtils.py | 7 +- verify/ruokuai.py | 78 --------------------- 7 files changed, 181 insertions(+), 117 deletions(-) delete mode 100644 verify/ruokuai.py diff --git a/TickerConfig.py b/TickerConfig.py index 77444ae..3bccafe 100644 --- a/TickerConfig.py +++ b/TickerConfig.py @@ -19,14 +19,15 @@ STATION_DATES = [ # 填入需要购买的车次(list),"G1353" STATION_TRAINS = [ - "", + "G6142", + "G6174", ] # 出发城市,比如深圳北,就填深圳就搜得到 -FROM_STATION = "" +FROM_STATION = "深圳北" # 到达城市 比如深圳北,就填深圳就搜得到 -TO_STATION = "" +TO_STATION = "隆回" # 座位(list) 多个座位ex: # "商务座", @@ -39,23 +40,24 @@ TO_STATION = "" # "无座", # "动卧", SET_TYPE = [ - "", + "二等座", ] # 当余票小于乘车人,如果选择优先提交,则删减联系人和余票数一致在提交 # bool -IS_MORE_TICKET = False +IS_MORE_TICKET = True # 乘车人(list) 多个乘车人ex: # - "张三" # - "李四" TICKET_PEOPLES = [ - "", + "文贤平", + "李梦云", ] # 12306登录账号 -USER = "" -PWD = "" +USER = "931128603@qq.com" +PWD = "QWERTY" # 加入小黑屋时间默认为5分钟,此功能为了防止僵尸票导致一直下单不成功错过正常的票 TICKET_BLACK_LIST_TIME = 5 @@ -77,12 +79,12 @@ IS_AUTO_CODE = True # password: "授权码" # host: "smtp.qq.com" EMAIL_CONF = { - "IS_MAIL": False, - "email": "", - "notice_email_list": "", - "username": "", - "password": "", - "host": "", + "IS_MAIL": True, + "email": "931128603@qq.com", + "notice_email_list": "931128603@qq.com", + "username": "931128603@qq.com", + "password": "lwvgfrcydzyvbfjf", + "host": "smtp.qq.com", } # 是否开启 pushbear 微信提醒, 使用前需要前往 http://pushbear.ftqq.com 扫码绑定获取 send_key 并关注获得抢票结果通知的公众号 diff --git a/config/urlConf.py b/config/urlConf.py index 236bc65..8ef5e74 100755 --- a/config/urlConf.py +++ b/config/urlConf.py @@ -139,7 +139,7 @@ urls = { "is_json": False, }, "getDevicesId": { # 获取用户信息 - "req_url": "/otn/HttpZF/logdevice?algID=YD9Iw7QM4u&hashCode=d7vhohETY2f2TpCef2MPZFvngSXyZU71bSRYvkHTkbc&FMQw=0&q4f3=zh-CN&VySQ=FGFC5l5w_W3LWESYu2oI4qV2jIzzka61&VPIf=1&custID=133&VEek=unknown&dzuS=0&yD16=0&EOQP=c227b88b01f5c513710d4b9f16a5ce52&lEnu=3232235624&jp76=52d67b2a5aa5e031084733d5006cc664&hAqN=MacIntel&platform=WEB&ks0Q=d22ca0b81584fbea62237b14bd04c866&TeRS=831x1440&tOHY=24xx900x1440&Fvje=i1l1o1s1&q5aJ=-8&wNLf=99115dfb07133750ba677d055874de87&0aew=Mozilla/5.0%20(Macintosh;%20Intel%20Mac%20OS%20X%2010_13_4)%20AppleWebKit/537.36%20(KHTML,%20like%20Gecko)%20Chrome/76.0.3809.132%20Safari/537.36&E3gR=ab86d46d16b9293beca4799ff15c5db1×tamp={0}", + "req_url": "/otn/HttpZF/logdevice", "req_type": "get", "Referer": "https://kyfw.12306.cn/otn/passport?redirect=/otn/", "Host": "kyfw.12306.cn", diff --git a/init/login.py b/init/login.py index bcdd7a0..a01b877 100755 --- a/init/login.py +++ b/init/login.py @@ -1,9 +1,13 @@ # -*- coding=utf-8 -*- import copy +import json +import random +import re import time from collections import OrderedDict from time import sleep import TickerConfig +from config.urlConf import urls from inter.GetPassCodeNewOrderAndLogin import getPassCodeNewOrderAndLogin1 from inter.GetRandCode import getRandCode from inter.LoginAysnSuggest import loginAysnSuggest @@ -119,6 +123,7 @@ class GoLogin: :return: """ user, passwd = TickerConfig.USER, TickerConfig.PWD + self.request_device_id() if not user or not passwd: raise UserPasswordException(u"温馨提示: 用户名或者密码为空,请仔细检查") login_num = 0 @@ -142,3 +147,137 @@ class GoLogin: loginAysnSuggest(self.session, username=user, password=passwd) login_num += 1 break + + def request_device_id(self): + """ + 获取加密后的浏览器特征 ID + :return: + """ + params = {"algID": self.request_alg_id(), "timestamp": int(time.time() * 1000)} + params = dict(params, **self._get_hash_code_params()) + response = self.session.httpClint.send(urls.get("getDevicesId"), params=params) + if response.find('callbackFunction') >= 0: + result = response[18:-2] + try: + result = json.loads(result) + self.session.httpClint.set_cookies({ + 'RAIL_EXPIRATION': result.get('exp'), + 'RAIL_DEVICEID': result.get('dfp'), + }) + except: + return False + + def request_alg_id(self): + response = self.session.httpClint.send(urls.get("GetJS")) + result = re.search(r'algID\\x3d(.*?)\\x26', response) + try: + return result.group(1) + except (IndexError, AttributeError) as e: + pass + return "" + + def _get_hash_code_params(self): + from collections import OrderedDict + data = { + 'adblock': '0', + 'browserLanguage': 'en-US', + 'cookieEnabled': '1', + 'custID': '133', + 'doNotTrack': 'unknown', + 'flashVersion': '0', + 'javaEnabled': '0', + 'jsFonts': 'c227b88b01f5c513710d4b9f16a5ce52', + 'localCode': '3232236206', + 'mimeTypes': '52d67b2a5aa5e031084733d5006cc664', + 'os': 'MacIntel', + 'platform': 'WEB', + 'plugins': 'd22ca0b81584fbea62237b14bd04c866', + 'scrAvailSize': str(random.randint(500, 1000)) + 'x1920', + 'srcScreenSize': '24xx1080x1920', + 'storeDb': 'i1l1o1s1', + 'timeZone': '-8', + 'touchSupport': '99115dfb07133750ba677d055874de87', + 'userAgent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.' + str( + random.randint( + 5000, 7000)) + '.0 Safari/537.36', + 'webSmartID': 'f4e3b7b14cc647e30a6267028ad54c56', + } + data_trans = { + 'browserVersion': 'd435', + 'touchSupport': 'wNLf', + 'systemLanguage': 'e6OK', + 'scrWidth': 'ssI5', + 'openDatabase': 'V8vl', + 'scrAvailSize': 'TeRS', + 'hasLiedResolution': '3neK', + 'hasLiedOs': 'ci5c', + 'timeZone': 'q5aJ', + 'userAgent': '0aew', + 'userLanguage': 'hLzX', + 'jsFonts': 'EOQP', + 'scrAvailHeight': '88tV', + 'browserName': '-UVA', + 'cookieCode': 'VySQ', + 'online': '9vyE', + 'scrAvailWidth': 'E-lJ', + 'flashVersion': 'dzuS', + 'scrDeviceXDPI': '3jCe', + 'srcScreenSize': 'tOHY', + 'storeDb': 'Fvje', + 'doNotTrack': 'VEek', + 'mimeTypes': 'jp76', + 'sessionStorage': 'HVia', + 'cookieEnabled': 'VPIf', + 'os': 'hAqN', + 'hasLiedLanguages': 'j5po', + 'hasLiedBrowser': '2xC5', + 'webSmartID': 'E3gR', + 'appcodeName': 'qT7b', + 'javaEnabled': 'yD16', + 'plugins': 'ks0Q', + 'appMinorVersion': 'qBVW', + 'cpuClass': 'Md7A', + 'indexedDb': '3sw-', + 'adblock': 'FMQw', + 'localCode': 'lEnu', + 'browserLanguage': 'q4f3', + 'scrHeight': '5Jwy', + 'localStorage': 'XM7l', + 'historyList': 'kU5z', + 'scrColorDepth': "qmyu" + } + data = OrderedDict(data) + d = '' + params = {} + for key, item in data.items(): + d += key + item + key = data_trans[key] if key in data_trans else key + params[key] = item + d_len = len(d) + d_f = int(d_len / 3) if d_len % 3 == 0 else int(d_len / 3) + 1 + if d_len >= 3: + d = d[d_f:2 * d_f] + d[2 * d_f:d_len] + d[0: d_f] + d_len = len(d) + d_f = int(d_len / 3) if d_len % 3 == 0 else int(d_len / 3) + 1 + if d_len >= 3: + d = d[2 * d_f:d_len] + d[0: d_f] + d[1 * d_f: 2 * d_f] + + d = self._encode_data_str_v2(d) + d = self._encode_data_str_v2(d) + d = self._encode_data_str_v2(d) + data_str = self._encode_string(d) + params['hashCode'] = data_str + return params + + def _encode_data_str_v2(self, d): + b = len(d) + if b % 2 == 0: + return d[b // 2: b] + d[0:b // 2] + else: + return d[b // 2 + 1:b] + d[b // 2] + d[0:b // 2] + + def _encode_string(self, str): + import hashlib + import base64 + result = base64.b64encode(hashlib.sha256(str.encode()).digest()).decode() + return result.replace('+', '-').replace('/', '_').replace('=', '') \ No newline at end of file diff --git a/init/select_ticket_info.py b/init/select_ticket_info.py index 8462a95..e4bd378 100755 --- a/init/select_ticket_info.py +++ b/init/select_ticket_info.py @@ -46,6 +46,7 @@ class select: self.passengerTicketStrByAfterLate = "" self.oldPassengerStr = "" self.set_type = "" + self.flag = True @staticmethod def get_ticket_info(): @@ -144,7 +145,7 @@ class select: self.cdn_certification() l = liftTicketInit(self) l.reqLiftTicketInit() - getDrvicesID(self) + # getDrvicesID(self) self.call_login() check_user = checkUser(self) t = threading.Thread(target=check_user.sendCheckUser) @@ -238,7 +239,7 @@ class select: c.sendChechFace() else: random_time = round(random.uniform(sleep_time_s, sleep_time_t), 2) - nateMsg = ' 候补无资格' if TickerConfig.ORDER_TYPE == 2 else "" + nateMsg = ' 无候补机会' if TickerConfig.ORDER_TYPE == 2 else "" print(f"正在第{num}次查询 随机停留时长:{random_time} 乘车日期: {','.join(TickerConfig.STATION_DATES)} 车次:{'.'.join(TickerConfig.STATION_TRAINS)} 下单无票{nateMsg} 耗时:{(datetime.datetime.now() - now).microseconds / 1000}ms") time.sleep(random_time) except PassengerUserException as e: diff --git a/inter/Query.py b/inter/Query.py index fc4c3d0..287e77a 100644 --- a/inter/Query.py +++ b/inter/Query.py @@ -49,6 +49,18 @@ class query: def check_is_need_train(self, ticket_info): return ticket_info[3] in self.station_trains + # def sendQueryFirst(self): + # """ + # 首次请求打印接口车次信息 + # :return: + # """ + # for station_date in self.station_dates: + # select_url = copy.copy(self.urls["select_url"]) + # select_url["req_url"] = select_url["req_url"].format(station_date, self.from_station, self.to_station, + # self.session.queryUrl) + # station_ticket = self.httpClint.send(select_url) + # values = station_ticket.get("data", "") + def sendQuery(self): """ 查询 @@ -62,10 +74,10 @@ class query: select_url["req_url"] = select_url["req_url"].format(station_date, self.from_station, self.to_station, self.session.queryUrl) station_ticket = self.httpClint.send(select_url) - if station_ticket.get("c_url", ""): - print(u"设置当前查询url为: {}".format(station_ticket.get("c_url", ""))) - self.session.queryUrl = station_ticket.get("c_url", "") # 重设查询接口 - continue + # if station_ticket.get("c_url", ""): + # print(u"设置当前查询url为: {}".format(station_ticket.get("c_url", ""))) + # self.session.queryUrl = station_ticket.get("c_url", "") # 重设查询接口 + # continue value = station_ticket.get("data", "") if not value: print(u'{0}-{1} 车次坐席查询为空,查询url: https://kyfw.12306.cn{2}, 可以手动查询是否有票'.format( @@ -77,20 +89,8 @@ class query: if result: for i in value['result']: ticket_info = i.split('|') - # if TickerConfig.TICKET_TYPE is 2 and self.check_is_need_train(ticket_info): - # # 如果最后一位为1,则是可以候补的,不知道这些正确嘛? - # if ticket_info[-2] == "1": - # nate = list(ticket_info[-1]) - # for set_type in TickerConfig.SET_TYPE: - # if TickerConfig.PASSENGER_TICKER_STR(set_type) not in nate: - # continue - # print("当前订单可以候补,尝试提交候补订单") - # return { - # "secretStr": ticket_info[0], - # "seat": TickerConfig.SET_TYPE, - # "status": True, - # } - # elif TickerConfig.TICKET_TYPE is 1: + if self.session.flag: + print(f"车次:{ticket_info[3]} 出发站:{self.from_station_h} 到达站:{self.to_station_h} 历时:{ticket_info[10]} 商务/特等座:{ticket_info[32]} 一等座:{ticket_info[31]} 二等座:{ticket_info[30]} 动卧:{ticket_info[33]} 硬卧:{ticket_info[28]} 软座:{ticket_info[23]} 硬座:{ticket_info[29]} 无座:{ticket_info[26]} {ticket_info[1]}") if ticket_info[1] == "预订" and self.check_is_need_train(ticket_info): # 筛选未在开始时间内的车次 for j in self._station_seat: is_ticket_pass = ticket_info[j] @@ -166,6 +166,7 @@ class query: } else: print(u"车次配置信息有误,或者返回数据异常,请检查 {}".format(station_ticket)) + self.session.flag = False return {"code": ticket.FAIL_CODE, "status": False, "cdn": self.httpClint.cdn, } diff --git a/myUrllib/httpUtils.py b/myUrllib/httpUtils.py index a83d169..7db3728 100755 --- a/myUrllib/httpUtils.py +++ b/myUrllib/httpUtils.py @@ -14,7 +14,7 @@ def _set_header_default(): # header_dict["Accept"] = "application/json, text/plain, */*" header_dict["Accept-Encoding"] = "gzip, deflate" header_dict[ - "User-Agent"] = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) 12306-electron/1.0.1 Chrome/59.0.3071.115 Electron/1.8.4 Safari/537.36" + "User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" header_dict["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8" header_dict["Origin"] = "https://kyfw.12306.cn" header_dict["Connection"] = "keep-alive" @@ -47,9 +47,8 @@ class HTTPClient(object): :param kwargs: :return: """ - for kwarg in kwargs: - for k, v in kwarg.items(): - self._s.cookies.set(k, v) + for k, v in kwargs.items(): + self._s.cookies.set(k, v) def get_cookies(self): """ diff --git a/verify/ruokuai.py b/verify/ruokuai.py deleted file mode 100644 index 5864735..0000000 --- a/verify/ruokuai.py +++ /dev/null @@ -1,78 +0,0 @@ -# coding:utf-8 -import base64 - -import requests -from hashlib import md5 - - -class RClient(object): - - def __init__(self, username, password): - self.username = username - try: - self.password = md5(password).hexdigest() - except TypeError: - self.password = md5(password.encode('utf-8')).hexdigest() - self.soft_id = '96061' - self.soft_key = '6facb9da7bb645ad9c4a229464b2cf89' - self.base_params = { - 'username': self.username, - 'password': self.password, - 'softid': self.soft_id, - 'softkey': self.soft_key, - } - self.headers = { - 'Connection': 'Keep-Alive', - 'Expect': '100-continue', - 'User-Agent': 'ben', - } - - def rk_create(self, im, im_type, timeout=60): - """ - im: 图片字节 - im_type: 题目类型 - """ - params = { - 'typeid': im_type, - 'timeout': timeout, - } - params.update(self.base_params) - files = {'image': ('a.jpg', im)} - r = requests.post('http://api.ruokuai.com/create.json', data=params, files=files, headers=self.headers) - print(r) - return r.json() - - def rk_create_base64(self, im, im_type, timeout=60): - """ - base64验证码识别 - im: 图片字节 - im_type: 题目类型 - :return: - """ - params = { - 'typeid': im_type, - 'timeout': timeout, - 'image': im, - } - params.update(self.base_params) - r = requests.post('http://api.ruokuai.com/create.json', data=params, headers=self.headers) - print(r) - return r.json() - - def rk_report_error(self, im_id): - """ - im_id:报错题目的ID - """ - params = { - 'id': im_id, - } - params.update(self.base_params) - r = requests.post('http://api.ruokuai.com/reporterror.json', data=params, headers=self.headers) - return r.json() - - -if __name__ == '__main__': - rc = RClient('931128603', '',) - im = open('tkcode', 'rb').read() - print(rc.rk_create(im, 6113)) -