增加用户功能

This commit is contained in:
Jalin
2019-01-06 22:43:30 +08:00
parent 0553dc936b
commit 3446bfc9c2
14 changed files with 434 additions and 34 deletions

View File

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

View File

@@ -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
View 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()

View File

@@ -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": { # 登录接口

View 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()

View File

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

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

View File

@@ -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 = ''

View File

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

View File

@@ -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()

View File

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

View File

@@ -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
View 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)