def __init__(self): self.session = Request() self.request_device_id() self.cluster = Cluster() self.update_query_interval() self.update_query_jobs() self.get_query_api_type()
def init_data(self, info): self.session = Request() self.session.add_response_hook(self.response_login_check) self.key = str(info.get('key')) self.user_name = info.get('user_name') self.password = info.get('password') self.type = info.get('type')
def check_item_available(self, item, try_num=0): session = Request() response = session.get(API_CHECK_CDN_AVAILABLE.format(item), headers={'Host': HOST_URL_OF_12306}, timeout=self.check_time_out, verify=False) if response.status_code == 200: if not self.is_recheck: self.available_items.append(item) else: self.recheck_available_items.append(item) if not self.is_ready: self.check_is_ready() elif try_num < self.retry_num: # 重试 stay_second(self.safe_stay_time) return self.check_item_available(item, try_num + 1) else: if not self.is_recheck: self.unavailable_items.append(item) else: self.recheck_unavailable_items.append(item) if not self.is_recheck and ( not self.last_check_at or (time_now() - self.last_check_at).seconds > self.save_second): self.save_available_items() stay_second(self.safe_stay_time)
def __init__(self, info, user): self.session = Request() self.heartbeat = user.heartbeat self.key = info.get('key') self.user_name = info.get('user_name') self.password = info.get('password') self.user = user
class OCR: """ 图片识别 """ session = None def __init__(self): self.session = Request() @classmethod def get_img_position(cls, img): """ 获取图像坐标 :param img_path: :return: """ self = cls() if Config().AUTO_CODE_PLATFORM == 'free': return self.get_image_by_free_site(img) return self.get_img_position_by_ruokuai(img) def get_img_position_by_ruokuai(self, img): ruokuai_account = Config().AUTO_CODE_ACCOUNT soft_id = '119671' soft_key = '6839cbaca1f942f58d2760baba5ed987' rc = RKClient(ruokuai_account.get('user'), ruokuai_account.get('pwd'), soft_id, soft_key) result = rc.rk_create(img, 6113) if "Result" in result: return self.get_image_position_by_offset(list(result['Result'])) CommonLog.print_auto_code_fail( result.get("Error", CommonLog.MESSAGE_RESPONSE_EMPTY_ERROR)) return None def get_image_position_by_offset(self, offsets): positions = [] width = 75 height = 75 for offset in offsets: random_x = random.randint(-5, 5) random_y = random.randint(-5, 5) offset = int(offset) x = width * ((offset - 1) % 4 + 1) - width / 2 + random_x y = height * math.ceil(offset / 4) - height / 2 + random_y positions.append(int(x)) positions.append(int(y)) return positions def get_image_by_free_site(self, img): data = {'img': img} response = self.session.post(API_FREE_CODE_QCR_API, data=data) result = response.json() if result.get('msg') == 'success': pos = result.get('result') return self.get_image_position_by_offset(pos) CommonLog.print_auto_code_fail( CommonLog.MESSAGE_GET_RESPONSE_FROM_FREE_AUTO_CODE) return None
class Notification(): """ 通知类 """ session = None def __init__(self): self.session = Request() @classmethod def voice_code(cls, phone, name='', content=''): self = cls() self.send_voice_code_of_yiyuan(phone, name=name, content=content) def send_voice_code_of_yiyuan(self, phone, name='', content=''): """ 发送语音验证码 购买地址 https://market.aliyun.com/products/57126001/cmapi019902.html?spm=5176.2020520132.101.5.37857218O6iJ3n :return: """ appcode = config.NOTIFICATION_API_APP_CODE if not appcode: CommonLog.add_quick_log(CommonLog.MESSAGE_EMPTY_APP_CODE).flush() return False body = { 'userName': name, 'mailNo': content } params = { 'content': body, 'mobile': phone, 'sex': 2, 'tNum': 'T170701001056' } response = self.session.request(url=API_NOTIFICATION_BY_VOICE_CODE + urllib.parse.urlencode(params), method='GET', headers={ 'Authorization': 'APPCODE {}'.format(appcode) }) response_message = '-' result = {} try: result = response.json() response_message = result['showapi_res_body']['remark'] except: pass if response.status_code == 401 or response.status_code == 403: return CommonLog.add_quick_log(CommonLog.MESSAGE_VOICE_API_FORBID).flush() if response.status_code == 200 and 'showapi_res_body' in result and result['showapi_res_body'].get('flag'): CommonLog.add_quick_log(CommonLog.MESSAGE_VOICE_API_SEND_SUCCESS.format(response_message)).flush() return True else: return CommonLog.add_quick_log(CommonLog.MESSAGE_VOICE_API_SEND_FAIL.format(response_message)).flush()
class UserJob: heartbeat = 60 * 2 # 心跳保持时长 heartbeat_interval = 5 key = None user_name = '' password = '' user = None info = {} # 用户信息 last_heartbeat = None is_ready = False passengers = [] retry_time = 5 # Init page global_repeat_submit_token = None ticket_info_for_passenger_form = None order_request_dto = None def __init__(self, info, user): self.session = Request() self.heartbeat = user.heartbeat self.key = info.get('key') self.user_name = info.get('user_name') self.password = info.get('password') self.user = user def run(self): # load user if not Const.IS_TEST: self.load_user() self.start() def start(self): """ 检测心跳 :return: """ while True: app_available_check() self.check_heartbeat() if Const.IS_TEST: return sleep(self.heartbeat_interval) def check_heartbeat(self): # 心跳检测 if self.last_heartbeat and ( time_now() - self.last_heartbeat).seconds < self.heartbeat: return True if self.is_first_time() or not self.check_user_is_login(): self.handle_login() self.is_ready = True UserLog.add_quick_log( UserLog.MESSAGE_USER_HEARTBEAT_NORMAL.format( self.get_name(), self.heartbeat)).flush() self.last_heartbeat = time_now() # def init_cookies def is_first_time(self): return not path.exists(self.get_cookie_path()) def handle_login(self): UserLog.print_start_login(user=self) self.login() def login(self): """ 获取验证码结果 :return 权限校验码 """ data = { 'username': self.user_name, 'password': self.password, 'appid': 'otn' } answer = AuthCode.get_auth_code(self.session) data['answer'] = answer response = self.session.post(API_BASE_LOGIN.get('url'), data) result = response.json() if result.get('result_code') == 0: # 登录成功 """ login 获得 cookie uamtk auth/uamtk 不请求,会返回 uamtk票据内容为空 /otn/uamauthclient 能拿到用户名 """ new_tk = self.auth_uamtk() user_name = self.auth_uamauthclient(new_tk) self.update_user_info({'user_name': user_name}) self.login_did_success() elif result.get('result_code') == 2: # 账号之内错误 # 登录失败,用户名或密码为空 # 密码输入错误 UserLog.add_quick_log( UserLog.MESSAGE_LOGIN_FAIL.format( result.get('result_message'))) else: UserLog.add_quick_log( UserLog.MESSAGE_LOGIN_FAIL.format( result.get('result_message', result.get('message', '-')))) return False pass def check_user_is_login(self): response = self.session.get(API_USER_CHECK.get('url')) is_login = response.json().get('data').get('flag', False) if is_login: self.save_user() return is_login def auth_uamtk(self): response = self.session.post(API_AUTH_UAMTK.get('url'), {'appid': 'otn'}) result = response.json() if result.get('newapptk'): return result.get('newapptk') # TODO 处理获取失败情况 return False def auth_uamauthclient(self, tk): response = self.session.post(API_AUTH_UAMAUTHCLIENT.get('url'), {'tk': tk}) result = response.json() if result.get('username'): return result.get('username') # TODO 处理获取失败情况 return False def login_did_success(self): """ 用户登录成功 :return: """ self.welcome_user() self.save_user() self.get_user_info() pass def welcome_user(self): UserLog.print_welcome_user(self) pass def get_cookie_path(self): return config.USER_DATA_DIR + self.user_name + '.cookie' def update_user_info(self, info): self.info = {**self.info, **info} def get_name(self): return self.info.get('user_name') def save_user(self): with open(self.get_cookie_path(), 'wb') as f: pickle.dump(self.session.cookies, f) def did_loaded_user(self): """ 恢复用户成功 :return: """ UserLog.add_quick_log( UserLog.MESSAGE_LOADED_USER.format(self.user_name)) if self.check_user_is_login(): UserLog.add_quick_log( UserLog.MESSAGE_LOADED_USER_SUCCESS.format(self.user_name)) self.get_user_info() UserLog.print_welcome_user(self) else: UserLog.add_quick_log(UserLog.MESSAGE_LOADED_USER_BUT_EXPIRED) def get_user_info(self): response = self.session.get(API_USER_INFO.get('url')) result = response.json() user_data = result.get('data.userDTO.loginUserDTO') if user_data: self.update_user_info({ **user_data, **{ 'user_name': user_data.get('name') } }) return True return None def load_user(self): cookie_path = self.get_cookie_path() if path.exists(cookie_path): with open(self.get_cookie_path(), 'rb') as f: self.session.cookies.update(pickle.load(f)) self.did_loaded_user() return True return None def check_is_ready(self): return self.is_ready def get_user_passengers(self): if self.passengers: return self.passengers response = self.session.post(API_USER_PASSENGERS) result = response.json() if result.get('data.normal_passengers'): self.passengers = result.get('data.normal_passengers') return self.passengers else: UserLog.add_quick_log( UserLog.MESSAGE_GET_USER_PASSENGERS_FAIL.format( result.get('messages', '-'), self.retry_time)) stay_second(self.retry_time) return self.get_user_passengers() def get_passengers_by_members(self, members): """ 获取格式化后的乘客信息 :param members: :return: [{ name: '项羽', type: 1, id_card: 0000000000000000000, type_text: '成人' }] """ self.get_user_passengers() results = [] for member in members: child_check = array_dict_find_by_key_value(results, 'name', member) if child_check: new_member = child_check.copy() new_member['type'] = UserType.CHILD new_member['type_text'] = dict_find_key_by_value( UserType.dicts, int(new_member['type'])) else: passenger = array_dict_find_by_key_value( self.passengers, 'passenger_name', member) if not passenger: UserLog.add_quick_log( UserLog.MESSAGE_USER_PASSENGERS_IS_INVALID.format( self.user_name, member)).flush(exit=True) # TODO 需要优化 new_member = { 'name': passenger.get('passenger_name'), 'id_card': passenger.get('passenger_id_no'), 'id_card_type': passenger.get('passenger_id_type_code'), 'mobile': passenger.get('mobile_no'), 'type': passenger.get('passenger_type'), 'type_text': dict_find_key_by_value( UserType.dicts, int(passenger.get('passenger_type'))) } results.append(new_member) return results def request_init_dc_page(self): """ 请求下单页面 拿到 token :return: """ data = {'_json_att': ''} response = self.session.post(API_INITDC_URL, data) html = response.text token = re.search(r'var globalRepeatSubmitToken = \'(.+?)\'', html) form = re.search(r'var ticketInfoForPassengerForm *= *(\{.+\})', html) order = re.search(r'var orderRequestDTO *= *(\{.+\})', html) # 系统忙,请稍后重试 if html.find('系统忙,请稍后重试') != -1: OrderLog.add_quick_log(OrderLog.MESSAGE_REQUEST_INIT_DC_PAGE_FAIL ).flush() # 重试无用,直接跳过 return False try: self.global_repeat_submit_token = token.groups()[0] self.ticket_info_for_passenger_form = json.loads( form.groups()[0].replace("'", '"')) self.order_request_dto = json.loads(order.groups()[0].replace( "'", '"')) except: pass # TODO Error return True
def init_data(self, info): self.session = Request() self.key = str(info.get('key')) self.user_name = info.get('user_name') self.password = info.get('password')
class UserJob: # heartbeat = 60 * 2 # 心跳保持时长 is_alive = True check_interval = 5 key = None user_name = '' password = '' user = None info = {} # 用户信息 last_heartbeat = None is_ready = False user_loaded = False # 用户是否已加载成功 passengers = [] retry_time = 3 login_num = 0 # 尝试登录次数 # Init page global_repeat_submit_token = None ticket_info_for_passenger_form = None order_request_dto = None cluster = None lock_init_user_time = 3 * 60 cookie = False def __init__(self, info): self.cluster = Cluster() self.init_data(info) def init_data(self, info): self.session = Request() self.session.add_response_hook(self.response_login_check) self.key = str(info.get('key')) self.user_name = info.get('user_name') self.password = info.get('password') def update_user(self): from py12306.user.user import User self.user = User() self.load_user() def run(self): # load user self.update_user() self.start() def start(self): """ 检测心跳 :return: """ while True and self.is_alive: app_available_check() if Config().is_slave(): self.load_user_from_remote() else: if Config().is_master() and not self.cookie: self.load_user_from_remote() # 主节点加载一次 Cookie self.check_heartbeat() if Const.IS_TEST: return stay_second(self.check_interval) def check_heartbeat(self): # 心跳检测 if self.get_last_heartbeat() and ( time_int() - self.get_last_heartbeat()) < Config().USER_HEARTBEAT_INTERVAL: return True # 只有主节点才能走到这 if self.is_first_time() or not self.check_user_is_login(): if not self.handle_login(): return self.user_did_load() message = UserLog.MESSAGE_USER_HEARTBEAT_NORMAL.format( self.get_name(), Config().USER_HEARTBEAT_INTERVAL) UserLog.add_quick_log(message).flush() def get_last_heartbeat(self): if Config().is_cluster_enabled(): return int( self.cluster.session.get(Cluster.KEY_USER_LAST_HEARTBEAT, 0)) return self.last_heartbeat def set_last_heartbeat(self, time=None): time = time if time != None else time_int() if Config().is_cluster_enabled(): self.cluster.session.set(Cluster.KEY_USER_LAST_HEARTBEAT, time) self.last_heartbeat = time # def init_cookies def is_first_time(self): if Config().is_cluster_enabled(): return not self.cluster.get_user_cookie(self.key) return not path.exists(self.get_cookie_path()) def handle_login(self, expire=False): if expire: UserLog.print_user_expired() self.is_ready = False UserLog.print_start_login(user=self) return self.login() def login(self): """ 获取验证码结果 :return 权限校验码 """ data = { 'username': self.user_name, 'password': self.password, 'appid': 'otn' } self.request_device_id() answer = AuthCode.get_auth_code(self.session) data['answer'] = answer response = self.session.post(API_BASE_LOGIN.get('url'), data) result = response.json() if result.get('result_code') == 0: # 登录成功 """ login 获得 cookie uamtk auth/uamtk 不请求,会返回 uamtk票据内容为空 /otn/uamauthclient 能拿到用户名 """ new_tk = self.auth_uamtk() user_name = self.auth_uamauthclient(new_tk) self.update_user_info({'user_name': user_name}) self.login_did_success() return True elif result.get('result_code') == 2: # 账号之内错误 # 登录失败,用户名或密码为空 # 密码输入错误 UserLog.add_quick_log( UserLog.MESSAGE_LOGIN_FAIL.format( result.get('result_message'))).flush() else: UserLog.add_quick_log( UserLog.MESSAGE_LOGIN_FAIL.format( result.get( 'result_message', result.get( 'message', CommonLog.MESSAGE_RESPONSE_EMPTY_ERROR)))).flush() return False def check_user_is_login(self): response = self.session.get(API_USER_LOGIN_CHECK) is_login = response.json().get('data.is_login', False) == 'Y' if is_login: self.save_user() self.set_last_heartbeat() return self.get_user_info( ) # 检测应该是不会维持状态,这里再请求下个人中心看有没有用,01-10 看来应该是没用 01-22 有时拿到的状态 是已失效的再加上试试 return is_login def auth_uamtk(self): response = self.session.post(API_AUTH_UAMTK.get('url'), {'appid': 'otn'}) result = response.json() if result.get('newapptk'): return result.get('newapptk') # TODO 处理获取失败情况 return False def auth_uamauthclient(self, tk): response = self.session.post(API_AUTH_UAMAUTHCLIENT.get('url'), {'tk': tk}) result = response.json() if result.get('username'): return result.get('username') # TODO 处理获取失败情况 return False 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.get(API_GET_BROWSER_DEVICE_ID, params=params) if response.text.find('callbackFunction') >= 0: result = response.text[18:-2] try: result = json.loads(result) self.session.cookies.update({ 'RAIL_EXPIRATION': result.get('exp'), 'RAIL_DEVICEID': result.get('dfp'), # 'RAIL_EXPIRATION': '1554474881858', # 'RAIL_DEVICEID': 'AuT-Gn6_zBqrgut3m5pj-OtRGGHDAKXfCDKI3SlDT98JBD-XzxPH08tjaplcAW5aKb8nyX90r92psp5QpwRIGTn6XeIwiQxvuwEnqseza6mPSAu_gmrGCLVpFvCbDUky4EB-UTjDH-ozAHx1oaz5KkvGgakW0Jou', }) except: return False def request_alg_id(self): response = self.session.get("https://kyfw.12306.cn/otn/HttpZF/GetJS") result = re.search(r'algID\\x3d(.*?)\\x26', response.text) 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) data_str = '' params = {} for key, item in data.items(): data_str += key + item key = data_trans[key] if key in data_trans else key params[key] = item data_str = self._encode_data_str(data_str) data_str_len = len(data_str) data_str_len_div = int(data_str_len / 2) if data_str_len % 2 == 0: data_str = data_str[data_str_len_div:data_str_len] + data_str[ 0:data_str_len_div] else: data_str = data_str[data_str_len_div + 1:data_str_len] + \ data_str[data_str_len_div] + data_str[0:data_str_len_div] data_str = self._encode_data_str(data_str) data_str_tmp = "" for e in range(0, len(data_str)): data_str_code = ord(data_str[e]) data_str_tmp += chr(0) if data_str_code == 127 else chr( data_str_code + 1) data_str = self._encode_string(data_str_tmp[::-1]) params['hashCode'] = data_str return params def _encode_data_str(self, data_str): data_str_len = len(data_str) data_str_len_tmp = int( data_str_len / 3) if data_str_len % 3 == 0 else int(data_str_len / 3) + 1 if data_str_len >= 3: data_str_e = data_str[0:data_str_len_tmp] data_str_f = data_str[data_str_len_tmp:2 * data_str_len_tmp] return data_str_f + data_str[ 2 * data_str_len_tmp:data_str_len] + data_str_e return data_str def _encode_string(self, str): import hashlib import base64 result = base64.b64encode(hashlib.sha256( str.encode()).digest()).decode() return result.replace('+', '-').replace('/', '_').replace('=', '') def login_did_success(self): """ 用户登录成功 :return: """ self.login_num += 1 self.welcome_user() self.save_user() self.get_user_info() self.set_last_heartbeat() self.is_ready = True def welcome_user(self): UserLog.print_welcome_user(self) pass def get_cookie_path(self): return Config().USER_DATA_DIR + self.user_name + '.cookie' def update_user_info(self, info): self.info = {**self.info, **info} def get_name(self): return self.info.get('user_name', '') def save_user(self): if Config().is_master(): self.cluster.set_user_cookie(self.key, self.session.cookies) self.cluster.set_user_info(self.key, self.info) with open(self.get_cookie_path(), 'wb') as f: pickle.dump(self.session.cookies, f) def did_loaded_user(self): """ 恢复用户成功 :return: """ UserLog.add_quick_log( UserLog.MESSAGE_LOADED_USER.format(self.user_name)).flush() if self.check_user_is_login() and self.get_user_info(): UserLog.add_quick_log( UserLog.MESSAGE_LOADED_USER_SUCCESS.format( self.user_name)).flush() UserLog.print_welcome_user(self) self.user_did_load() else: UserLog.add_quick_log( UserLog.MESSAGE_LOADED_USER_BUT_EXPIRED).flush() self.set_last_heartbeat(0) def user_did_load(self): """ 用户已经加载成功 :return: """ self.is_ready = True if self.user_loaded: return self.user_loaded = True Event().user_loaded({'key': self.key}) # 发布通知 def get_user_info(self): response = self.session.get(API_USER_INFO.get('url')) result = response.json() user_data = result.get('data.userDTO.loginUserDTO') # 子节点访问会导致主节点登录失效 TODO 可快考虑实时同步 cookie if user_data: self.update_user_info({ **user_data, **{ 'user_name': user_data.get('name') } }) self.save_user() return True return False def load_user(self): if Config().is_cluster_enabled(): return cookie_path = self.get_cookie_path() if path.exists(cookie_path): with open(self.get_cookie_path(), 'rb') as f: cookie = pickle.load(f) self.cookie = True self.session.cookies.update(cookie) self.did_loaded_user() return True return None def load_user_from_remote(self): cookie = self.cluster.get_user_cookie(self.key) info = self.cluster.get_user_info(self.key) if Config().is_slave() and (not cookie or not info): while True: # 子节点只能取 UserLog.add_quick_log( UserLog.MESSAGE_USER_COOKIE_NOT_FOUND_FROM_REMOTE.format( self.user_name)).flush() stay_second(self.retry_time) return self.load_user_from_remote() if info: self.info = info if cookie: self.session.cookies.update(cookie) if not self.cookie: # 第一次加载 self.cookie = True if not Config().is_slave(): self.did_loaded_user() else: self.is_ready = True # 设置子节点用户 已准备好 UserLog.print_welcome_user(self) return True return False def check_is_ready(self): return self.is_ready def wait_for_ready(self): if self.is_ready: return self UserLog.add_quick_log( UserLog.MESSAGE_WAIT_USER_INIT_COMPLETE.format( self.retry_time)).flush() stay_second(self.retry_time) return self.wait_for_ready() def destroy(self): """ 退出用户 :return: """ UserLog.add_quick_log( UserLog.MESSAGE_USER_BEING_DESTROY.format(self.user_name)).flush() self.is_alive = False def response_login_check(self, response, **kwargs): if Config().is_master() and response.json().get( 'data.noLogin') == 'true': # relogin self.handle_login(expire=True) def get_user_passengers(self): if self.passengers: return self.passengers response = self.session.post(API_USER_PASSENGERS) result = response.json() if result.get('data.normal_passengers'): self.passengers = result.get('data.normal_passengers') # 将乘客写入到文件 with open(Config().USER_PASSENGERS_FILE % self.user_name, 'w', encoding='utf-8') as f: f.write( json.dumps(self.passengers, indent=4, ensure_ascii=False)) return self.passengers else: UserLog.add_quick_log( UserLog.MESSAGE_GET_USER_PASSENGERS_FAIL.format( result.get('messages', CommonLog.MESSAGE_RESPONSE_EMPTY_ERROR), self.retry_time)).flush() if Config().is_slave(): self.load_user_from_remote() # 加载最新 cookie stay_second(self.retry_time) return self.get_user_passengers() def get_passengers_by_members(self, members): """ 获取格式化后的乘客信息 :param members: :return: [{ name: '项羽', type: 1, id_card: 0000000000000000000, type_text: '成人' }] """ self.get_user_passengers() results = [] for member in members: is_member_code = is_number(member) if not is_member_code: child_check = array_dict_find_by_key_value( results, 'name', member) if not is_member_code and child_check: new_member = child_check.copy() new_member['type'] = UserType.CHILD new_member['type_text'] = dict_find_key_by_value( UserType.dicts, int(new_member['type'])) else: if is_member_code: passenger = array_dict_find_by_key_value( self.passengers, 'code', member) else: passenger = array_dict_find_by_key_value( self.passengers, 'passenger_name', member) if not passenger: UserLog.add_quick_log( UserLog.MESSAGE_USER_PASSENGERS_IS_INVALID.format( self.user_name, member)).flush() return False new_member = { 'name': passenger.get('passenger_name'), 'id_card': passenger.get('passenger_id_no'), 'id_card_type': passenger.get('passenger_id_type_code'), 'mobile': passenger.get('mobile_no'), 'type': passenger.get('passenger_type'), 'type_text': dict_find_key_by_value( UserType.dicts, int(passenger.get('passenger_type'))) } results.append(new_member) return results def request_init_dc_page(self): """ 请求下单页面 拿到 token :return: """ data = {'_json_att': ''} response = self.session.post(API_INITDC_URL, data) html = response.text token = re.search(r'var globalRepeatSubmitToken = \'(.+?)\'', html) form = re.search(r'var ticketInfoForPassengerForm *= *(\{.+\})', html) order = re.search(r'var orderRequestDTO *= *(\{.+\})', html) # 系统忙,请稍后重试 if html.find('系统忙,请稍后重试') != -1: OrderLog.add_quick_log(OrderLog.MESSAGE_REQUEST_INIT_DC_PAGE_FAIL ).flush() # 重试无用,直接跳过 return False try: self.global_repeat_submit_token = token.groups()[0] self.ticket_info_for_passenger_form = json.loads( form.groups()[0].replace("'", '"')) self.order_request_dto = json.loads(order.groups()[0].replace( "'", '"')) except: pass # TODO Error return True
class Notification(): """ 通知类 """ session = None def __init__(self): self.session = Request() @classmethod def voice_code(cls, phone, name='', content=''): self = cls() self.send_voice_code_of_yiyuan(phone, name=name, content=content) @classmethod def dingtalk_webhook(cls, content=''): self = cls() self.send_dingtalk_by_webbook(content=content) @classmethod def send_email(cls, to, title='', content=''): self = cls() self.send_email_by_smtp(to, title, content) @classmethod def send_to_telegram(cls, content=''): self = cls() self.send_to_telegram_bot(content=content) def send_voice_code_of_yiyuan(self, phone, name='', content=''): """ 发送语音验证码 购买地址 https://market.aliyun.com/products/57126001/cmapi019902.html?spm=5176.2020520132.101.5.37857218O6iJ3n :return: """ appcode = Config().NOTIFICATION_API_APP_CODE if not appcode: CommonLog.add_quick_log(CommonLog.MESSAGE_EMPTY_APP_CODE).flush() return False body = {'userName': name, 'mailNo': content} params = { 'content': body, 'mobile': phone, 'sex': 2, 'tNum': 'T170701001056' } response = self.session.request( url=API_NOTIFICATION_BY_VOICE_CODE + urllib.parse.urlencode(params), method='GET', headers={'Authorization': 'APPCODE {}'.format(appcode)}) result = response.json() response_message = result.get('showapi_res_body.remark') if response.status_code in [400, 401, 403]: return CommonLog.add_quick_log( CommonLog.MESSAGE_VOICE_API_FORBID).flush() if response.status_code == 200 and result.get('showapi_res_body.flag'): CommonLog.add_quick_log( CommonLog.MESSAGE_VOICE_API_SEND_SUCCESS.format( response_message)).flush() return True else: return CommonLog.add_quick_log( CommonLog.MESSAGE_VOICE_API_SEND_FAIL.format( response_message)).flush() def send_email_by_smtp(self, to, title, content): import smtplib from email.message import EmailMessage to = to if isinstance(to, list) else [to] message = EmailMessage() message['Subject'] = title message['From'] = Config().EMAIL_SENDER message['To'] = to message.set_content(content) try: server = smtplib.SMTP(Config().EMAIL_SERVER_HOST) server.login(Config().EMAIL_SERVER_USER, Config().EMAIL_SERVER_PASSWORD) server.send_message(message) server.quit() CommonLog.add_quick_log( CommonLog.MESSAGE_SEND_EMAIL_SUCCESS).flush() except Exception as e: CommonLog.add_quick_log( CommonLog.MESSAGE_SEND_EMAIL_FAIL.format(e)).flush() def send_dingtalk_by_webbook(self, content): from dingtalkchatbot.chatbot import DingtalkChatbot webhook = Config().DINGTALK_WEBHOOK dingtalk = DingtalkChatbot(webhook) dingtalk.send_text(msg=content, is_at_all=True) pass def send_to_telegram_bot(self, content): bot_api_url = Config().TELEGRAM_BOT_API_URL if not bot_api_url: return False data = {'text': content} response = self.session.request(url=bot_api_url, method='POST', data=data) result = response.json().get('result') response_status = result.get('statusCode') if response_status == 200: CommonLog.add_quick_log( CommonLog.MESSAGE_SEND_TELEGRAM_SUCCESS).flush() else: response_error_message = result.get('description') CommonLog.add_quick_log( CommonLog.MESSAGE_SEND_TELEGRAM_FAIL.format( response_error_message)).flush()
class OCR: """ 图片识别 """ session = None def __init__(self): self.session = Request() @classmethod def get_img_position(cls, img): """ 获取图像坐标 :param img_path: :return: """ self = cls() if Config().AUTO_CODE_PLATFORM == 'free': return self.get_image_by_free_site(img) return self.get_img_position_by_ruokuai(img) def get_img_position_by_ruokuai(self, img): ruokuai_account = Config().AUTO_CODE_ACCOUNT soft_id = '119671' soft_key = '6839cbaca1f942f58d2760baba5ed987' rc = RKClient(ruokuai_account.get('user'), ruokuai_account.get('pwd'), soft_id, soft_key) result = rc.rk_create(img, 6113) if "Result" in result: return self.get_image_position_by_offset(list(result['Result'])) CommonLog.print_auto_code_fail( result.get("Error", CommonLog.MESSAGE_RESPONSE_EMPTY_ERROR)) return None def get_image_position_by_offset(self, offsets): positions = [] width = 75 height = 75 for offset in offsets: random_x = random.randint(-5, 5) random_y = random.randint(-5, 5) offset = int(offset) x = width * ((offset - 1) % 4 + 1) - width / 2 + random_x y = height * math.ceil(offset / 4) - height / 2 + random_y positions.append(int(x)) positions.append(int(y)) return positions def get_image_by_free_site(self, img): """ 本地机器学习打码 :param img: :return: """ # from py12306.helpers.ocr.ml_predict import get_coordinate # import base64 # # result = get_coordinate(base64.b64decode(img)) # result = self.get_image_position_by_offset(result) # # CommonLog.print_auth_code_info("验证码识别的结果为:" + result) # # if result: # return result """ 线上打码,可靠性未知 """ data = {'img': img} response = self.session.post(API_FREE_CODE_QCR_API, data=data) result = response.json() if result.get('msg') == 'success': pos = result.get('result') return self.get_image_position_by_offset(pos) else: from py12306.helpers.ocr.ml_predict import get_coordinate import base64 result = get_coordinate(base64.b64decode(img)) result = self.get_image_position_by_offset(result) # CommonLog.print_auth_code_info("验证码识别的结果为:" + result) if result: return result return None
def __init__(self): self.session = Request() self.cluster = Cluster() self.update_query_interval() self.update_query_jobs()
class Notification(): """ 通知类 """ session = None def __init__(self): self.session = Request() @classmethod def voice_code(cls, phone, name='', content=''): self = cls() if Config().NOTIFICATION_VOICE_CODE_TYPE == 'dingxin': self.send_voice_code_of_dingxin(phone, name=name, info=content) else: self.send_voice_code_of_yiyuan(phone, name=name, content=content) @classmethod def dingtalk_webhook(cls, content=''): self = cls() self.send_dingtalk_by_webbook(content=content) @classmethod def send_email(cls, to, title='', content=''): self = cls() self.send_email_by_smtp(to, title, content) @classmethod def send_to_telegram(cls, content=''): self = cls() self.send_to_telegram_bot(content=content) @classmethod def server_chan(cls, skey='', title='', content=''): self = cls() self.send_serverchan(skey=skey, title=title, content=content) @classmethod def push_bear(cls, skey='', title='', content=''): self = cls() self.send_pushbear(skey=skey, title=title, content=content) @classmethod def push_bark(cls, content=''): self = cls() self.push_to_bark(content) def send_voice_code_of_yiyuan(self, phone, name='', content=''): """ 发送语音验证码 购买地址 https://market.aliyun.com/products/57126001/cmapi019902.html?spm=5176.2020520132.101.5.37857218O6iJ3n :return: """ appcode = Config().NOTIFICATION_API_APP_CODE if not appcode: CommonLog.add_quick_log(CommonLog.MESSAGE_EMPTY_APP_CODE).flush() return False body = { 'userName': name, 'mailNo': content } params = { 'content': body, 'mobile': phone, 'sex': 2, 'tNum': 'T170701001056' } response = self.session.request(url=API_NOTIFICATION_BY_VOICE_CODE + urllib.parse.urlencode(params), method='GET', headers={'Authorization': 'APPCODE {}'.format(appcode)}) result = response.json() response_message = result.get('showapi_res_body.remark') if response.status_code in [400, 401, 403]: return CommonLog.add_quick_log(CommonLog.MESSAGE_VOICE_API_FORBID).flush() if response.status_code == 200 and result.get('showapi_res_body.flag'): CommonLog.add_quick_log(CommonLog.MESSAGE_VOICE_API_SEND_SUCCESS.format(response_message)).flush() return True else: return CommonLog.add_quick_log(CommonLog.MESSAGE_VOICE_API_SEND_FAIL.format(response_message)).flush() def send_voice_code_of_dingxin(self, phone, name='', info={}): """ 发送语音验证码 ( 鼎信 ) 购买地址 https://market.aliyun.com/products/56928004/cmapi026600.html?spm=5176.2020520132.101.2.51547218rkAXxy :return: """ appcode = Config().NOTIFICATION_API_APP_CODE if not appcode: CommonLog.add_quick_log(CommonLog.MESSAGE_EMPTY_APP_CODE).flush() return False data = { 'tpl_id': 'TP1901174', 'phone': phone, 'param': 'name:{name},job_name:{left_station}到{arrive_station}{set_type},orderno:{orderno}'.format( name=name, left_station=info.get('left_station'), arrive_station=info.get('arrive_station'), set_type=info.get('set_type'), orderno=info.get('orderno')) } response = self.session.request(url=API_NOTIFICATION_BY_VOICE_CODE_DINGXIN, method='POST', data=data, headers={'Authorization': 'APPCODE {}'.format(appcode)}) result = response.json() response_message = result.get('return_code') if response.status_code in [400, 401, 403]: return CommonLog.add_quick_log(CommonLog.MESSAGE_VOICE_API_FORBID).flush() if response.status_code == 200 and result.get('return_code') == '00000': CommonLog.add_quick_log(CommonLog.MESSAGE_VOICE_API_SEND_SUCCESS.format(response_message)).flush() return True else: return CommonLog.add_quick_log(CommonLog.MESSAGE_VOICE_API_SEND_FAIL.format(response_message)).flush() def send_email_by_smtp(self, to, title, content): import smtplib from email.message import EmailMessage to = to if isinstance(to, list) else [to] message = EmailMessage() message['Subject'] = title message['From'] = Config().EMAIL_SENDER message['To'] = to message.set_content(content) try: server = smtplib.SMTP(Config().EMAIL_SERVER_HOST) server.login(Config().EMAIL_SERVER_USER, Config().EMAIL_SERVER_PASSWORD) server.ehlo() server.starttls() server.send_message(message) server.quit() CommonLog.add_quick_log(CommonLog.MESSAGE_SEND_EMAIL_SUCCESS).flush() except Exception as e: CommonLog.add_quick_log(CommonLog.MESSAGE_SEND_EMAIL_FAIL.format(e)).flush() def send_dingtalk_by_webbook(self, content): from dingtalkchatbot.chatbot import DingtalkChatbot webhook = Config().DINGTALK_WEBHOOK dingtalk = DingtalkChatbot(webhook) dingtalk.send_text(msg=content, is_at_all=True) pass def send_to_telegram_bot(self, content): bot_api_url = Config().TELEGRAM_BOT_API_URL if not bot_api_url: return False data = { 'text': content } response = self.session.request(url=bot_api_url, method='POST', data=data) result = response.json().get('result') response_status = result.get('statusCode') if response_status == 200: CommonLog.add_quick_log(CommonLog.MESSAGE_SEND_TELEGRAM_SUCCESS).flush() else: response_error_message = result.get('description') CommonLog.add_quick_log(CommonLog.MESSAGE_SEND_TELEGRAM_FAIL.format(response_error_message)).flush() def push_to_bark(self, content): bark_url = Config().BARK_PUSH_URL if not bark_url: return False response = self.session.request(url=bark_url + '/' + content, method='get') result = response.json() response_status = result.get('code') if response_status == 200: CommonLog.add_quick_log(CommonLog.MESSAGE_SEND_BARK_SUCCESS).flush() else: response_error_message = result.get('message') CommonLog.add_quick_log(CommonLog.MESSAGE_SEND_BARK_FAIL.format(response_error_message)).flush() def send_serverchan(self, skey, title, content): from lightpush import lightpush lgp = lightpush() lgp.set_single_push(key=skey) try: lgp.single_push(title, content) CommonLog.add_quick_log(CommonLog.MESSAGE_SEND_SERVER_CHAN_SUCCESS).flush() except Exception as e: CommonLog.add_quick_log(CommonLog.MESSAGE_SEND_SERVER_CHAN_FAIL.format(e)).flush() def send_pushbear(self, skey, title, content): from lightpush import lightpush lgp = lightpush() lgp.set_group_push(key=skey) try: lgp.group_push(title, content) CommonLog.add_quick_log(CommonLog.MESSAGE_SEND_PUSH_BEAR_SUCCESS).flush() except Exception as e: CommonLog.add_quick_log(CommonLog.MESSAGE_SEND_PUSH_BEAR_SUCCESS.format(e)).flush()
class UserJob: # heartbeat = 60 * 2 # 心跳保持时长 is_alive = True check_interval = 5 key = None user_name = '' password = '' user = None info = {} # 用户信息 last_heartbeat = None is_ready = False user_loaded = False # 用户是否已加载成功 passengers = [] retry_time = 3 login_num = 0 # 尝试登录次数 # Init page global_repeat_submit_token = None ticket_info_for_passenger_form = None order_request_dto = None cluster = None lock_init_user_time = 3 * 60 cookie = False def __init__(self, info): self.cluster = Cluster() self.init_data(info) def init_data(self, info): self.session = Request() self.session.add_response_hook(self.response_login_check) self.key = str(info.get('key')) self.user_name = info.get('user_name') self.password = info.get('password') def update_user(self): from py12306.user.user import User self.user = User() self.load_user() def run(self): # load user self.update_user() self.start() def start(self): """ 检测心跳 :return: """ while True and self.is_alive: app_available_check() if Config().is_slave(): self.load_user_from_remote() else: if Config().is_master() and not self.cookie: self.load_user_from_remote() # 主节点加载一次 Cookie self.check_heartbeat() if Const.IS_TEST: return stay_second(self.check_interval) def check_heartbeat(self): # 心跳检测 if self.get_last_heartbeat() and ( time_int() - self.get_last_heartbeat()) < Config().USER_HEARTBEAT_INTERVAL: return True # 只有主节点才能走到这 if self.is_first_time() or not self.check_user_is_login(): if not self.handle_login(): return self.user_did_load() message = UserLog.MESSAGE_USER_HEARTBEAT_NORMAL.format( self.get_name(), Config().USER_HEARTBEAT_INTERVAL) UserLog.add_quick_log(message).flush() def get_last_heartbeat(self): if Config().is_cluster_enabled(): return int( self.cluster.session.get(Cluster.KEY_USER_LAST_HEARTBEAT, 0)) return self.last_heartbeat def set_last_heartbeat(self, time=None): time = time if time != None else time_int() if Config().is_cluster_enabled(): self.cluster.session.set(Cluster.KEY_USER_LAST_HEARTBEAT, time) self.last_heartbeat = time # def init_cookies def is_first_time(self): if Config().is_cluster_enabled(): return not self.cluster.get_user_cookie(self.key) return not path.exists(self.get_cookie_path()) def handle_login(self, expire=False): if expire: UserLog.print_user_expired() self.is_ready = False UserLog.print_start_login(user=self) return self.login() def login(self): """ 获取验证码结果 :return 权限校验码 """ data = { 'username': self.user_name, 'password': self.password, 'appid': 'otn' } self.request_device_id() answer = AuthCode.get_auth_code(self.session) data['answer'] = answer response = self.session.post(API_BASE_LOGIN.get('url'), data) result = response.json() print(result) if result.get('result_code') == 0: # 登录成功 """ login 获得 cookie uamtk auth/uamtk 不请求,会返回 uamtk票据内容为空 /otn/uamauthclient 能拿到用户名 """ new_tk = self.auth_uamtk() user_name = self.auth_uamauthclient(new_tk) # self.init_my_12306_api() self.update_user_info({'user_name': user_name}) self.login_did_success() return True elif result.get('result_code') == 2: # 账号之内错误 # 登录失败,用户名或密码为空 # 密码输入错误 UserLog.add_quick_log( UserLog.MESSAGE_LOGIN_FAIL.format( result.get('result_message'))).flush() else: UserLog.add_quick_log( UserLog.MESSAGE_LOGIN_FAIL.format( result.get( 'result_message', result.get( 'message', CommonLog.MESSAGE_RESPONSE_EMPTY_ERROR)))).flush() return False def check_user_is_login(self): response = self.session.get(API_USER_LOGIN_CHECK) is_login = response.json().get('data.is_login', False) == 'Y' if is_login: self.save_user() self.set_last_heartbeat() return self.get_user_info( ) # 检测应该是不会维持状态,这里再请求下个人中心看有没有用,01-10 看来应该是没用 01-22 有时拿到的状态 是已失效的再加上试试 return is_login def auth_uamtk(self): response = self.session.post(API_AUTH_UAMTK.get('url'), {'appid': 'otn'}) result = response.json() if result.get('newapptk'): return result.get('newapptk') # TODO 处理获取失败情况 return False def auth_uamauthclient(self, tk): response = self.session.post(API_AUTH_UAMAUTHCLIENT.get('url'), {'tk': tk}) result = response.json() if result.get('username'): return result.get('username') # # TODO 处理获取失败情况 return False def init_my_12306_api(self): response = self.session.post(API_AUTH_INITMy12306Api.get('url')) result = response.json() print(result) print(result.get('data')['user_name']) # if result.get('data')['user_name']: # return result.get('data')['user_name'] # return False def request_device_id(self): """ 获取加密后的浏览器特征 ID :return: """ response = self.session.get(API_GET_BROWSER_DEVICE_ID) if response.status_code == 200: try: result = json.loads(response.text) self.session.cookies.update(result) self.session.cookies.update({ 'RAIL_EXPIRATION': '1569235953446', 'RAIL_DEVICEID': 'AZbdkCY6rk-b3J_W98mcINAH-YjQBFH1V3Km1ksO793jzhyDkQS4_u25heGonaHws5pml1x0ZB8L2pmcUct6rztKmJV0FNmDDAJD4Fe4Y_azJhhfS4JNuvBiEO_3tvYRKgicqV75RdzYSClHbZh0AyDo4iD8V5Bf', }) except: return False def login_did_success(self): """ 用户登录成功 :return: """ self.login_num += 1 self.welcome_user() self.save_user() self.get_user_info() self.set_last_heartbeat() self.is_ready = True def welcome_user(self): UserLog.print_welcome_user(self) pass def get_cookie_path(self): return Config().USER_DATA_DIR + self.user_name + '.cookie' def update_user_info(self, info): self.info = {**self.info, **info} def get_name(self): return self.info.get('user_name', '') def save_user(self): if Config().is_master(): self.cluster.set_user_cookie(self.key, self.session.cookies) self.cluster.set_user_info(self.key, self.info) with open(self.get_cookie_path(), 'wb') as f: pickle.dump(self.session.cookies, f) def did_loaded_user(self): """ 恢复用户成功 :return: """ UserLog.add_quick_log( UserLog.MESSAGE_LOADED_USER.format(self.user_name)).flush() if self.check_user_is_login() and self.get_user_info(): UserLog.add_quick_log( UserLog.MESSAGE_LOADED_USER_SUCCESS.format( self.user_name)).flush() UserLog.print_welcome_user(self) self.user_did_load() else: UserLog.add_quick_log( UserLog.MESSAGE_LOADED_USER_BUT_EXPIRED).flush() self.set_last_heartbeat(0) def user_did_load(self): """ 用户已经加载成功 :return: """ self.is_ready = True if self.user_loaded: return self.user_loaded = True Event().user_loaded({'key': self.key}) # 发布通知 def get_user_info(self): response = self.session.get(API_USER_INFO.get('url')) result = response.json() user_data = result.get('data.userDTO.loginUserDTO') # 子节点访问会导致主节点登录失效 TODO 可快考虑实时同步 cookie if user_data: self.update_user_info({ **user_data, **{ 'user_name': user_data.get('name') } }) self.save_user() return True return False def load_user(self): if Config().is_cluster_enabled(): return cookie_path = self.get_cookie_path() if path.exists(cookie_path): with open(self.get_cookie_path(), 'rb') as f: cookie = pickle.load(f) self.cookie = True self.session.cookies.update(cookie) self.did_loaded_user() return True return None def load_user_from_remote(self): cookie = self.cluster.get_user_cookie(self.key) info = self.cluster.get_user_info(self.key) if Config().is_slave() and (not cookie or not info): while True: # 子节点只能取 UserLog.add_quick_log( UserLog.MESSAGE_USER_COOKIE_NOT_FOUND_FROM_REMOTE.format( self.user_name)).flush() stay_second(self.retry_time) return self.load_user_from_remote() if info: self.info = info if cookie: self.session.cookies.update(cookie) if not self.cookie: # 第一次加载 self.cookie = True if not Config().is_slave(): self.did_loaded_user() else: self.is_ready = True # 设置子节点用户 已准备好 UserLog.print_welcome_user(self) return True return False def check_is_ready(self): return self.is_ready def wait_for_ready(self): if self.is_ready: return self UserLog.add_quick_log( UserLog.MESSAGE_WAIT_USER_INIT_COMPLETE.format( self.retry_time)).flush() stay_second(self.retry_time) return self.wait_for_ready() def destroy(self): """ 退出用户 :return: """ UserLog.add_quick_log( UserLog.MESSAGE_USER_BEING_DESTROY.format(self.user_name)).flush() self.is_alive = False def response_login_check(self, response, **kwargs): if Config().is_master() and response.json().get( 'data.noLogin') == 'true': # relogin self.handle_login(expire=True) def get_user_passengers(self): if self.passengers: return self.passengers response = self.session.post(API_USER_PASSENGERS) result = response.json() if result.get('data.normal_passengers'): self.passengers = result.get('data.normal_passengers') # 将乘客写入到文件 with open(Config().USER_PASSENGERS_FILE % self.user_name, 'w', encoding='utf-8') as f: f.write( json.dumps(self.passengers, indent=4, ensure_ascii=False)) return self.passengers else: UserLog.add_quick_log( UserLog.MESSAGE_GET_USER_PASSENGERS_FAIL.format( result.get('messages', CommonLog.MESSAGE_RESPONSE_EMPTY_ERROR), self.retry_time)).flush() if Config().is_slave(): self.load_user_from_remote() # 加载最新 cookie stay_second(self.retry_time) return self.get_user_passengers() def get_passengers_by_members(self, members): """ 获取格式化后的乘客信息 :param members: :return: [{ name: '项羽', type: 1, id_card: 0000000000000000000, type_text: '成人', enc_str: 'aaaaaa' }] """ self.get_user_passengers() results = [] for member in members: is_member_code = is_number(member) if not is_member_code: child_check = array_dict_find_by_key_value( results, 'name', member) if not is_member_code and child_check: new_member = child_check.copy() new_member['type'] = UserType.CHILD new_member['type_text'] = dict_find_key_by_value( UserType.dicts, int(new_member['type'])) else: if is_member_code: passenger = array_dict_find_by_key_value( self.passengers, 'code', member) else: passenger = array_dict_find_by_key_value( self.passengers, 'passenger_name', member) if not passenger: UserLog.add_quick_log( UserLog.MESSAGE_USER_PASSENGERS_IS_INVALID.format( self.user_name, member)).flush() return False new_member = { 'name': passenger.get('passenger_name'), 'id_card': passenger.get('passenger_id_no'), 'id_card_type': passenger.get('passenger_id_type_code'), 'mobile': passenger.get('mobile_no'), 'type': passenger.get('passenger_type'), 'type_text': dict_find_key_by_value( UserType.dicts, int(passenger.get('passenger_type'))), 'enc_str': passenger.get('allEncStr') } results.append(new_member) return results def request_init_dc_page(self): """ 请求下单页面 拿到 token :return: """ data = {'_json_att': ''} response = self.session.post(API_INITDC_URL, data) html = response.text token = re.search(r'var globalRepeatSubmitToken = \'(.+?)\'', html) form = re.search(r'var ticketInfoForPassengerForm *= *(\{.+\})', html) order = re.search(r'var orderRequestDTO *= *(\{.+\})', html) # 系统忙,请稍后重试 if html.find('系统忙,请稍后重试') != -1: OrderLog.add_quick_log(OrderLog.MESSAGE_REQUEST_INIT_DC_PAGE_FAIL ).flush() # 重试无用,直接跳过 return False try: self.global_repeat_submit_token = token.groups()[0] self.ticket_info_for_passenger_form = json.loads( form.groups()[0].replace("'", '"')) self.order_request_dto = json.loads(order.groups()[0].replace( "'", '"')) except: return False # TODO Error return True
def __init__(self): self.interval = init_interval_by_number(config.QUERY_INTERVAL) self.session = Request()
class UserJob: # heartbeat = 60 * 2 # 心跳保持时长 is_alive = True check_interval = 5 key = None user_name = '' password = '' type = 'qr' user = None info = {} # 用户信息 last_heartbeat = None is_ready = False user_loaded = False # 用户是否已加载成功 passengers = [] retry_time = 3 retry_count = 0 login_num = 0 # 尝试登录次数 # Init page global_repeat_submit_token = None ticket_info_for_passenger_form = None order_request_dto = None cluster = None lock_init_user_time = 3 * 60 cookie = False def __init__(self, info): self.cluster = Cluster() self.init_data(info) def init_data(self, info): self.session = Request() self.session.add_response_hook(self.response_login_check) self.key = str(info.get('key')) self.user_name = info.get('user_name') self.password = info.get('password') self.type = info.get('type') def update_user(self): from py12306.user.user import User self.user = User() self.load_user() def run(self): # load user self.update_user() self.start() def start(self): """ 检测心跳 :return: """ while True and self.is_alive: app_available_check() if Config().is_slave(): self.load_user_from_remote() else: if Config().is_master() and not self.cookie: self.load_user_from_remote() # 主节点加载一次 Cookie self.check_heartbeat() if Const.IS_TEST: return stay_second(self.check_interval) def check_heartbeat(self): # 心跳检测 if self.get_last_heartbeat() and (time_int() - self.get_last_heartbeat()) < Config().USER_HEARTBEAT_INTERVAL: return True # 只有主节点才能走到这 if self.is_first_time() or not self.check_user_is_login(): if not self.handle_login(): return self.user_did_load() message = UserLog.MESSAGE_USER_HEARTBEAT_NORMAL.format(self.get_name(), Config().USER_HEARTBEAT_INTERVAL) UserLog.add_quick_log(message).flush() def get_last_heartbeat(self): if Config().is_cluster_enabled(): return int(self.cluster.session.get(Cluster.KEY_USER_LAST_HEARTBEAT, 0)) return self.last_heartbeat def set_last_heartbeat(self, time=None): time = time if time != None else time_int() if Config().is_cluster_enabled(): self.cluster.session.set(Cluster.KEY_USER_LAST_HEARTBEAT, time) self.last_heartbeat = time # def init_cookies def is_first_time(self): if Config().is_cluster_enabled(): return not self.cluster.get_user_cookie(self.key) return not path.exists(self.get_cookie_path()) def handle_login(self, expire=False): if expire: UserLog.print_user_expired() self.is_ready = False UserLog.print_start_login(user=self) if self.type == 'qr': return self.qr_login() else: return self.login() def login(self): """ 获取验证码结果 :return 权限校验码 """ data = { 'username': self.user_name, 'password': self.password, 'appid': 'otn' } answer = AuthCode.get_auth_code(self.session) data['answer'] = answer self.request_device_id() response = self.session.post(API_BASE_LOGIN.get('url'), data) result = response.json() if result.get('result_code') == 0: # 登录成功 """ login 获得 cookie uamtk auth/uamtk 不请求,会返回 uamtk票据内容为空 /otn/uamauthclient 能拿到用户名 """ new_tk = self.auth_uamtk() user_name = self.auth_uamauthclient(new_tk) self.update_user_info({'user_name': user_name}) self.login_did_success() return True elif result.get('result_code') == 2: # 账号之内错误 # 登录失败,用户名或密码为空 # 密码输入错误 UserLog.add_quick_log(UserLog.MESSAGE_LOGIN_FAIL.format(result.get('result_message'))).flush() else: UserLog.add_quick_log( UserLog.MESSAGE_LOGIN_FAIL.format(result.get('result_message', result.get('message', CommonLog.MESSAGE_RESPONSE_EMPTY_ERROR)))).flush() return False def qr_login(self): self.request_device_id() image_uuid, png_path = self.download_code() while True: data = { 'RAIL_DEVICEID': self.session.cookies.get('RAIL_DEVICEID'), 'RAIL_EXPIRATION': self.session.cookies.get('RAIL_EXPIRATION'), 'uuid': image_uuid, 'appid': 'otn' } response = self.session.post(API_AUTH_QRCODE_CHECK.get('url'), data) result = response.json() result_code = int(result.get('result_code')) if result_code == 0: time.sleep(2) elif result_code == 1: UserLog.add_quick_log('请确认登录').flush() time.sleep(2) elif result_code == 2: break elif result_code == 3: try: os.remove(png_path) except Exception as e: UserLog.add_quick_log('无法删除文件: {}'.format(e)).flush() image_uuid = self.download_code() try: os.remove(png_path) except Exception as e: UserLog.add_quick_log('无法删除文件: {}'.format(e)).flush() self.session.get(API_USER_LOGIN, allow_redirects=True) new_tk = self.auth_uamtk() user_name = self.auth_uamauthclient(new_tk) self.update_user_info({'user_name': user_name}) self.session.get(API_USER_LOGIN, allow_redirects=True) self.login_did_success() return True def download_code(self): try: UserLog.add_quick_log(UserLog.MESSAGE_QRCODE_DOWNLOADING).flush() response = self.session.post(API_AUTH_QRCODE_BASE64_DOWNLOAD.get('url'), data={'appid': 'otn'}) result = response.json() if result.get('result_code') == '0': img_bytes = base64.b64decode(result.get('image')) try: os.mkdir(Config().USER_DATA_DIR + '/qrcode') except FileExistsError: pass png_path = path.normpath(Config().USER_DATA_DIR + '/qrcode/%d.png' % time.time()) with open(png_path, 'wb') as file: file.write(img_bytes) file.close() if os.name == 'nt': os.startfile(png_path) else: print_qrcode(png_path) UserLog.add_log(UserLog.MESSAGE_QRCODE_DOWNLOADED.format(png_path)).flush() Notification.send_email_with_qrcode(Config().EMAIL_RECEIVER, '你有新的登录二维码啦!', png_path) self.retry_count = 0 return result.get('uuid'), png_path raise KeyError('获取二维码失败: {}'.format(result.get('result_message'))) except Exception as e: UserLog.add_quick_log( UserLog.MESSAGE_QRCODE_FAIL.format(e, self.retry_time)).flush() self.retry_count = self.retry_count + 1 if self.retry_count == 20: self.retry_count = 0 try: os.remove(self.get_cookie_path()) except: pass time.sleep(self.retry_time) return self.download_code() def check_user_is_login(self): response = self.session.get(API_USER_LOGIN_CHECK) is_login = response.json().get('data.is_login', False) == 'Y' if is_login: self.save_user() self.set_last_heartbeat() return self.get_user_info() # 检测应该是不会维持状态,这里再请求下个人中心看有没有用,01-10 看来应该是没用 01-22 有时拿到的状态 是已失效的再加上试试 return is_login def auth_uamtk(self): response = self.session.post(API_AUTH_UAMTK.get('url'), {'appid': 'otn'}, headers={ 'Referer': 'https://kyfw.12306.cn/otn/passport?redirect=/otn/login/userLogin', 'Origin': 'https://kyfw.12306.cn' }) result = response.json() if result.get('newapptk'): return result.get('newapptk') # TODO 处理获取失败情况 return False def auth_uamauthclient(self, tk): response = self.session.post(API_AUTH_UAMAUTHCLIENT.get('url'), {'tk': tk}) result = response.json() if result.get('username'): return result.get('username') # TODO 处理获取失败情况 return False def request_device_id(self): """ 获取加密后的浏览器特征 ID :return: """ response = self.session.get(API_GET_BROWSER_DEVICE_ID) if response.status_code == 200: try: result = json.loads(response.text) headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36" } self.session.headers.update(headers) response = self.session.get(base64.b64decode(result['id']).decode()) if response.text.find('callbackFunction') >= 0: result = response.text[18:-2] result = json.loads(result) if not Config().is_cache_rail_id_enabled(): self.session.cookies.update({ 'RAIL_EXPIRATION': result.get('exp'), 'RAIL_DEVICEID': result.get('dfp'), }) else: self.session.cookies.update({ 'RAIL_EXPIRATION': Config().RAIL_EXPIRATION, 'RAIL_DEVICEID': Config().RAIL_DEVICEID, }) except: return False def login_did_success(self): """ 用户登录成功 :return: """ self.login_num += 1 self.welcome_user() self.save_user() self.get_user_info() self.set_last_heartbeat() self.is_ready = True def welcome_user(self): UserLog.print_welcome_user(self) pass def get_cookie_path(self): return Config().USER_DATA_DIR + self.user_name + '.cookie' def update_user_info(self, info): self.info = {**self.info, **info} def get_name(self): return self.info.get('user_name', '') def save_user(self): if Config().is_master(): self.cluster.set_user_cookie(self.key, self.session.cookies) self.cluster.set_user_info(self.key, self.info) with open(self.get_cookie_path(), 'wb') as f: pickle.dump(self.session.cookies, f) def did_loaded_user(self): """ 恢复用户成功 :return: """ UserLog.add_quick_log(UserLog.MESSAGE_LOADED_USER.format(self.user_name)).flush() if self.check_user_is_login() and self.get_user_info(): UserLog.add_quick_log(UserLog.MESSAGE_LOADED_USER_SUCCESS.format(self.user_name)).flush() UserLog.print_welcome_user(self) self.user_did_load() else: UserLog.add_quick_log(UserLog.MESSAGE_LOADED_USER_BUT_EXPIRED).flush() self.set_last_heartbeat(0) def user_did_load(self): """ 用户已经加载成功 :return: """ self.is_ready = True if self.user_loaded: return self.user_loaded = True Event().user_loaded({'key': self.key}) # 发布通知 def get_user_info(self): response = self.session.get(API_USER_INFO.get('url')) result = response.json() user_data = result.get('data.userDTO.loginUserDTO') # 子节点访问会导致主节点登录失效 TODO 可快考虑实时同步 cookie if user_data: self.update_user_info({**user_data, **{'user_name': user_data.get('name')}}) self.save_user() return True return False def load_user(self): if Config().is_cluster_enabled(): return cookie_path = self.get_cookie_path() if path.exists(cookie_path): with open(self.get_cookie_path(), 'rb') as f: cookie = pickle.load(f) self.cookie = True self.session.cookies.update(cookie) self.did_loaded_user() return True return None def load_user_from_remote(self): cookie = self.cluster.get_user_cookie(self.key) info = self.cluster.get_user_info(self.key) if Config().is_slave() and (not cookie or not info): while True: # 子节点只能取 UserLog.add_quick_log(UserLog.MESSAGE_USER_COOKIE_NOT_FOUND_FROM_REMOTE.format(self.user_name)).flush() stay_second(self.retry_time) return self.load_user_from_remote() if info: self.info = info if cookie: self.session.cookies.update(cookie) if not self.cookie: # 第一次加载 self.cookie = True if not Config().is_slave(): self.did_loaded_user() else: self.is_ready = True # 设置子节点用户 已准备好 UserLog.print_welcome_user(self) return True return False def check_is_ready(self): return self.is_ready def wait_for_ready(self): if self.is_ready: return self UserLog.add_quick_log(UserLog.MESSAGE_WAIT_USER_INIT_COMPLETE.format(self.retry_time)).flush() stay_second(self.retry_time) return self.wait_for_ready() def destroy(self): """ 退出用户 :return: """ UserLog.add_quick_log(UserLog.MESSAGE_USER_BEING_DESTROY.format(self.user_name)).flush() self.is_alive = False def response_login_check(self, response, **kwargs): if Config().is_master() and response.json().get('data.noLogin') == 'true': # relogin self.handle_login(expire=True) def get_user_passengers(self): if self.passengers: return self.passengers response = self.session.post(API_USER_PASSENGERS) result = response.json() if result.get('data.normal_passengers'): self.passengers = result.get('data.normal_passengers') # 将乘客写入到文件 with open(Config().USER_PASSENGERS_FILE % self.user_name, 'w', encoding='utf-8') as f: f.write(json.dumps(self.passengers, indent=4, ensure_ascii=False)) return self.passengers else: UserLog.add_quick_log( UserLog.MESSAGE_GET_USER_PASSENGERS_FAIL.format( result.get('messages', CommonLog.MESSAGE_RESPONSE_EMPTY_ERROR), self.retry_time)).flush() if Config().is_slave(): self.load_user_from_remote() # 加载最新 cookie stay_second(self.retry_time) return self.get_user_passengers() def get_passengers_by_members(self, members): """ 获取格式化后的乘客信息 :param members: :return: [{ name: '项羽', type: 1, id_card: 0000000000000000000, type_text: '成人', enc_str: 'aaaaaa' }] """ self.get_user_passengers() results = [] for member in members: is_member_code = is_number(member) if not is_member_code: if member[0] == "*": audlt = 1 member = member[1:] else: audlt = 0 child_check = array_dict_find_by_key_value(results, 'name', member) if not is_member_code and child_check: new_member = child_check.copy() new_member['type'] = UserType.CHILD new_member['type_text'] = dict_find_key_by_value(UserType.dicts, int(new_member['type'])) else: if is_member_code: passenger = array_dict_find_by_key_value(self.passengers, 'code', member) else: passenger = array_dict_find_by_key_value(self.passengers, 'passenger_name', member) if audlt: passenger['passenger_type'] = UserType.ADULT if not passenger: UserLog.add_quick_log( UserLog.MESSAGE_USER_PASSENGERS_IS_INVALID.format(self.user_name, member)).flush() return False new_member = { 'name': passenger.get('passenger_name'), 'id_card': passenger.get('passenger_id_no'), 'id_card_type': passenger.get('passenger_id_type_code'), 'mobile': passenger.get('mobile_no'), 'type': passenger.get('passenger_type'), 'type_text': dict_find_key_by_value(UserType.dicts, int(passenger.get('passenger_type'))), 'enc_str': passenger.get('allEncStr') } results.append(new_member) return results def request_init_dc_page(self): """ 请求下单页面 拿到 token :return: """ data = {'_json_att': ''} response = self.session.post(API_INITDC_URL, data) html = response.text token = re.search(r'var globalRepeatSubmitToken = \'(.+?)\'', html) form = re.search(r'var ticketInfoForPassengerForm *= *(\{.+\})', html) order = re.search(r'var orderRequestDTO *= *(\{.+\})', html) # 系统忙,请稍后重试 if html.find('系统忙,请稍后重试') != -1: OrderLog.add_quick_log(OrderLog.MESSAGE_REQUEST_INIT_DC_PAGE_FAIL).flush() # 重试无用,直接跳过 return False, False, html try: self.global_repeat_submit_token = token.groups()[0] self.ticket_info_for_passenger_form = json.loads(form.groups()[0].replace("'", '"')) self.order_request_dto = json.loads(order.groups()[0].replace("'", '"')) except: return False, False, html # TODO Error slide_val = re.search(r"var if_check_slide_passcode.*='(\d?)'", html) is_slide = False if slide_val: is_slide = int(slide_val[1]) == 1 return True, is_slide, html
class OCR: """ 图片识别 """ session = None def __init__(self): self.session = Request() @classmethod def get_img_position(cls, img): """ 获取图像坐标 :param img_path: :return: """ self = cls() if Config().AUTO_CODE_PLATFORM == 'free': return self.get_image_by_free_site(img) return self.get_img_position_by_ruokuai(img) def get_img_position_by_ruokuai(self, img): ruokuai_account = Config().AUTO_CODE_ACCOUNT soft_id = '119671' soft_key = '6839cbaca1f942f58d2760baba5ed987' rc = RKClient(ruokuai_account.get('user'), ruokuai_account.get('pwd'), soft_id, soft_key) result = rc.rk_create(img, 6113) if "Result" in result: return self.get_image_position_by_offset(list(result['Result'])) CommonLog.print_auto_code_fail(result.get("Error", '-')) return None def get_image_position_by_offset(self, offsets): positions = [] width = 75 height = 75 for offset in offsets: random_x = random.randint(-5, 5) random_y = random.randint(-5, 5) offset = int(offset) x = width * ((offset - 1) % 4 + 1) - width / 2 + random_x y = height * math.ceil(offset / 4) - height / 2 + random_y positions.append(int(x)) positions.append(int(y)) return positions def get_image_by_free_site(self, img): data = {'base64': img} response = self.session.post(API_FREE_CODE_QCR_API, json=data) result = response.json() if result.get('success') and result.get('check'): check_data = { 'check': result.get('check'), 'img_buf': img, 'logon': 1, 'type': 'D' } check_response = self.session.post(API_FREE_CODE_QCR_API_CHECK, json=check_data) check_result = check_response.json() if check_result.get('res'): position = check_result.get('res') return position.replace('(', '').replace(')', '').split(',') CommonLog.print_auto_code_fail(result.get("Error", '-')) return None
def __init__(self): self.session = Request()
class UserJob: # heartbeat = 60 * 2 # 心跳保持时长 is_alive = True check_interval = 5 key = None user_name = '' password = '' user = None info = {} # 用户信息 last_heartbeat = None is_ready = False user_loaded = False # 用户是否已加载成功 passengers = [] retry_time = 3 login_num = 0 # 尝试登录次数 # Init page global_repeat_submit_token = None ticket_info_for_passenger_form = None order_request_dto = None cluster = None lock_init_user_time = 3 * 60 cookie = False def __init__(self, info): self.cluster = Cluster() self.init_data(info) def init_data(self, info): self.session = Request() self.session.add_response_hook(self.response_login_check) self.key = str(info.get('key')) self.user_name = info.get('user_name') self.password = info.get('password') def update_user(self): from py12306.user.user import User self.user = User() self.load_user() def run(self): # load user self.update_user() self.start() def start(self): """ 检测心跳 :return: """ while True and self.is_alive: app_available_check() if Config().is_slave(): self.load_user_from_remote() else: if Config().is_master() and not self.cookie: self.load_user_from_remote() # 主节点加载一次 Cookie self.check_heartbeat() if Const.IS_TEST: return stay_second(self.check_interval) def check_heartbeat(self): # 心跳检测 if self.get_last_heartbeat() and ( time_int() - self.get_last_heartbeat()) < Config().USER_HEARTBEAT_INTERVAL: return True # 只有主节点才能走到这 if self.is_first_time() or not self.check_user_is_login(): if not self.handle_login(): return self.user_did_load() message = UserLog.MESSAGE_USER_HEARTBEAT_NORMAL.format( self.get_name(), Config().USER_HEARTBEAT_INTERVAL) UserLog.add_quick_log(message).flush() def get_last_heartbeat(self): if Config().is_cluster_enabled(): return int( self.cluster.session.get(Cluster.KEY_USER_LAST_HEARTBEAT, 0)) return self.last_heartbeat def set_last_heartbeat(self, time=None): time = time if time != None else time_int() if Config().is_cluster_enabled(): self.cluster.session.set(Cluster.KEY_USER_LAST_HEARTBEAT, time) self.last_heartbeat = time # def init_cookies def is_first_time(self): if Config().is_cluster_enabled(): return not self.cluster.get_user_cookie(self.key) return not path.exists(self.get_cookie_path()) def handle_login(self, expire=False): if expire: UserLog.print_user_expired() self.is_ready = False UserLog.print_start_login(user=self) return self.login() def login(self): """ 获取验证码结果 :return 权限校验码 """ data = { 'username': self.user_name, 'password': self.password, 'appid': 'otn' } self.request_device_id() answer = AuthCode.get_auth_code(self.session) data['answer'] = answer response = self.session.post(API_BASE_LOGIN.get('url'), data) result = response.json() if result.get('result_code') == 0: # 登录成功 """ login 获得 cookie uamtk auth/uamtk 不请求,会返回 uamtk票据内容为空 /otn/uamauthclient 能拿到用户名 """ new_tk = self.auth_uamtk() user_name = self.auth_uamauthclient(new_tk) self.update_user_info({'user_name': user_name}) self.login_did_success() return True elif result.get('result_code') == 2: # 账号之内错误 # 登录失败,用户名或密码为空 # 密码输入错误 UserLog.add_quick_log( UserLog.MESSAGE_LOGIN_FAIL.format( result.get('result_message'))).flush() else: UserLog.add_quick_log( UserLog.MESSAGE_LOGIN_FAIL.format( result.get( 'result_message', result.get( 'message', CommonLog.MESSAGE_RESPONSE_EMPTY_ERROR)))).flush() return False def check_user_is_login(self): response = self.session.get(API_USER_LOGIN_CHECK) is_login = response.json().get('data.is_login', False) == 'Y' if is_login: self.save_user() self.set_last_heartbeat() return self.get_user_info( ) # 检测应该是不会维持状态,这里再请求下个人中心看有没有用,01-10 看来应该是没用 01-22 有时拿到的状态 是已失效的再加上试试 return is_login def auth_uamtk(self): response = self.session.post(API_AUTH_UAMTK.get('url'), {'appid': 'otn'}) result = response.json() if result.get('newapptk'): return result.get('newapptk') # TODO 处理获取失败情况 return False def auth_uamauthclient(self, tk): response = self.session.post(API_AUTH_UAMAUTHCLIENT.get('url'), {'tk': tk}) result = response.json() if result.get('username'): return result.get('username') # TODO 处理获取失败情况 return False def request_device_id(self): """ 获取加密后的浏览器特征 ID :return: """ """ TODO 通过程序进行加密,目前不清楚固定参数能使用多久 encrypt js address: https://kyfw.12306.cn/otn/HttpZF/GetJS line:2716 """ params = { "algID": "ozy7Gbfya4", "hashCode": "SPPnxwJaxslzp6PLz38mV_078n44WjSOp7vTgvQpGxA", "FMQw": "0", "q4f3": "zh-CN", "VySQ": "FGGlO7IzXMj0sfYT705RPKxtnYIHB5MI", "VPIf": "1", "custID": "133", "VEek": "unknown", "dzuS": "0", "yD16": "0", "EOQP": "c227b88b01f5c513710d4b9f16a5ce52", "lEnu": "167838050", "jp76": "52d67b2a5aa5e031084733d5006cc664", "hAqN": "MacIntel", "platform": "WEB", "ks0Q": "d22ca0b81584fbea62237b14bd04c866", "TeRS": "878x1440", "tOHY": "24xx900x1440", "Fvje": "i1l1o1s1", "q5aJ": "-8", "wNLf": "99115dfb07133750ba677d055874de87", "0aew": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36", "E3gR": "8b9855470c623dc9bd6e495a1417f6f8", "timestamp": int(time.time() * 1000) } response = self.session.get(API_GET_BROWSER_DEVICE_ID, params=params) if response.text.find('callbackFunction') >= 0: result = response.text[18:-2] try: result = json.loads(result) self.session.cookies.update({ 'RAIL_EXPIRATION': result.get('exp'), 'RAIL_DEVICEID': result.get('dfp'), }) except: return False def login_did_success(self): """ 用户登录成功 :return: """ self.login_num += 1 self.welcome_user() self.save_user() self.get_user_info() self.set_last_heartbeat() self.is_ready = True def welcome_user(self): UserLog.print_welcome_user(self) pass def get_cookie_path(self): return Config().USER_DATA_DIR + self.user_name + '.cookie' def update_user_info(self, info): self.info = {**self.info, **info} def get_name(self): return self.info.get('user_name', '') def save_user(self): if Config().is_master(): self.cluster.set_user_cookie(self.key, self.session.cookies) self.cluster.set_user_info(self.key, self.info) with open(self.get_cookie_path(), 'wb') as f: pickle.dump(self.session.cookies, f) def did_loaded_user(self): """ 恢复用户成功 :return: """ UserLog.add_quick_log( UserLog.MESSAGE_LOADED_USER.format(self.user_name)).flush() if self.check_user_is_login() and self.get_user_info(): UserLog.add_quick_log( UserLog.MESSAGE_LOADED_USER_SUCCESS.format( self.user_name)).flush() UserLog.print_welcome_user(self) self.user_did_load() else: UserLog.add_quick_log( UserLog.MESSAGE_LOADED_USER_BUT_EXPIRED).flush() self.set_last_heartbeat(0) def user_did_load(self): """ 用户已经加载成功 :return: """ self.is_ready = True if self.user_loaded: return self.user_loaded = True Event().user_loaded({'key': self.key}) # 发布通知 def get_user_info(self): response = self.session.get(API_USER_INFO.get('url')) result = response.json() user_data = result.get('data.userDTO.loginUserDTO') # 子节点访问会导致主节点登录失效 TODO 可快考虑实时同步 cookie if user_data: self.update_user_info({ **user_data, **{ 'user_name': user_data.get('name') } }) self.save_user() return True return False def load_user(self): if Config().is_cluster_enabled(): return cookie_path = self.get_cookie_path() if path.exists(cookie_path): with open(self.get_cookie_path(), 'rb') as f: cookie = pickle.load(f) self.cookie = True self.session.cookies.update(cookie) self.did_loaded_user() return True return None def load_user_from_remote(self): cookie = self.cluster.get_user_cookie(self.key) info = self.cluster.get_user_info(self.key) if Config().is_slave() and (not cookie or not info): while True: # 子节点只能取 UserLog.add_quick_log( UserLog.MESSAGE_USER_COOKIE_NOT_FOUND_FROM_REMOTE.format( self.user_name)).flush() stay_second(self.retry_time) return self.load_user_from_remote() if info: self.info = info if cookie: self.session.cookies.update(cookie) if not self.cookie: # 第一次加载 self.cookie = True if not Config().is_slave(): self.did_loaded_user() else: self.is_ready = True # 设置子节点用户 已准备好 UserLog.print_welcome_user(self) return True return False def check_is_ready(self): return self.is_ready def wait_for_ready(self): if self.is_ready: return self UserLog.add_quick_log( UserLog.MESSAGE_WAIT_USER_INIT_COMPLETE.format( self.retry_time)).flush() stay_second(self.retry_time) return self.wait_for_ready() def destroy(self): """ 退出用户 :return: """ UserLog.add_quick_log( UserLog.MESSAGE_USER_BEING_DESTROY.format(self.user_name)).flush() self.is_alive = False def response_login_check(self, response, **kwargs): if Config().is_master() and response.json().get( 'data.noLogin') == 'true': # relogin self.handle_login(expire=True) def get_user_passengers(self): if self.passengers: return self.passengers response = self.session.post(API_USER_PASSENGERS) result = response.json() if result.get('data.normal_passengers'): self.passengers = result.get('data.normal_passengers') # 将乘客写入到文件 with open(Config().USER_PASSENGERS_FILE % self.user_name, 'w', encoding='utf-8') as f: f.write( json.dumps(self.passengers, indent=4, ensure_ascii=False)) return self.passengers else: UserLog.add_quick_log( UserLog.MESSAGE_GET_USER_PASSENGERS_FAIL.format( result.get('messages', CommonLog.MESSAGE_RESPONSE_EMPTY_ERROR), self.retry_time)).flush() if Config().is_slave(): self.load_user_from_remote() # 加载最新 cookie stay_second(self.retry_time) return self.get_user_passengers() def get_passengers_by_members(self, members): """ 获取格式化后的乘客信息 :param members: :return: [{ name: '项羽', type: 1, id_card: 0000000000000000000, type_text: '成人' }] """ self.get_user_passengers() results = [] for member in members: is_member_code = is_number(member) if not is_member_code: child_check = array_dict_find_by_key_value( results, 'name', member) if not is_member_code and child_check: new_member = child_check.copy() new_member['type'] = UserType.CHILD new_member['type_text'] = dict_find_key_by_value( UserType.dicts, int(new_member['type'])) else: if is_member_code: passenger = array_dict_find_by_key_value( self.passengers, 'code', member) else: passenger = array_dict_find_by_key_value( self.passengers, 'passenger_name', member) if not passenger: UserLog.add_quick_log( UserLog.MESSAGE_USER_PASSENGERS_IS_INVALID.format( self.user_name, member)).flush() return False new_member = { 'name': passenger.get('passenger_name'), 'id_card': passenger.get('passenger_id_no'), 'id_card_type': passenger.get('passenger_id_type_code'), 'mobile': passenger.get('mobile_no'), 'type': passenger.get('passenger_type'), 'type_text': dict_find_key_by_value( UserType.dicts, int(passenger.get('passenger_type'))) } results.append(new_member) return results def request_init_dc_page(self): """ 请求下单页面 拿到 token :return: """ data = {'_json_att': ''} response = self.session.post(API_INITDC_URL, data) html = response.text token = re.search(r'var globalRepeatSubmitToken = \'(.+?)\'', html) form = re.search(r'var ticketInfoForPassengerForm *= *(\{.+\})', html) order = re.search(r'var orderRequestDTO *= *(\{.+\})', html) # 系统忙,请稍后重试 if html.find('系统忙,请稍后重试') != -1: OrderLog.add_quick_log(OrderLog.MESSAGE_REQUEST_INIT_DC_PAGE_FAIL ).flush() # 重试无用,直接跳过 return False try: self.global_repeat_submit_token = token.groups()[0] self.ticket_info_for_passenger_form = json.loads( form.groups()[0].replace("'", '"')) self.order_request_dto = json.loads(order.groups()[0].replace( "'", '"')) except: pass # TODO Error return True
class Notification(): """ 通知类 """ session = None def __init__(self): self.session = Request() @classmethod def voice_code(cls, phone, name='', content=''): self = cls() self.send_voice_code_of_yiyuan(phone, name=name, content=content) @classmethod def send_email(cls, to, title='', content=''): self = cls() self.send_email_by_smtp(to, title, content) def send_voice_code_of_yiyuan(self, phone, name='', content=''): """ 发送语音验证码 购买地址 https://market.aliyun.com/products/57126001/cmapi019902.html?spm=5176.2020520132.101.5.37857218O6iJ3n :return: """ appcode = Config().NOTIFICATION_API_APP_CODE if not appcode: CommonLog.add_quick_log(CommonLog.MESSAGE_EMPTY_APP_CODE).flush() return False body = {'userName': name, 'mailNo': content} params = { 'content': body, 'mobile': phone, 'sex': 2, 'tNum': 'T170701001056' } response = self.session.request( url=API_NOTIFICATION_BY_VOICE_CODE + urllib.parse.urlencode(params), method='GET', headers={'Authorization': 'APPCODE {}'.format(appcode)}) result = response.json() response_message = result.get('showapi_res_body.remark') if response.status_code in [400, 401, 403]: return CommonLog.add_quick_log( CommonLog.MESSAGE_VOICE_API_FORBID).flush() if response.status_code == 200 and result.get('showapi_res_body.flag'): CommonLog.add_quick_log( CommonLog.MESSAGE_VOICE_API_SEND_SUCCESS.format( response_message)).flush() return True else: return CommonLog.add_quick_log( CommonLog.MESSAGE_VOICE_API_SEND_FAIL.format( response_message)).flush() def send_email_by_smtp(self, to, title, content): import smtplib from email.message import EmailMessage to = to if isinstance(to, list) else [to] message = EmailMessage() message['Subject'] = title message['From'] = '*****@*****.**' message['To'] = to message.set_content(content) try: server = smtplib.SMTP(Config().EMAIL_SERVER_HOST) server.login(Config().EMAIL_SERVER_USER, Config().EMAIL_SERVER_PASSWORD) server.send_message(message) server.quit() CommonLog.add_quick_log( CommonLog.MESSAGE_SEND_EMAIL_SUCCESS).flush() except Exception as e: CommonLog.add_quick_log( CommonLog.MESSAGE_SEND_EMAIL_FAIL.format(e)).flush()
class UserJob: # heartbeat = 60 * 2 # 心跳保持时长 is_alive = True check_interval = 5 key = None user_name = '' password = '' user = None info = {} # 用户信息 last_heartbeat = None is_ready = False user_loaded = False # 用户是否已加载成功 passengers = [] retry_time = 3 login_num = 0 # 尝试登录次数 # Init page global_repeat_submit_token = None ticket_info_for_passenger_form = None order_request_dto = None cluster = None lock_init_user_time = 3 * 60 cookie = False def __init__(self, info): self.cluster = Cluster() self.init_data(info) def init_data(self, info): self.session = Request() self.key = str(info.get('key')) self.user_name = info.get('user_name') self.password = info.get('password') def update_user(self): from py12306.user.user import User self.user = User() # if not Const.IS_TEST: 测试模块下也可以从文件中加载用户 self.load_user() def run(self): # load user self.update_user() self.start() def start(self): """ 检测心跳 :return: """ while True and self.is_alive: app_available_check() if Config().is_slave(): self.load_user_from_remote() pass else: if Config().is_master() and not self.cookie: self.load_user_from_remote() # 主节点加载一次 Cookie self.check_heartbeat() if Const.IS_TEST: return stay_second(self.check_interval) def check_heartbeat(self): # 心跳检测 if self.get_last_heartbeat() and ( time_int() - self.get_last_heartbeat()) < Config().USER_HEARTBEAT_INTERVAL: return True # 只有主节点才能走到这 if self.is_first_time() or not self.check_user_is_login(): self.is_ready = False if not self.handle_login(): return self.set_last_heartbeat() self.is_ready = True self.user_did_load() message = UserLog.MESSAGE_USER_HEARTBEAT_NORMAL.format( self.get_name(), Config().USER_HEARTBEAT_INTERVAL) if not Config.is_cluster_enabled(): UserLog.add_quick_log(message).flush() else: self.cluster.publish_log_message(message) # self.set_last_heartbeat() def get_last_heartbeat(self): if Config().is_cluster_enabled(): return int( self.cluster.session.get(Cluster.KEY_USER_LAST_HEARTBEAT, 0)) return self.last_heartbeat def set_last_heartbeat(self, time=None): if Config().is_cluster_enabled(): return self.cluster.session.set( Cluster.KEY_USER_LAST_HEARTBEAT, time if time != None else time_int()) self.last_heartbeat = time_int() # def init_cookies def is_first_time(self): if Config().is_cluster_enabled(): return not self.cluster.get_user_cookie(self.key) return not path.exists(self.get_cookie_path()) def handle_login(self): UserLog.print_start_login(user=self) return self.login() def login(self): """ 获取验证码结果 :return 权限校验码 """ data = { 'username': self.user_name, 'password': self.password, 'appid': 'otn' } answer = AuthCode.get_auth_code(self.session) data['answer'] = answer response = self.session.post(API_BASE_LOGIN.get('url'), data) result = response.json() if result.get('result_code') == 0: # 登录成功 """ login 获得 cookie uamtk auth/uamtk 不请求,会返回 uamtk票据内容为空 /otn/uamauthclient 能拿到用户名 """ new_tk = self.auth_uamtk() user_name = self.auth_uamauthclient(new_tk) self.update_user_info({'user_name': user_name}) self.login_did_success() return True elif result.get('result_code') == 2: # 账号之内错误 # 登录失败,用户名或密码为空 # 密码输入错误 UserLog.add_quick_log( UserLog.MESSAGE_LOGIN_FAIL.format( result.get('result_message'))).flush() else: UserLog.add_quick_log( UserLog.MESSAGE_LOGIN_FAIL.format( result.get('result_message', result.get('message', '-')))).flush() return False pass def check_user_is_login(self): response = self.session.get(API_USER_CHECK.get('url')) is_login = response.json().get('data.flag', False) if is_login: self.save_user() self.set_last_heartbeat() # self.get_user_info() # 检测应该是不会维持状态,这里再请求下个人中心看有没有用,01-10 看来应该是没用 return is_login def auth_uamtk(self): response = self.session.post(API_AUTH_UAMTK.get('url'), {'appid': 'otn'}) result = response.json() if result.get('newapptk'): return result.get('newapptk') # TODO 处理获取失败情况 return False def auth_uamauthclient(self, tk): response = self.session.post(API_AUTH_UAMAUTHCLIENT.get('url'), {'tk': tk}) result = response.json() if result.get('username'): return result.get('username') # TODO 处理获取失败情况 return False def login_did_success(self): """ 用户登录成功 :return: """ self.login_num += 1 self.welcome_user() self.save_user() self.get_user_info() pass def welcome_user(self): UserLog.print_welcome_user(self) pass def get_cookie_path(self): return Config().USER_DATA_DIR + self.user_name + '.cookie' def update_user_info(self, info): self.info = {**self.info, **info} def get_name(self): return self.info.get('user_name', '') def save_user(self): if Config().is_master(): self.cluster.set_user_cookie(self.key, self.session.cookies) self.cluster.set_user_info(self.key, self.info) with open(self.get_cookie_path(), 'wb') as f: pickle.dump(self.session.cookies, f) def did_loaded_user(self): """ 恢复用户成功 :return: """ UserLog.add_quick_log( UserLog.MESSAGE_LOADED_USER.format(self.user_name)).flush() if self.check_user_is_login() and self.get_user_info(): UserLog.add_quick_log( UserLog.MESSAGE_LOADED_USER_SUCCESS.format( self.user_name)).flush() self.is_ready = True UserLog.print_welcome_user(self) self.user_did_load() else: UserLog.add_quick_log( UserLog.MESSAGE_LOADED_USER_BUT_EXPIRED).flush() self.set_last_heartbeat(0) def user_did_load(self): """ 用户已经加载成功 :return: """ if self.user_loaded: return self.user_loaded = True Event().user_loaded({'key': self.key}) # 发布通知 def get_user_info(self): response = self.session.get(API_USER_INFO.get('url')) result = response.json() user_data = result.get('data.userDTO.loginUserDTO') # 子节点访问会导致主节点登录失效 TODO 可快考虑实时同步 cookie if user_data: self.update_user_info({ **user_data, **{ 'user_name': user_data.get('name') } }) self.save_user() return True return None def load_user(self): if Config().is_cluster_enabled(): return cookie_path = self.get_cookie_path() if path.exists(cookie_path): with open(self.get_cookie_path(), 'rb') as f: cookie = pickle.load(f) self.cookie = True self.session.cookies.update(cookie) self.did_loaded_user() return True return None def load_user_from_remote(self): cookie = self.cluster.get_user_cookie(self.key) info = self.cluster.get_user_info(self.key) if Config().is_slave() and (not cookie or not info): while True: # 子节点只能取 UserLog.add_quick_log( UserLog.MESSAGE_USER_COOKIE_NOT_FOUND_FROM_REMOTE.format( self.user_name)).flush() stay_second(self.retry_time) return self.load_user_from_remote() if info: self.info = info if cookie: self.session.cookies.update(cookie) if not self.cookie: # 第一次加载 self.cookie = True if not Config().is_slave(): self.did_loaded_user() else: UserLog.print_welcome_user(self) return True return False def check_is_ready(self): return self.is_ready def wait_for_ready(self): if self.is_ready: return self UserLog.add_quick_log( UserLog.MESSAGE_WAIT_USER_INIT_COMPLETE.format( self.retry_time)).flush() stay_second(self.retry_time) return self.wait_for_ready() def destroy(self): """ 退出用户 :return: """ UserLog.add_quick_log( UserLog.MESSAGE_USER_BEING_DESTROY.format(self.user_name)).flush() self.is_alive = False def get_user_passengers(self): if self.passengers: return self.passengers response = self.session.post(API_USER_PASSENGERS) result = response.json() if result.get('data.normal_passengers'): self.passengers = result.get('data.normal_passengers') return self.passengers else: UserLog.add_quick_log( UserLog.MESSAGE_GET_USER_PASSENGERS_FAIL.format( result.get('messages', '-'), self.retry_time)).flush() stay_second(self.retry_time) return self.get_user_passengers() def get_passengers_by_members(self, members): """ 获取格式化后的乘客信息 :param members: :return: [{ name: '项羽', type: 1, id_card: 0000000000000000000, type_text: '成人' }] """ self.get_user_passengers() results = [] for member in members: child_check = array_dict_find_by_key_value(results, 'name', member) if child_check: new_member = child_check.copy() new_member['type'] = UserType.CHILD new_member['type_text'] = dict_find_key_by_value( UserType.dicts, int(new_member['type'])) else: passenger = array_dict_find_by_key_value( self.passengers, 'passenger_name', member) if not passenger: UserLog.add_quick_log( UserLog.MESSAGE_USER_PASSENGERS_IS_INVALID.format( self.user_name, member)).flush() return False new_member = { 'name': passenger.get('passenger_name'), 'id_card': passenger.get('passenger_id_no'), 'id_card_type': passenger.get('passenger_id_type_code'), 'mobile': passenger.get('mobile_no'), 'type': passenger.get('passenger_type'), 'type_text': dict_find_key_by_value( UserType.dicts, int(passenger.get('passenger_type'))) } results.append(new_member) return results def request_init_dc_page(self): """ 请求下单页面 拿到 token :return: """ data = {'_json_att': ''} response = self.session.post(API_INITDC_URL, data) html = response.text token = re.search(r'var globalRepeatSubmitToken = \'(.+?)\'', html) form = re.search(r'var ticketInfoForPassengerForm *= *(\{.+\})', html) order = re.search(r'var orderRequestDTO *= *(\{.+\})', html) # 系统忙,请稍后重试 if html.find('系统忙,请稍后重试') != -1: OrderLog.add_quick_log(OrderLog.MESSAGE_REQUEST_INIT_DC_PAGE_FAIL ).flush() # 重试无用,直接跳过 return False try: self.global_repeat_submit_token = token.groups()[0] self.ticket_info_for_passenger_form = json.loads( form.groups()[0].replace("'", '"')) self.order_request_dto = json.loads(order.groups()[0].replace( "'", '"')) except: pass # TODO Error return True
class Query: """ 余票查询 """ jobs = [] query_jobs = [] # session = {} # 查询间隔 interval = {} cluster = None is_in_thread = False retry_time = 3 is_ready = False api_type = None # Query api url, Current know value leftTicket/queryX | leftTicket/queryZ def __init__(self): self.session = Request() self.cluster = Cluster() self.update_query_interval() self.update_query_jobs() self.get_query_api_type() def update_query_interval(self, auto=False): self.interval = init_interval_by_number(Config().QUERY_INTERVAL) if auto: jobs_do(self.jobs, 'update_interval') def update_query_jobs(self, auto=False): self.query_jobs = Config().QUERY_JOBS if auto: QueryLog.add_quick_log(QueryLog.MESSAGE_JOBS_DID_CHANGED).flush() self.refresh_jobs() if not Config().is_slave(): jobs_do(self.jobs, 'check_passengers') @classmethod def run(cls): self = cls() app_available_check() self.start() pass @classmethod def check_before_run(cls): self = cls() self.init_jobs() self.is_ready = True def start(self): # return # DEBUG QueryLog.init_data() stay_second(3) # 多线程 while True: if Config().QUERY_JOB_THREAD_ENABLED: # 多线程 if not self.is_in_thread: self.is_in_thread = True create_thread_and_run(jobs=self.jobs, callback_name='run', wait=Const.IS_TEST) if Const.IS_TEST: return stay_second(self.retry_time) else: if not self.jobs: break self.is_in_thread = False jobs_do(self.jobs, 'run') if Const.IS_TEST: return # while True: # app_available_check() # if Config().QUERY_JOB_THREAD_ENABLED: # 多线程 # create_thread_and_run(jobs=self.jobs, callback_name='run') # else: # for job in self.jobs: job.run() # if Const.IS_TEST: return # self.refresh_jobs() # 刷新任务 def refresh_jobs(self): """ 更新任务 :return: """ allow_jobs = [] for job in self.query_jobs: id = md5(job) job_ins = objects_find_object_by_key_value(self.jobs, 'id', id) # [1 ,2] if not job_ins: job_ins = self.init_job(job) if Config().QUERY_JOB_THREAD_ENABLED: # 多线程重新添加 create_thread_and_run(jobs=job_ins, callback_name='run', wait=Const.IS_TEST) allow_jobs.append(job_ins) for job in self.jobs: # 退出已删除 Job if job not in allow_jobs: job.destroy() QueryLog.print_init_jobs(jobs=self.jobs) def init_jobs(self): for job in self.query_jobs: self.init_job(job) QueryLog.print_init_jobs(jobs=self.jobs) def init_job(self, job): job = Job(info=job, query=self) self.jobs.append(job) return job @classmethod def wait_for_ready(cls): self = cls() if self.is_ready: return self stay_second(self.retry_time) return self.wait_for_ready() @classmethod def job_by_name(cls, name) -> Job: self = cls() for job in self.jobs: if job.job_name == name: return job return None @classmethod def job_by_name(cls, name) -> Job: self = cls() return objects_find_object_by_key_value(self.jobs, 'job_name', name) @classmethod def job_by_account_key(cls, account_key) -> Job: self = cls() return objects_find_object_by_key_value(self.jobs, 'account_key', account_key) @classmethod def get_query_api_type(cls): import re self = cls() if self.api_type: return self.api_type response = self.session.get(API_QUERY_INIT_PAGE) if response.status_code == 200: res = re.search(r'var CLeftTicketUrl = \'(.*)\';', response.text) try: self.api_type = res.group(1) except IndexError: pass return cls.get_query_api_type()
class OCR: """ 图片识别 """ session = None def __init__(self): self.session = Request() @classmethod def get_img_position(cls, img): """ 获取图像坐标 :param img_path: :return: """ self = cls() if Config().AUTO_CODE_PLATFORM == 'free_1': return self.get_image_by_free_site(img) elif Config().AUTO_CODE_PLATFORM == 'free_2': return self.get_image_by_local_verify(img) return self.get_img_position_by_ruokuai(img) def get_img_position_by_ruokuai(self, img): ruokuai_account = Config().AUTO_CODE_ACCOUNT soft_id = '119671' soft_key = '6839cbaca1f942f58d2760baba5ed987' rc = RKClient(ruokuai_account.get('user'), ruokuai_account.get('pwd'), soft_id, soft_key) result = rc.rk_create(img, 6113) if "Result" in result: return self.get_image_position_by_offset(list(result['Result'])) CommonLog.print_auto_code_fail( result.get("Error", CommonLog.MESSAGE_RESPONSE_EMPTY_ERROR)) return None def get_image_position_by_offset(self, offsets): positions = [] width = 75 height = 75 for offset in offsets: random_x = random.randint(-5, 5) random_y = random.randint(-5, 5) offset = int(offset) x = width * ((offset - 1) % 4 + 1) - width / 2 + random_x y = height * math.ceil(offset / 4) - height / 2 + random_y positions.append(int(x)) positions.append(int(y)) return positions def get_image_by_free_site(self, img): data = {'img': img} response = self.session.post(API_FREE_CODE_QCR_API, data=data) result = response.json() if result.get('msg') == 'success': pos = result.get('result') return self.get_image_position_by_offset(pos) CommonLog.print_auto_code_fail( CommonLog.MESSAGE_GET_RESPONSE_FROM_FREE_AUTO_CODE) return None def get_image_by_local_verify(self, img): with open('./tkcode.png', 'rb') as f: result = f.read() result = verify(result) print(result) return self.codexy(Ofset=result, is_raw_input=False) def codexy(self, Ofset=None, is_raw_input=True): """ 获取验证码 :return: str """ if is_raw_input: print(u""" ***************** | 1 | 2 | 3 | 4 | ***************** | 5 | 6 | 7 | 8 | ***************** """) print( u"验证码分为8个,对应上面数字,例如第一和第二张,输入1, 2 如果开启cdn查询的话,会冲掉提示,直接鼠标点击命令行获取焦点,输入即可,不要输入空格" ) print(u"如果是linux无图形界面,请使用自动打码,is_auto_code: True") print(u"如果没有弹出验证码,请手动双击根目录下的tkcode.png文件") Ofset = input(u"输入对应的验证码: ") if isinstance(Ofset, list): select = Ofset else: Ofset = Ofset.replace(",", ",") select = Ofset.split(',') post = [] offsetsX = 0 # 选择的答案的left值,通过浏览器点击8个小图的中点得到的,这样基本没问题 offsetsY = 0 # 选择的答案的top值 for ofset in select: if ofset == '1': offsetsY = 77 offsetsX = 40 elif ofset == '2': offsetsY = 77 offsetsX = 112 elif ofset == '3': offsetsY = 77 offsetsX = 184 elif ofset == '4': offsetsY = 77 offsetsX = 256 elif ofset == '5': offsetsY = 149 offsetsX = 40 elif ofset == '6': offsetsY = 149 offsetsX = 112 elif ofset == '7': offsetsY = 149 offsetsX = 184 elif ofset == '8': offsetsY = 149 offsetsX = 256 else: pass post.append(offsetsX) post.append(offsetsY) # randCode = str(post).replace(']', '').replace('[', '').replace("'", '').replace(' ', '') print(u"验证码识别坐标为{0}".format(post)) # return randCode return post