build the basic frame

This commit is contained in:
Jalin
2019-01-06 10:01:26 +08:00
parent b32da477cb
commit e6e19056f9
18 changed files with 749 additions and 0 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
.idea
.DS_Store
venv
__pycache__
env.py

1
data/stations.txt Executable file
View File

File diff suppressed because one or more lines are too long

72
env.py.example Normal file
View File

@@ -0,0 +1,72 @@
# encoding=utf8
# 12306 账号
USER_ACCOUNTS = [
{
'user_name': '',
'password': ''
}
]
# 查询任务
QUERY_JOBS = [
{
'left_dates': [ # 出发日期 :Array
"2019-01-24",
"2019-01-25",
"2019-01-26",
"2019-01-27",
"2019-01-28"
],
'stations': { # 车站 :Dict
'left': '广州',
'arrive': '达州',
},
'members': [
'1'
],
'allow_less_member': 0,
'seats': [ # 筛选座位 有先后顺序 :Array
'硬卧',
'硬座'
],
'train_numbers': [ # 筛选车次
"K1096",
"K814",
"K356",
"K1172",
"K4184"
]
},
{
'left_dates': [ # 出发日期 :Array
"2019-01-24",
"2019-01-25",
"2019-01-26",
"2019-01-27",
"2019-01-28"
],
'stations': { # 车站 :Dict
'left': '广州',
'arrive': '达州',
},
'members': [
'1'
],
'allow_less_member': 0,
'seats': [ # 筛选座位 有先后顺序 :Array
'硬卧',
'硬座'
],
'train_numbers': [ # 筛选车次
"K1096",
"K814",
"K356",
"K1172",
"K4184"
]
}
]

17
main.py Normal file
View File

@@ -0,0 +1,17 @@
# encoding=utf8
from threading import Thread
from py12306.log.query_log import QueryLog
from py12306.query.query import Query
def main():
# Thread(target=Query.run).start() # 余票查询
QueryLog.add_log('init')
Query.run()
pass
if __name__ == '__main__':
main()

0
py12306/__init__.py Normal file
View File

34
py12306/config.py Normal file
View File

@@ -0,0 +1,34 @@
from os import path
# from py12306.helpers.config import Config
# 12306 账号
USER_ACCOUNTS = []
# 查询任务
QUERY_JOBS = []
SEAT_TYPES = {
'商务座': 32,
'一等座': 31,
'二等座': 30,
'特等座': 25,
'软卧': 23,
'硬卧': 28,
'硬座': 29,
'无座': 26,
}
# Query
QUERY_DATA_DIR = 'runtime/query'
STATION_FILE = 'data/stations.txt'
CONFIG_FILE = 'env.py'
if path.exists(CONFIG_FILE):
exec(open(CONFIG_FILE, encoding='utf8').read())
def get(key, default=None):
return eval(key)

View File

360
py12306/helpers/api.py Normal file
View File

