Add verify of query result

This commit is contained in:
Jalin
2019-05-16 13:17:40 +08:00
parent 8b795d3217
commit ad83fd261a
4 changed files with 174 additions and 10 deletions

View File

@@ -4,15 +4,32 @@ from typing import List
from py12306.app.app import Logger
from py12306.lib.api import API_QUERY_INIT_PAGE, API_LEFT_TICKETS
from py12306.lib.exceptions import RetryException
from py12306.lib.func import retry
from py12306.lib.helper import DataHelper
from py12306.lib.func import retry, number_of_time_period
from py12306.lib.helper import DataHelper, TrainSeat
from py12306.lib.request import Request
class TicketSeatData(DataHelper):
name: str
num: str
raw: str
class QueryTicketData(DataHelper):
left_date: str
left_station: str
arrive_station: str
left_periods: List[tuple] = []
allow_train_numbers: List[str] = []
execpt_train_numbers: List[str] = []
allow_seats: List[str] = []
available_seat: TicketSeatData
members: list
member_num: int
less_member: bool = False
def _after(self):
self.member_num = len(self.members)
class TicketData(DataHelper):
@@ -76,11 +93,105 @@ class QueryTicket:
if not result:
Logger.error('车票查询失败, %s' % resp.reason)
tickets = QueryParser.parse_ticket(result)
for ticket in tickets:
self.is_ticket_valid(ticket, data)
if not data:
continue
# 验证完成,准备下单
Logger.info('[ 查询到座位可用 出发时间 {left_date} 车次 {train_number} 座位类型 {seat_type} 余票数量 {rest_num} ]'.format(
left_date=data.left_date, train_number=ticket.train_number, seat_type=data.available_seat.name,
rest_num=data.available_seat.raw))
def is_ticket_valid(self, ticket: TicketData, query: QueryTicketData) -> bool:
"""
验证 Ticket 信息是否可用
) 出发日期验证
) 车票数量验证
) 时间点验证(00:00 - 24:00)
) 车次验证
) 座位验证
) 乘车人数验证
:param ticket: 车票信息
:param query: 查询条件
:return:
"""
if not self.verify_ticket_num(ticket):
return False
if not self.verify_period(ticket.left_time, query.left_periods):
return False
if query.allow_train_numbers and ticket.train_no.upper() not in map(str.upper, query.allow_train_numbers):
return False
if query.execpt_train_numbers and ticket.train_no.upper() in map(str.upper, query.execpt_train_numbers):
return False
if not self.verify_seat(ticket, query):
return False
if not self.verify_member_count(query):
return False
return True
@staticmethod
def verify_period(period: str, available_periods: List[tuple]):
if not available_periods:
return True
period = number_of_time_period(period)
for available_period in available_periods:
if period < number_of_time_period(available_period[0]) or period > number_of_time_period(
available_period[1]):
return False
return True
@staticmethod
def verify_ticket_num(ticket: TicketData):
return ticket.ticket_num == 'Y' and ticket.order_text == '预订'
def verify_seat(self, ticket: TicketData, query: QueryTicketData) -> bool:
"""
检查座位是否可用
TODO 小黑屋判断 通过 车次 + 座位
:param ticket:
:param query:
:return:
"""
allow_seats = query.allow_seats
for seat in allow_seats:
seat_num = TrainSeat.types[seat]
raw = ticket.get_origin()[seat_num]
if self.verify_seat_text(raw):
query.available_seat = TicketSeatData({
'name': seat,
'num': seat_num,
'raw': raw
})
return True
return False
@staticmethod
def verify_seat_text(seat: str) -> bool:
return seat != '' and seat != '' and seat != '*'
@staticmethod
def verify_member_count(query: QueryTicketData) -> bool:
seat = query.available_seat
if not (seat.raw == '' or query.member_num <= int(seat.raw)):
rest_num = int(seat.raw)
if query.less_member:
query.member_num = rest_num
Logger.info(
'余票数小于乘车人数,当前余票数: %d, 实际人数 %d, 删减人车人数到: %d' % (rest_num, query.member_num, query.member_num))
else:
Logger.info('余票数 %d 小于乘车人数 %d,放弃此次提交机会' % (rest_num, query.member_num))
return False
return True
class QueryParser:
@classmethod
def parse_ticket(cls, items: dict) -> List[TicketData]:
def parse_ticket(cls, items: List[dict]) -> List[TicketData]:
res = []
for item in items:
info = item.split('|')

