完善查询功能
This commit is contained in:
@@ -7,6 +7,9 @@ USER_ACCOUNTS = []
|
||||
|
||||
# 查询任务
|
||||
QUERY_JOBS = []
|
||||
# 查询间隔
|
||||
QUERY_INTERVAL = 1
|
||||
|
||||
|
||||
SEAT_TYPES = {
|
||||
'商务座': 32,
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import random
|
||||
from time import sleep
|
||||
|
||||
from py12306 import config
|
||||
import functools
|
||||
|
||||
@@ -36,4 +39,25 @@ def get_seat_name_by_number(number):
|
||||
return [k for k, v in config.SEAT_TYPES.items() if v == number].pop()
|
||||
|
||||
|
||||
# 初始化间隔
|
||||
def init_interval_by_number(number):
|
||||
if isinstance(number, dict):
|
||||
min = float(number.get('min'))
|
||||
max = float(number.get('max'))
|
||||
else:
|
||||
min = number / 2
|
||||
max = number
|
||||
return {
|
||||
'min': min,
|
||||
'max': max
|
||||
}
|
||||
|
||||
|
||||
def get_interval_num(interval, decimal=2):
|
||||
return round(random.uniform(interval.get('min'), interval.get('max')), decimal)
|
||||
|
||||
|
||||
def stay_second(second):
|
||||
sleep(second)
|
||||
|
||||
# def test:
|
||||
|
||||
@@ -3,6 +3,7 @@ from py12306.helpers.func import *
|
||||
|
||||
class BaseLog:
|
||||
logs = []
|
||||
quick_log = []
|
||||
|
||||
@classmethod
|
||||
def add_log(cls, content):
|
||||
@@ -11,8 +12,19 @@ class BaseLog:
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def flush(cls):
|
||||
def flush(cls, sep='\n', end='\n', file=None):
|
||||
self = cls()
|
||||
for i in self.logs:
|
||||
print(i)
|
||||
logs = self.quick_log if self.quick_log else self.logs
|
||||
# for i in logs:
|
||||
print(*logs, sep=sep, end=end, file=file)
|
||||
if self.quick_log:
|
||||
self.quick_log = []
|
||||
else:
|
||||
self.logs = []
|
||||
# print(self.logs)
|
||||
|
||||
@classmethod
|
||||
def add_quick_log(cls, content):
|
||||
self = cls()
|
||||
self.quick_log.append(content)
|
||||
return self
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import datetime
|
||||
import json
|
||||
from os import path
|
||||
from py12306.log.base import BaseLog
|
||||
@@ -7,12 +8,17 @@ from py12306.helpers.func import *
|
||||
@singleton
|
||||
class QueryLog(BaseLog):
|
||||
data = {
|
||||
'query_count': 0,
|
||||
'query_count': 1,
|
||||
'last_time': '',
|
||||
}
|
||||
data_path = config.QUERY_DATA_DIR + '/status.json'
|
||||
|
||||
LOG_INIT_JOBS = ''
|
||||
|
||||
MESSAGE_GIVE_UP_CHANCE_CAUSE_TICKET_NUM_LESS_THAN_SPECIFIED = '余票数小于乘车人数,放弃此次提交机会'
|
||||
MESSAGE_QUERY_LOG_OF_EVERY_TRAIN = '{}-{}'
|
||||
MESSAGE_QUERY_START_BY_DATE = '出发日期 {}: {} - {}'
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.init_data()
|
||||
@@ -20,12 +26,12 @@ class QueryLog(BaseLog):
|
||||
def init_data(self):
|
||||
# 获取上次记录
|
||||
print('Query Log 初始化')
|
||||
file_path = config.QUERY_DATA_DIR + '/status.json'
|
||||
if path.exists(file_path):
|
||||
result = open(file_path, encoding='utf-8').read()
|
||||
if path.exists(self.data_path):
|
||||
result = open(self.data_path, encoding='utf-8').read()
|
||||
if result:
|
||||
result = json.loads(result)
|
||||
self.data = {**self.data, **result}
|
||||
self.print_data_restored()
|
||||
|
||||
@classmethod
|
||||
def print_init_jobs(cls, jobs):
|
||||
@@ -34,7 +40,7 @@ class QueryLog(BaseLog):
|
||||
输出初始化信息
|
||||
:return:
|
||||
"""
|
||||
self.add_log('# 发现任务 {} 条 #'.format(len(jobs)))
|
||||
self.add_log('# 发现 {} 个任务 #'.format(len(jobs)))
|
||||
index = 1
|
||||
for job in jobs:
|
||||
self.add_log('================== 任务 {} =================='.format(index))
|
||||
@@ -45,5 +51,69 @@ class QueryLog(BaseLog):
|
||||
self.add_log('筛选车次:{}'.format(','.join(job.allow_train_numbers)))
|
||||
# 乘车日期:['2019-01-24', '2019-01-25', '2019-01-26', '2019-01-27']
|
||||
index += 1
|
||||
|
||||
self.add_log('')
|
||||
self.flush()
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def print_ticket_num_less_than_specified(cls, rest_num, job):
|
||||
self = cls()
|
||||
self.add_quick_log(
|
||||
'余票数小于乘车人数,当前余票数: {rest_num}, 实际人数 {actual_num}, 删减人车人数到: {take_num}'.format(rest_num=rest_num,
|
||||
actual_num=job.member_num,
|
||||
take=job.member_num_take))
|
||||
self.flush()
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def print_ticket_seat_available(cls, left_date, train_number, seat_type, rest_num):
|
||||
self = cls()
|
||||
self.add_quick_log(
|
||||
'查询到座位可用 出发时间 {left_date} 车次 {train_number} 座位类型 {seat_type} 余票数量 {rest_num}'.format(left_date=left_date,
|
||||
train_number=train_number,
|
||||
seat_type=seat_type,
|
||||
rest_num=rest_num))
|
||||
self.flush()
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def print_query_error(cls, reason, code=None):
|
||||
self = cls()
|
||||
self.add_quick_log('查询余票请求失败')
|
||||
if code:
|
||||
self.add_quick_log('状态码{} '.format(code))
|
||||
if reason:
|
||||
self.add_quick_log('错误原因{} '.format(reason))
|
||||
self.flush(sep='\t')
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def print_job_start(cls):
|
||||
self = cls()
|
||||
self.add_quick_log('=== 正在进行第 {query_count} 次查询 === {time}'.format(query_count=self.data.get('query_count'), time=datetime.datetime.now()))
|
||||
self.refresh_data()
|
||||
self.flush()
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def add_stay_log(cls, second):
|
||||
self = cls()
|
||||
self.add_log('安全停留 {}'.format(second))
|
||||
return self
|
||||
|
||||
def print_data_restored(self):
|
||||
self.add_quick_log('============================================================')
|
||||
self.add_quick_log('|=== 查询记录恢复成功 上次查询 {last_date} ===|'.format(last_date=self.data.get('last_time')))
|
||||
self.add_quick_log('============================================================')
|
||||
self.add_log('')
|
||||
self.flush()
|
||||
return self
|
||||
|
||||
def refresh_data(self):
|
||||
self.data['query_count'] += 1
|
||||
self.data['last_time'] = str(datetime.datetime.now())
|
||||
self.save_data()
|
||||
|
||||
def save_data(self):
|
||||
with open(self.data_path, 'w') as file:
|
||||
file.write(json.dumps(self.data))
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
from py12306.helpers.api import LEFT_TICKETS
|
||||
from py12306.helpers.station import Station
|
||||
from py12306.log.query_log import QueryLog
|
||||
from py12306.helpers.func import *
|
||||
|
||||
|
||||
class Job:
|
||||
@@ -15,10 +18,19 @@ class Job:
|
||||
allow_seats = []
|
||||
allow_train_numbers = []
|
||||
members = []
|
||||
member_num = []
|
||||
member_num = 0
|
||||
member_num_take = 0 # 最终提交的人数
|
||||
allow_less_member = False
|
||||
|
||||
interval = {}
|
||||
|
||||
query = None
|
||||
INDEX_TICKET_NUM = 11
|
||||
INDEX_TRAIN_NUMBER = 3
|
||||
INDEX_LEFT_DATE = 13
|
||||
|
||||
|
||||
def __init__(self, info):
|
||||
def __init__(self, info, query):
|
||||
self.left_dates = info.get('left_dates')
|
||||
self.left_station = info.get('stations').get('left')
|
||||
self.arrive_station = info.get('stations').get('arrive')
|
||||
@@ -29,3 +41,110 @@ class Job:
|
||||
self.allow_train_numbers = info.get('train_numbers')
|
||||
self.members = info.get('members')
|
||||
self.member_num = len(self.members)
|
||||
self.member_num_take = self.member_num
|
||||
self.allow_less_member = bool(info.get('allow_less_member'))
|
||||
|
||||
self.interval = query.interval
|
||||
self.query = query
|
||||
|
||||
def run(self):
|
||||
self.start()
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
处理单个任务
|
||||
根据日期循环查询
|
||||
|
||||
展示处理时间
|
||||
:param job:
|
||||
:return:
|
||||
"""
|
||||
QueryLog.print_job_start()
|
||||
for date in self.left_dates:
|
||||
response = self.query_by_date(date)
|
||||
self.handle_response(response)
|
||||
self.safe_stay()
|
||||
QueryLog.flush(sep='\t\t')
|
||||
QueryLog.add_quick_log('').flush()
|
||||
|
||||
def query_by_date(self, date):
|
||||
"""
|
||||
通过日期进行查询
|
||||
:return:
|
||||
"""
|
||||
QueryLog.add_log(QueryLog.MESSAGE_QUERY_START_BY_DATE.format(date, self.left_station, self.arrive_station))
|
||||
url = LEFT_TICKETS.get('url').format(left_date=date, left_station=self.left_station_code,
|
||||
arrive_station=self.arrive_station_code, type='leftTicket/queryZ')
|
||||
|
||||
return self.query.session.get(url)
|
||||
|
||||
def handle_response(self, response):
|
||||
"""
|
||||
错误判断
|
||||
余票判断
|
||||
小黑屋判断
|
||||
座位判断
|
||||
乘车人判断
|
||||
:param result:
|
||||
:return:
|
||||
"""
|
||||
results = self.get_results(response)
|
||||
if not results:
|
||||
return False
|
||||
for result in results:
|
||||
ticket_info = result.split('|')
|
||||
if not self.is_trains_number_valid(ticket_info): # 车次是否有效
|
||||
continue
|
||||
QueryLog.add_log(QueryLog.MESSAGE_QUERY_LOG_OF_EVERY_TRAIN.format(ticket_info[self.INDEX_TRAIN_NUMBER], ticket_info[self.INDEX_TICKET_NUM]))
|
||||
if not self.is_has_ticket(ticket_info):
|
||||
continue
|
||||
allow_seats = self.allow_seats if self.allow_seats else list(config.SEAT_TYPES.values()) # 未设置 则所有可用
|
||||
for seat in allow_seats: # 检查座位是否有票
|
||||
ticket_of_seat = ticket_info[get_seat_number_by_name(seat)]
|
||||
if not self.is_has_ticket_by_seat(ticket_of_seat): # 座位是否有效
|
||||
continue
|
||||
QueryLog.print_ticket_seat_available(left_date=ticket_info[self.INDEX_LEFT_DATE], train_number=ticket_info[self.INDEX_TRAIN_NUMBER], seat_type=seat, rest_num=ticket_of_seat)
|
||||
if not self.is_member_number_valid(ticket_of_seat): # 乘车人数是否有效
|
||||
if self.allow_less_member:
|
||||
self.member_num_take = int(ticket_of_seat)
|
||||
QueryLog.print_ticket_num_less_than_specified(ticket_of_seat, self)
|
||||
else:
|
||||
QueryLog.add_quick_log( QueryLog.MESSAGE_GIVE_UP_CHANCE_CAUSE_TICKET_NUM_LESS_THAN_SPECIFIED).flush()
|
||||
continue
|
||||
# 检查完成 开始提交订单
|
||||
print('检查完成 开始提交订单')
|
||||
|
||||
def get_results(self, response):
|
||||
"""
|
||||
解析查询返回结果
|
||||
:param response:
|
||||
:return:
|
||||
"""
|
||||
if response.status_code != 200:
|
||||
QueryLog.print_query_error(response.reason, response.status_code)
|
||||
try:
|
||||
result_data = response.json().get('data', {})
|
||||
result = result_data.get('result', [])
|
||||
except:
|
||||
pass # TODO
|
||||
return result if result else False
|
||||
|
||||
def is_has_ticket(self, ticket_info):
|
||||
return ticket_info[11] == 'Y' and ticket_info[1] == '预订'
|
||||
|
||||
def is_has_ticket_by_seat(self, seat):
|
||||
return seat != '' and seat != '无' and seat != '*'
|
||||
|
||||
def is_trains_number_valid(self, ticket_info):
|
||||
if self.allow_train_numbers:
|
||||
return ticket_info[3] in self.allow_train_numbers
|
||||
return True
|
||||
|
||||
def is_member_number_valid(self, seat):
|
||||
return seat == '有' or self.member_num <= int(seat)
|
||||
|
||||
|
||||
def safe_stay(self):
|
||||
interval = get_interval_num(self.interval)
|
||||
QueryLog.add_stay_log(interval)
|
||||
stay_second(interval)
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
from requests_html import HTMLSession
|
||||
|
||||
import py12306.config as config
|
||||
from py12306.helpers.api import LEFT_TICKETS
|
||||
from py12306.helpers.func import *
|
||||
from py12306.helpers.station import Station
|
||||
from py12306.log.query_log import QueryLog
|
||||
from py12306.query.job import Job
|
||||
|
||||
@@ -16,7 +13,11 @@ class Query:
|
||||
jobs = []
|
||||
session = {}
|
||||
|
||||
# 查询间隔
|
||||
interval = {}
|
||||
|
||||
def __init__(self):
|
||||
self.interval = init_interval_by_number(config.QUERY_INTERVAL)
|
||||
self.session = HTMLSession()
|
||||
|
||||
@classmethod
|
||||
@@ -27,52 +28,13 @@ class Query:
|
||||
|
||||
def start(self):
|
||||
self.init_jobs()
|
||||
QueryLog.print_init_jobs(jobs=self.jobs).flush()
|
||||
for job in self.jobs:
|
||||
self.handle_single_job(job)
|
||||
QueryLog.print_init_jobs(jobs=self.jobs)
|
||||
while True:
|
||||
for job in self.jobs:
|
||||
job.run()
|
||||
|
||||
def init_jobs(self):
|
||||
jobs = config.QUERY_JOBS
|
||||
for job in jobs:
|
||||
job = Job(info=job)
|
||||
job = Job(info=job, query=self)
|
||||
self.jobs.append(job)
|
||||
|
||||
def handle_single_job(self, job):
|
||||
"""
|
||||
处理单个任务
|
||||
根据日期循环查询
|
||||
|
||||
展示处理时间
|
||||
:param job:
|
||||
:return:
|
||||
"""
|
||||
for date in job.left_dates:
|
||||
result = self.query_by_job_and_date(job, date)
|
||||
self.handle_single_result(result)
|
||||
|
||||
station = Station.get_station_by_name('广州')
|
||||
print(station)
|
||||
pass
|
||||
|
||||
def query_by_job_and_date(self, job, date):
|
||||
"""
|
||||
通过日期进行查询
|
||||
:return:
|
||||
"""
|
||||
QueryLog.add_log('正在查询 {}, {} - {}'.format(date, job.left_station, job.arrive_station))
|
||||
url = LEFT_TICKETS.get('url').format(left_date=date, left_station=job.left_station_code,
|
||||
arrive_station=job.arrive_station_code, type='leftTicket/queryZ')
|
||||
|
||||
return self.session.get(url)
|
||||
|
||||
def handle_single_result(self, result):
|
||||
"""
|
||||
错误判断
|
||||
余票判断
|
||||
小黑屋判断
|
||||
座位判断
|
||||
乘车人判断
|
||||
:param result:
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
22
py12306/user/user.py
Normal file
22
py12306/user/user.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from py12306.helpers.func import *
|
||||
|
||||
|
||||
@singleton
|
||||
class User:
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
初始化用户
|
||||
恢复
|
||||
登录
|
||||
"""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def run(cls):
|
||||
self = cls()
|
||||
self.start()
|
||||
pass
|
||||
|
||||
def start(self):
|
||||
pass
|
||||
Reference in New Issue
Block a user