@@ -0,0 +1,360 @@
# coding=utf-8
# 查询余票
import time
BASE_URL_OF_12306 = 'https://kyfw.12306.cn'
LEFT_TICKETS = {
"url": BASE_URL_OF_12306 + "/otn/{type}?leftTicketDTO.train_date={left_date}&leftTicketDTO.from_station={left_station}&leftTicketDTO.to_station={arrive_station}&purpose_codes=ADULT",
"re_try": 10,
"re_time": 0.01,
"s_time": 0.01,
"is_logger": False,
"is_json": True,
"is_cdn": True,
}
urls = {
"auth": { # 登录接口
"req_url": "/passport/web/auth/uamtk",
"req_type": "post",
"Referer": "https://kyfw.12306.cn/otn/passport?redirect=/otn/login/userLogin",
"Host": "kyfw.12306.cn",
"Content-Type": 1,
"re_try": 10,
"re_time": 0.1,
"s_time": 0.1,
"is_logger": True,
"is_json": True,
},
"login": { # 登录接口
"req_url": "/passport/web/login",
"req_type": "post",
"Referer": "https://kyfw.12306.cn/otn/login/init",
"Host": "kyfw.12306.cn",
"Content-Type": 1,
"re_try": 10,
"re_time": 0.1,
"s_time": 0.1,
"is_logger": True,
"is_json": True,
},
"left_ticket_init": { # 登录接口
"req_url": "/otn/leftTicket/init",
"req_type": "post",
"Referer": "https://kyfw.12306.cn/otn/login/init",
"Host": "kyfw.12306.cn",
"Content-Type": 1,
"re_try": 10,
"re_time": 0.1,
"s_time": 0.1,
"is_logger": False,
"is_json": False,
},
"getCodeImg": { # 登录验证码
"req_url": "/passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand&{0}",
"req_type": "get",
"Referer": "https://kyfw.12306.cn/otn/login/init",
"Host": "kyfw.12306.cn",
"Content-Type": 1,
"re_try": 10,
"re_time": 0.1,
"s_time": 0.1,
"is_logger": False,
"is_json": False,
"not_decode": True,
},
"codeCheck": { # 验证码校验
"req_url": "/passport/captcha/captcha-check",
"req_type": "post",
"Referer": "https://kyfw.12306.cn/otn/login/init",
"Host": "kyfw.12306.cn",
"Content-Type": 1,
"re_try": 10,
"re_time": 0.1,
"s_time": 0.1,
"is_logger": True,
"is_json": True,
},
"loginInit": { # 登录页面
"req_url": "/otn/login/init",
"req_type": "get",
"Referer": "https://kyfw.12306.cn/otn/index/init",
"Host": "kyfw.12306.cn",
"re_try": 1,
"re_time": 0.1,
"s_time": 0.1,
"is_logger": False,
"is_json": False,
},
"loginInitCdn": { # 登录页面
"req_url": "/otn/login/init",
"req_type": "get",
"Referer": "https://kyfw.12306.cn/otn/index/init",
"Host": "kyfw.12306.cn",
"re_try": 1,
"re_time": 0.1,
"s_time": 0.1,
"is_logger": False,
"is_test_cdn": True,
"is_json": False,
},
"getUserInfo": { # 获取用户信息
"req_url": "/otn/index/initMy12306",
"req_type": "get",
"Referer": "https://kyfw.12306.cn/otn/passport?redirect=/otn/login/userLogin",
"Host": "kyfw.12306.cn",
"re_try": 10,
"re_time": 0.1,
"s_time": 0.01,
"is_logger": False,
"is_json": False,
},
"userLogin": { # 用户登录
"req_url": "/otn/login/userLogin",
"req_type": "get",
"Referer": "https://kyfw.12306.cn/otn/passport?redirect=/otn/login/userLogin",
"Host": "kyfw.12306.cn",
"re_try": 10,
"re_time": 0.1,
"s_time": 0.1,
"is_logger": True,
"is_json": True,
},
"uamauthclient": { # 登录
"req_url": "/otn/uamauthclient",
"req_type": "post",
"Referer": "https://kyfw.12306.cn/otn/passport?redirect=/otn/login/userLogin",
"Host": "kyfw.12306.cn",
"Content-Type": 1,
"re_try": 10,
"re_time": 0.1,
"s_time": 0.1,
"is_logger": True,
"is_json": True,
},
"initdc_url": { # 生成订单页面
"req_url": "/otn/confirmPassenger/initDc",
"req_type": "get",
"Referer": "https://kyfw.12306.cn/otn/leftTicket/init",
"Host": "kyfw.12306.cn",
"re_try": 10,
"re_time": 0.1,
"s_time": 1,
"is_logger": False,
"is_json": False,
},
"GetJS": { # 订单页面js
"req_url": "/otn/HttpZF/GetJS",
"req_type": "get",
"Referer": "https://kyfw.12306.cn/otn/confirmPassenger/initDc",
"Host": "kyfw.12306.cn",
"re_try": 10,
"re_time": 0.1,
"s_time": 0.1,
"is_logger": False,
"is_json": False,
},
"odxmfwg": { # 订单页面js
"req_url": "/otn/dynamicJs/odxmfwg",
"req_type": "get",
"Referer": "https://kyfw.12306.cn/otn/confirmPassenger/initDc",
"Host": "kyfw.12306.cn",
"re_try": 10,
"re_time": 0.1,
"s_time": 0.1,
"is_logger": False,
"is_json": False,
},
"get_passengerDTOs": { # 获取乘车人
"req_url": "/otn/confirmPassenger/getPassengerDTOs",
"req_type": "post",
"Referer": "https://kyfw.12306.cn/otn/confirmPassenger/initDc",
"Host": "kyfw.12306.cn",
"re_try": 10,
"re_time": 0.1,
"s_time": 0.1,
"is_logger": True,
"is_json": True,
},
"select_url": { # 查询余票
"req_url": "/otn/{3}?leftTicketDTO.train_date={0}&leftTicketDTO.from_station={1}&leftTicketDTO.to_station={2}&purpose_codes=ADULT",
"req_type": "get",
"Referer": "https://kyfw.12306.cn/otn/leftTicket/init",
"Host": "kyfw.12306.cn",
"re_try": 10,
"re_time": 0.01,
"s_time": 0.01,
"is_logger": False,
"is_json": True,
"is_cdn": True,
},
"check_user_url": { # 检查用户登录
"req_url": "/otn/login/checkUser",
"req_type": "post",
"Referer": "https://kyfw.12306.cn/otn/leftTicket/init",
"Host": "kyfw.12306.cn",
"re_try": 10,
"re_time": 0.3,
"s_time": 0.1,
"is_logger": True,
"is_json": True,
},
"submit_station_url": { # 提交订单
"req_url": "/otn/leftTicket/submitOrderRequest",
"req_type": "post",
"Referer": "https://kyfw.12306.cn/otn/leftTicket/init",
"Host": "kyfw.12306.cn",
"re_try": 10,
"re_time": 0.01,
"s_time": 0.1,
"is_logger": True,
"is_json": True,
},
"checkOrderInfoUrl": { # 检查订单信息规范
"req_url": "/otn/confirmPassenger/checkOrderInfo",
"req_type": "post",
"Referer": "https://kyfw.12306.cn/otn/confirmPassenger/initDc",
"Host": "kyfw.12306.cn",
"re_try": 10,
"re_time": 0.01,
"s_time": 0.1,
"is_logger": True,
"is_json": True,
},
"getQueueCountUrl": { # 剩余余票数
"req_url": "/otn/confirmPassenger/getQueueCount",
"req_type": "post",
"Referer": "https://kyfw.12306.cn/otn/confirmPassenger/initDc",
"Host": "kyfw.12306.cn",
"re_try": 10,
"re_time": 0.01,
"s_time": 0.1,
"is_logger": True,
"is_json": True,
},
"checkQueueOrderUrl": { # 订单队列排队
"req_url": "/otn/confirmPassenger/confirmSingleForQueue",
"req_type": "post",
"Referer": "https://kyfw.12306.cn/otn/confirmPassenger/initDc",
"Host": "kyfw.12306.cn",
"re_try": 10,
"re_time": 0.01,
"s_time": 0.1,
"is_logger": True,
"is_json": True,
},
"checkRandCodeAnsyn": { # 暂时没用到
"req_url": "/otn/passcodeNew/checkRandCodeAnsyn",
"req_type": "post",
"Referer": "https://kyfw.12306.cn/otn/confirmPassenger/initDc",
"Host": "kyfw.12306.cn",
"re_try": 10,
"re_time": 0.01,
"s_time": 0.1,
"is_logger": True,
"is_json": True,
},
"codeImgByOrder": { # 订单页面验证码
"req_url": "/otn/passcodeNew/getPassCodeNew?module=passenger&rand=randp&{}",
"req_type": "post",
"Referer": "https://kyfw.12306.cn/otn/confirmPassenger/initDc",
"Host": "kyfw.12306.cn",
"re_try": 10,
"re_time": 0.01,
"s_time": 0.1,
"is_logger": False,
"is_json": False,
},
"queryOrderWaitTimeUrl": { # 订单等待页面
"req_url": "/otn/confirmPassenger/queryOrderWaitTime?random={0}&tourFlag=dc&_json_att=",
"req_type": "get",
"Referer": "https://kyfw.12306.cn/otn/confirmPassenger/initDc",
"Host": "kyfw.12306.cn",
"re_try": 10,
"re_time": 0.01,
"s_time": 0.1,
"is_logger": True,
"is_json": True,
},
"queryMyOrderNoCompleteUrl": { # 订单查询页面
"req_url": "/otn/queryOrder/queryMyOrderNoComplete",
"req_type": "post",
"Referer": "https://kyfw.12306.cn/otn/queryOrder/initNoComplete",
"Host": "kyfw.12306.cn",
"re_try": 10,
"re_time": 0.01,
"s_time": 0.1,
"is_logger": True,
"is_json": True,
},
"initNoCompleteUrl": { # 获取订单列表
"req_url": "/otn/queryOrder/initNoComplete",
"req_type": "post",
"Referer": "https://kyfw.12306.cn/otn/queryOrder/initNoComplete",
"Host": "kyfw.12306.cn",
"re_try": 10,
"re_time": 0.01,
"s_time": 0.1,
"is_logger": False,
"is_json": False,
},
"cancelNoCompleteMyOrder": { # 取消订单
"req_url": "/otn/queryOrder/cancelNoCompleteMyOrder",
"req_type": "post",
"Referer": "https://kyfw.12306.cn/otn/queryOrder/initNoComplete",
"Host": "kyfw.12306.cn",
"re_try": 10,
"re_time": 0.01,
"s_time": 0.1,
"is_logger": True,
"is_json": True,
},
"autoSubmitOrderRequest": { # 快速自动提交订单
"req_url": "/otn/confirmPassenger/autoSubmitOrderRequest",
"req_type": "post",
"Referer": "https://kyfw.12306.cn/otn/leftTicket/init",
"Host": "kyfw.12306.cn",
"Content-Type": 1,
"re_try": 10,
"re_time": 0.01,
"s_time": 0.1,
"is_logger": True,
"is_json": True,
},
"getQueueCountAsync": { # 快速获取订单数据
"req_url": "/otn/confirmPassenger/getQueueCountAsync",
"req_type": "post",
"Referer": "https://kyfw.12306.cn/otn/leftTicket/init",
"Host": "kyfw.12306.cn",
"Content-Type": 1,
"re_try": 10,
"re_time": 0.01,
"s_time": 0.1,
"is_logger": True,
"is_json": True,
},
"confirmSingleForQueueAsys": { # 快速订单排队
"req_url": "/otn/confirmPassenger/confirmSingleForQueueAsys",
"req_type": "post",
"Referer": "https://kyfw.12306.cn/otn/leftTicket/init",
"Content-Type": 1,
"Host": "kyfw.12306.cn",
"re_try": 10,
"re_time": 0.01,
"s_time": 0.1,
"is_logger": True,
"is_json": True,
},
"cdn_host": {
"req_url": "http://ping.chinaz.com/kyfw.12306.cn",
"req_type": "post"
},
"cdn_list": {
"req_url": "http://ping.chinaz.com/iframe.ashx?t=ping&callback=jQuery111304824429956769827_{}".format(
int(round(time.time() * 1000))),
"req_type": "post"
}
}