View File

@@ -72,3 +72,12 @@ def retry(num: int = 3):
return wrapper
return decorator
def number_of_time_period(period: str) -> int:
"""
Example: 23:00 -> 2300
:param period:
:return:
"""
return int(period.replace(':', ''))

View File

@@ -41,6 +41,8 @@ class DataHelper:
self.__dict__[self.__mappers[str(key)]] = val
elif key in self.__annotations__:
self.__dict__[key] = val
if getattr(self, '_after', None):
self._after()
def __generate_mappers(self):
for key, val in self.__annotations__.items():
@@ -58,3 +60,11 @@ class DataHelper:
except (KeyError, AttributeError):
pass
def get_origin(self) -> dict:
return self.__origin
class TrainSeat:
types = {'特等座': 25, '商务座': 32, '一等座': 31, '二等座': 30, '软卧': 23, '硬卧': 28, '硬座': 29, '无座': 26, }
order_types = {'特等座': 'P', '商务座': 9, '一等座': 'M', '二等座': 'O', '软卧': 4, '硬卧': 3, '硬座': 1, '无座': 1}

View File

@@ -1,25 +1,59 @@
from unittest import TestCase
from tests.helper import BaseTest
from py12306.app.query import QueryTicket, QueryParser
from py12306.app.query import *
class TestQueryTicket(BaseTest):
task = {
'name': 'admin',
}
query_dict = {
'left_date': '2019-05-18',
'left_station': 'BJP',
'arrive_station': 'LZJ',
'allow_seats': ['二等座'],
'members': ['test']
}
query: QueryTicketData
ticket_str = 'iV6uPpzX3CcwqhHe4yzrJHp9hFVCouaXtS01wlUB8f%2BuA%2BKD%2FTV5KLu37w1aKHO2zlAlwMDa%2FDYY%0A2xykUxU964zvkfI3qZZ6uGEKWi0tXCT8fhkQTVvnRI43%2FAinVozab2W1Cq%2FMzJtGBv3D1Q3CscAj%0ANA1XmfNzd6Carglhzvyyy63MkbLIRvxrngx9F01W9jhKXnQupQNTOM3Pw4UIxbesBWkmQfYNj%2Fj%2F%0A3mU33kluoI5vbGVsm115Ec%2BS29KPeaM%2B4%2F2h2UZsiCb%2F5hKfew8Hijodr2VuFftbkge1meSTRRvz%0A|预订|240000G42704|G427|BXP|LAJ|BXP|LAJ|06:21|13:44|07:23|Y|nhsXhn1BbGmb4MI%2BEto43zoslKFQlIY8c356nXAHAEk9Zb2G|20190518|3|P3|01|05|0|0|||||||||||有|无|5||O090M0|O9M|0|0|null'
ticket: TicketData
def setUp(self) -> None:
super().setUp()
self.ticket = QueryParser.parse_ticket([self.ticket_str])[0]
self.query = QueryTicketData(self.query_dict)
def test_get_query_api_type(self):
res = QueryTicket().get_query_api_type()
self.assertEqual('leftTicket/query', res)
def test_get_ticket(self):
data = {
'left_date': '2019-05-18',
'left_station': 'BJP',
'arrive_station': 'LZJ'
}
res = QueryTicket().get_ticket(data)
res = QueryTicket().get_ticket(self.query_dict)
def test_is_ticket_valid(self):
res = QueryTicket().is_ticket_valid(self.ticket, self.query)
self.assertEqual(res, True)
def test_verify_period(self):
self.query.left_periods = [('08:00', '16:00')]
res = QueryTicket.verify_period('12:00', self.query.left_periods)
self.assertEqual(res, True)
res = QueryTicket.verify_period('16:00', self.query.left_periods)
self.assertEqual(res, True)
res = QueryTicket.verify_period('16:01', self.query.left_periods)
self.assertEqual(res, False)
def test_verify_ticket_num(self):
self.ticket.ticket_num = 'Y'
res = QueryTicket.verify_ticket_num(self.ticket)
self.assertEqual(res, True)
def test_verify_seat(self):
self.query.allow_seats = ['硬座', '二等座']
res = QueryTicket().verify_seat(self.ticket, self.query)
self.assertEqual(res, True)
self.assertEqual(self.query.available_seat.num, 30)
class TestQueryParser(TestCase):