增加用户功能
This commit is contained in:
6
main.py
6
main.py
@@ -4,12 +4,14 @@ from threading import Thread
|
||||
|
||||
from py12306.log.query_log import QueryLog
|
||||
from py12306.query.query import Query
|
||||
from py12306.user.user import User
|
||||
|
||||
|
||||
def main():
|
||||
# Thread(target=Query.run).start() # 余票查询
|
||||
QueryLog.add_log('init')
|
||||
Query.run()
|
||||
# QueryLog.add_log('init')
|
||||
# Query.run()
|
||||
User.run()
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,12 @@ USER_HEARTBEAT_INTERVAL = 120
|
||||
# 多线程查询
|
||||
QUERY_JOB_THREAD_ENABLED = 0
|
||||
|
||||
# 打码平台账号
|
||||
AUTO_CODE_ACCOUNT = {
|
||||
'user': '',
|
||||
'pwd': ''
|
||||
}
|
||||
|
||||
SEAT_TYPES = {
|
||||
'商务座': 32,
|
||||
'一等座': 31,
|
||||
@@ -28,10 +34,12 @@ SEAT_TYPES = {
|
||||
'无座': 26,
|
||||
}
|
||||
|
||||
# Query
|
||||
QUERY_DATA_DIR = 'runtime/query'
|
||||
USER_DATA_DIR = 'runtime/user'
|
||||
PROJECT_DIR = path.dirname(path.dirname(path.abspath(__file__))) + '/'
|
||||
|
||||
# Query
|
||||
RUNTIME_DIR = PROJECT_DIR + 'runtime/'
|
||||
QUERY_DATA_DIR = RUNTIME_DIR + 'query/'
|
||||
USER_DATA_DIR = RUNTIME_DIR + 'user/'
|
||||
|
||||
STATION_FILE = 'data/stations.txt'
|
||||
CONFIG_FILE = 'env.py'
|
||||
|
||||
71
py12306/helpers/OCR.py
Normal file
71
py12306/helpers/OCR.py
Normal file
@@ -0,0 +1,71 @@
|
||||
from py12306 import config
|
||||
from py12306.log.common_log import CommonLog
|
||||
from py12306.vender.ruokuai.main import RKClient
|
||||
|
||||
|
||||
class OCR:
|
||||
"""
|
||||
图片识别
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def get_img_position(cls, img_path):
|
||||
"""
|
||||
获取图像坐标
|
||||
:param img_path:
|
||||
:return:
|
||||
"""
|
||||
self = cls()
|
||||
return self.get_img_position_by_ruokuai(img_path)
|
||||
pass
|
||||
|
||||
def get_img_position_by_ruokuai(self, img_path):
|
||||
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)
|
||||
im = open(img_path, 'rb').read()
|
||||
result = rc.rk_create(im, 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 = []
|
||||
for offset in offsets:
|
||||
if offset == '1':
|
||||
y = 46
|
||||
x = 42
|
||||
elif offset == '2':
|
||||
y = 46
|
||||
x = 105
|
||||
elif offset == '3':
|
||||
y = 45
|
||||
x = 184
|
||||
elif offset == '4':
|
||||
y = 48
|
||||
x = 256
|
||||
elif offset == '5':
|
||||
y = 36
|
||||
x = 117
|
||||
elif offset == '6':
|
||||
y = 112
|
||||
x = 115
|
||||
elif offset == '7':
|
||||
y = 114
|
||||
x = 181
|
||||
elif offset == '8':
|
||||
y = 111
|
||||
x = 252
|
||||
else:
|
||||
pass
|
||||
positions.append(x)
|
||||
positions.append(y)
|
||||
return positions
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
# code_result = AuthCode.get_auth_code()
|
||||
@@ -26,8 +26,22 @@ API_USER_CHECK = {
|
||||
"is_cdn": True,
|
||||
}
|
||||
|
||||
API_AUTH_CODE_DOWNLOAD = {
|
||||
'url': BASE_URL_OF_12306 + '/passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand&_={random}'
|
||||
}
|
||||
API_AUTH_CODE_CHECK = {
|
||||
'url': BASE_URL_OF_12306 + '/passport/captcha/captcha-check?answer={answer}&rand=sjrand&login_site=E&_={random}'
|
||||
}
|
||||
API_AUTH_UAMTK = {
|
||||
'url': BASE_URL_OF_12306 + '/passport/web/auth/uamtk'
|
||||
}
|
||||
API_AUTH_UAMAUTHCLIENT = {
|
||||
'url': BASE_URL_OF_12306 + '/otn/uamauthclient'
|
||||
}
|
||||
|
||||
|
||||
API_USER_INFO = {
|
||||
'url': BASE_URL_OF_12306 + '/otn/modifyUser/initQueryUserInfoApi'
|
||||
}
|
||||
|
||||
urls = {
|
||||
"auth": { # 登录接口
|
||||
|
||||
69
py12306/helpers/auth_code.py
Normal file
69
py12306/helpers/auth_code.py
Normal file
@@ -0,0 +1,69 @@
|
||||
import random
|
||||
import time
|
||||
|
||||
from requests.exceptions import SSLError
|
||||
|
||||
from py12306.helpers.OCR import OCR
|
||||
from py12306.helpers.api import API_AUTH_CODE_DOWNLOAD, API_AUTH_CODE_CHECK
|
||||
from py12306.helpers.request import Request
|
||||
from py12306.helpers.func import *
|
||||
from py12306.log.common_log import CommonLog
|
||||
from py12306.log.user_log import UserLog
|
||||
|
||||
|
||||
class AuthCode:
|
||||
"""
|
||||
验证码类
|
||||
"""
|
||||
session = None
|
||||
data_path = config.RUNTIME_DIR
|
||||
retry_time = 1
|
||||
|
||||
def __init__(self, session):
|
||||
self.session = session
|
||||
|
||||
@classmethod
|
||||
def get_auth_code(cls, session):
|
||||
self = cls(session)
|
||||
img_path = self.download_code()
|
||||
position = OCR.get_img_position(img_path)
|
||||
answer = ','.join(map(str, position))
|
||||
if not self.check_code(answer):
|
||||
time.sleep(self.retry_time)
|
||||
return self.get_auth_code(session)
|
||||
return position
|
||||
|
||||
def download_code(self):
|
||||
url = API_AUTH_CODE_DOWNLOAD.get('url').format(random=random.random())
|
||||
code_path = self.data_path + 'code.png'
|
||||
try:
|
||||
UserLog.add_quick_log(UserLog.MESSAGE_DOWNLAODING_THE_CODE).flush()
|
||||
response = self.session.save_to_file(url, code_path) # TODO 返回错误情况
|
||||
except SSLError as e:
|
||||
UserLog.add_quick_log(
|
||||
UserLog.MESSAGE_DOWNLAOD_AUTH_CODE_FAIL.format(e, self.retry_time)).flush()
|
||||
time.sleep(self.retry_time)
|
||||
return self.download_code()
|
||||
return code_path
|
||||
|
||||
def check_code(self, answer):
|
||||
"""
|
||||
校验验证码
|
||||
:return:
|
||||
"""
|
||||
url = API_AUTH_CODE_CHECK.get('url').format(answer=answer, random=random.random())
|
||||
response = self.session.get(url)
|
||||
result = response.json()
|
||||
if result.get('result_code') == '4':
|
||||
UserLog.add_quick_log(UserLog.MESSAGE_CODE_AUTH_SUCCESS).flush()
|
||||
return True
|
||||
else:
|
||||
UserLog.add_quick_log(
|
||||
UserLog.MESSAGE_CODE_AUTH_FAIL.format(result.get('result_message'), self.retry_time)).flush()
|
||||
self.session.cookies.clear_session_cookies()
|
||||
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
code_result = AuthCode.get_auth_code()
|
||||
@@ -1,3 +1,4 @@
|
||||
import datetime
|
||||
import random
|
||||
import threading
|
||||
from time import sleep
|
||||
@@ -65,7 +66,11 @@ def stay_second(second):
|
||||
def is_main_thread():
|
||||
return threading.current_thread() == threading.main_thread()
|
||||
|
||||
|
||||
def current_thread_id():
|
||||
return threading.current_thread().ident
|
||||
|
||||
|
||||
def time_now():
|
||||
return datetime.datetime.now()
|
||||
# def test:
|
||||
|
||||
20
py12306/helpers/request.py
Normal file
20
py12306/helpers/request.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from requests_html import HTMLSession
|
||||
|
||||
|
||||
class Request(HTMLSession):
|
||||
"""
|
||||
请求处理类
|
||||
"""
|
||||
# session = {}
|
||||
|
||||
# def __init__(self, mock_browser=True, session=None):
|
||||
# super().__init__(mock_browser=mock_browser)
|
||||
# self.session = session if session else HTMLSession()
|
||||
pass
|
||||
|
||||
def save_to_file(self, url, path):
|
||||
response = self.get(url, stream=True)
|
||||
with open(path, 'wb') as f:
|
||||
for chunk in response.iter_content(chunk_size=1024):
|
||||
f.write(chunk)
|
||||
return response
|
||||
20
py12306/log/common_log.py
Normal file
20
py12306/log/common_log.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from py12306.log.base import BaseLog
|
||||
from py12306.helpers.func import *
|
||||
|
||||
|
||||
@singleton
|
||||
class CommonLog(BaseLog):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.init_data()
|
||||
|
||||
def init_data(self):
|
||||
print('Common Log 初始化')
|
||||
|
||||
@classmethod
|
||||
def print_auto_code_fail(cls, reason):
|
||||
self = cls()
|
||||
self.add_quick_log('打码失败: 错误原因 {reason}'.format(reason=reason))
|
||||
self.flush()
|
||||
return self
|
||||
@@ -12,7 +12,7 @@ class QueryLog(BaseLog):
|
||||
'query_count': 1,
|
||||
'last_time': '',
|
||||
}
|
||||
data_path = config.QUERY_DATA_DIR + '/status.json'
|
||||
data_path = config.QUERY_DATA_DIR + 'status.json'
|
||||
|
||||
LOG_INIT_JOBS = ''
|
||||
|
||||
|
||||
@@ -4,6 +4,15 @@ from py12306.helpers.func import *
|
||||
|
||||
@singleton
|
||||
class UserLog(BaseLog):
|
||||
MESSAGE_DOWNLAOD_AUTH_CODE_FAIL = '验证码下载失败 错误原因: {} {} 秒后重试'
|
||||
MESSAGE_DOWNLAODING_THE_CODE = '正在下载验证码...'
|
||||
MESSAGE_CODE_AUTH_FAIL = '验证码验证失败 错误原因: {} {} 秒后重试'
|
||||
MESSAGE_CODE_AUTH_SUCCESS = '验证码验证成功 开始登录...'
|
||||
MESSAGE_LOGIN_FAIL = '登录失败 错误原因: {}'
|
||||
MESSAGE_LOADED_USER = '正在尝试恢复用户: {}'
|
||||
MESSAGE_LOADED_USER_SUCCESS = '用户恢复成功: {}'
|
||||
MESSAGE_LOADED_USER_BUT_EXPIRED = '用户状态已过期,正在重新登录'
|
||||
MESSAGE_USER_HEARTBEAT_NORMAL = '用户 {} 心跳正常,下次检测 {} 秒后'
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
@@ -20,6 +29,19 @@ class UserLog(BaseLog):
|
||||
"""
|
||||
self = cls()
|
||||
self.add_log('================== 发现 {} 个用户 =================='.format(len(users)))
|
||||
self.add_log('')
|
||||
self.flush()
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def print_welcome_user(cls, user):
|
||||
self = cls()
|
||||
self.add_log('# 欢迎回来,{} #'.format(user.get_name()))
|
||||
self.flush()
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def print_start_login(cls, user):
|
||||
self = cls()
|
||||
self.add_log('正在登录用户 {}'.format(user.user_name))
|
||||
self.flush()
|
||||
return self
|
||||
|
||||
@@ -38,8 +38,7 @@ class Query:
|
||||
thread = threading.Thread(target=job.run)
|
||||
thread.start()
|
||||
threads.append(thread)
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
for thread in threads: thread.join()
|
||||
else:
|
||||
for job in self.jobs:
|
||||
job.run()
|
||||
|
||||
@@ -1,68 +1,178 @@
|
||||
import pickle
|
||||
from os import path
|
||||
|
||||
from requests_html import HTMLSession
|
||||
|
||||
from py12306.helpers.api import API_USER_CHECK, API_BASE_LOGIN
|
||||
from py12306.helpers.api import API_USER_CHECK, API_BASE_LOGIN, API_AUTH_UAMTK, API_AUTH_UAMAUTHCLIENT, API_USER_INFO
|
||||
from py12306.helpers.auth_code import AuthCode
|
||||
from py12306.helpers.func import *
|
||||
from py12306.helpers.request import Request
|
||||
from py12306.log.user_log import UserLog
|
||||
|
||||
|
||||
class UserJob:
|
||||
heartbeat = 60 * 2
|
||||
heartbeat = 60 * 2 # 心跳保持时长
|
||||
heartbeat_interval = 5
|
||||
key = None
|
||||
user_name: ''
|
||||
password: ''
|
||||
user: None
|
||||
user_name = ''
|
||||
password = ''
|
||||
user = None
|
||||
info = {} # 用户信息
|
||||
last_heartbeat = None
|
||||
|
||||
def __init__(self, info, user):
|
||||
self.session = HTMLSession()
|
||||
# cookie TODO
|
||||
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
|
||||
# load user
|
||||
self.load_user()
|
||||
|
||||
def run(self):
|
||||
self.start()
|
||||
|
||||
def start(self):
|
||||
self.check_heartbeat()
|
||||
"""
|
||||
检测心跳
|
||||
:return:
|
||||
"""
|
||||
while True:
|
||||
self.check_heartbeat()
|
||||
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()
|
||||
pass
|
||||
|
||||
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 self.get_user_cookie()
|
||||
return not path.exists(self.get_cookie_path())
|
||||
|
||||
def handle_login(self):
|
||||
self.base_login()
|
||||
UserLog.print_start_login(user=self)
|
||||
self.login()
|
||||
|
||||
def base_login(self):
|
||||
def login(self):
|
||||
"""
|
||||
获取验证码结果
|
||||
:return:
|
||||
: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('status')
|
||||
is_login = response.json().get('data').get('flag', False)
|
||||
if is_login:
|
||||
self.save_user()
|
||||
return is_login
|
||||
|
||||
def get_user_cookie(self):
|
||||
path = self.get_cookie_path()
|
||||
if path.exists(path):
|
||||
return open(path, encoding='utf-8').read()
|
||||
return None
|
||||
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'
|
||||
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')
|
||||
if user_data.get('userDTO') and user_data['userDTO'].get('loginUserDTO'):
|
||||
user_data = user_data['userDTO']['loginUserDTO']
|
||||
self.update_user_info({**user_data, **{'user_name': user_data['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
|
||||
|
||||
@@ -19,10 +19,16 @@ class User:
|
||||
|
||||
def start(self):
|
||||
self.init_users()
|
||||
UserLog.print_init_users(jobs=self.users)
|
||||
UserLog.print_init_users(users=self.users)
|
||||
while True:
|
||||
# 多线程维护用户
|
||||
threads = []
|
||||
for user in self.users:
|
||||
user.run()
|
||||
thread = threading.Thread(target=user.run)
|
||||
thread.start()
|
||||
threads.append(thread)
|
||||
# user.run()
|
||||
for thread in threads: thread.join()
|
||||
|
||||
def init_users(self):
|
||||
accounts = config.USER_ACCOUNTS
|
||||
|
||||
54
py12306/vender/ruokuai/main.py
Executable file
54
py12306/vender/ruokuai/main.py
Executable file
@@ -0,0 +1,54 @@
|
||||
import requests
|
||||
from hashlib import md5
|
||||
|
||||
|
||||
class RKClient(object):
|
||||
|
||||
def __init__(self, username, password, soft_id, soft_key):
|
||||
self.username = username
|
||||
self.password = md5(password.encode('utf-8')).hexdigest()
|
||||
self.soft_id = soft_id
|
||||
self.soft_key = soft_key
|
||||
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)
|
||||
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 = RKClient('username', 'password', 'soft_id', 'soft_key')
|
||||
im = open('a.jpg', 'rb').read()
|
||||
# print rc.rk_create(im, 3040)
|
||||
|
||||
Reference in New Issue
Block a user