14
py12306/helpers/config.py Normal file
View File

@@ -0,0 +1,14 @@
# from py12306.config import get_value_by_key
from py12306.config import get_value_by_key
class BaseConfig:
AA = 'USER_ACCOUNTS'
class Config(BaseConfig):
@classmethod
def get(cls, key, default=None):
self = cls()
return get_value_by_key(key);

39
py12306/helpers/func.py Normal file
View File

@@ -0,0 +1,39 @@
from py12306 import config
import functools
def singleton(cls):
"""
将一个类作为单例
来自 https://wiki.python.org/moin/PythonDecoratorLibrary#Singleton
"""
cls.__new_original__ = cls.__new__
@functools.wraps(cls.__new__)
def singleton_new(cls, *args, **kw):
it = cls.__dict__.get('__it__')
if it is not None:
return it
cls.__it__ = it = cls.__new_original__(cls, *args, **kw)
it.__init_original__(*args, **kw)
return it
cls.__new__ = singleton_new
cls.__init_original__ = cls.__init__
cls.__init__ = object.__init__
return cls
# 座位
def get_seat_number_by_name(name):
return config.SEAT_TYPES[name]
def get_seat_name_by_number(number):
return [k for k, v in config.SEAT_TYPES.items() if v == number].pop()
# def test:

View File

@@ -0,0 +1,34 @@
from os import path
from py12306.helpers.func import *
@singleton
class Station:
stations = []
def __init__(self):
print('Station 初始化')
if path.exists(config.STATION_FILE):
result = open(config.STATION_FILE, encoding='utf-8').read()
result = result.lstrip('@').split('@')
for i in result:
tmp_info = i.split('|')
self.stations.append({
'key': tmp_info[2],
'name': tmp_info[1],
'pinyin': tmp_info[3],
'id': tmp_info[5]
})
@classmethod
def get_station_by_name(cls, name):
self = cls()
for station in self.stations:
if station.get('name') == name:
return station
return None
@classmethod
def get_station_key_by_name(cls, name):
return cls.get_station_by_name(name).get('key')

0
py12306/log/__init__.py Normal file
View File

18
py12306/log/base.py Normal file
View File

@@ -0,0 +1,18 @@
from py12306.helpers.func import *
class BaseLog:
logs = []
@classmethod
def add_log(cls, content):
self = cls()
self.logs.append(content)
return self
@classmethod
def flush(cls):
self = cls()
for i in self.logs:
print(i)
# print(self.logs)

49
py12306/log/query_log.py Normal file
View File

@@ -0,0 +1,49 @@
import json
from os import path
from py12306.log.base import BaseLog
from py12306.helpers.func import *
@singleton
class QueryLog(BaseLog):
data = {
'query_count': 0,
'last_time': '',
}
LOG_INIT_JOBS = ''
def __init__(self):
super().__init__()
self.init_data()
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 result:
result = json.loads(result)
self.data = {**self.data, **result}
@classmethod
def print_init_jobs(cls, jobs):
self = cls()
"""
输出初始化信息
:return:
"""
self.add_log('# 发现任务 {} 条 #'.format(len(jobs)))
index = 1
for job in jobs:
self.add_log('================== 任务 {} =================='.format(index))
self.add_log('出发站:{} 到达站:{}'.format(job.left_station, job.arrive_station))
self.add_log('乘车日期:{}'.format(job.left_dates))
self.add_log('坐席:{}'.format(''.join(job.allow_seats)))
self.add_log('乘车人:{}'.format(''.join(job.members)))
self.add_log('筛选车次:{}'.format(''.join(job.allow_train_numbers)))
# 乘车日期:['2019-01-24', '2019-01-25', '2019-01-26', '2019-01-27']
index += 1
return self

View File

31
py12306/query/job.py Normal file
View File

@@ -0,0 +1,31 @@
from py12306.helpers.station import Station
class Job:
"""
查询任务
"""
left_dates = []
left_station = ''
arrive_station = ''
left_station_code = ''
arrive_station_code = ''
allow_seats = []
allow_train_numbers = []
members = []
member_num = []
def __init__(self, info):
self.left_dates = info.get('left_dates')
self.left_station = info.get('stations').get('left')
self.arrive_station = info.get('stations').get('arrive')
self.left_station_code = Station.get_station_key_by_name(self.left_station)
self.arrive_station_code = Station.get_station_key_by_name(self.arrive_station)
self.allow_seats = info.get('seats')
self.allow_train_numbers = info.get('train_numbers')
self.members = info.get('members')
self.member_num = len(self.members)

78
py12306/query/query.py Normal file
View File

@@ -0,0 +1,78 @@
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
class Query:
"""
余票查询
"""
jobs = []
session = {}
def __init__(self):
self.session = HTMLSession()
@classmethod
def run(cls):
self = cls()
self.start()
pass
def start(self):
self.init_jobs()
QueryLog.print_init_jobs(jobs=self.jobs).flush()
for job in self.jobs:
self.handle_single_job(job)
def init_jobs(self):
jobs = config.QUERY_JOBS
for job in jobs:
job = Job(info=job)
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

0
py12306/user/__init__.py Normal file
View File