diff --git a/.gitignore b/.gitignore index 7fd5c69..daeb342 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ *.html *.pyc *.yaml +*.log +idea/ diff --git a/README.md b/README.md index 2c4ac8e..691990e 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,10 @@ - 配置yaml文件的时候,需注意空格和遵循yaml语法格式,项目的yaml配置ex: - ticket_config.yaml 配置说明 ``` - #station_date:出发日期,格式ex:2018-01-06 + #station_date:出发日期改为多日期查询,格式ex: + - "2018-02-03" + - "2018-02-04" + - "2018-02-05" #station_trains:过滤车次,格式ex: # - "G1353" # - "G1329" @@ -170,4 +173,21 @@ - 更新请求第三方库 - 优化若干代码,小伙伴尽情的放肆起来 +- 2018.1.21跟新 + - 修复若干bug + - 合并dev + - 恢复之前因为12306改版引起的订票功能 + - 增加派对失败自动取消订单功能 + - 优化接口请求规范 + - 增加多日期查询,请严格按照yaml格式添加 即可 + - 注意:如果多日期查询的话,可能查询时间会比较长 + - 增加如果排队时间超过一分钟,自动取消订单 + +- 2018.1.23更新 + - 增加若快平台打码,yaml新增字段aotu_code_type,1=打码兔,2=若快 若快注册地址:http://www.ruokuai.com/client/index?6726 + - 修改is_aotu_code字段为全部是否自动打码字段,也就是说字段为rue,则全部自动打码,为False全部手动打码,包括提交订单,注意centOs不可设置手动打码 + - 修复bug + - 优化抢票功能 + + diff --git a/config/configCommon.py b/config/configCommon.py new file mode 100644 index 0000000..2861638 --- /dev/null +++ b/config/configCommon.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- + +import os +import time + +saleMinDelayDay = 0 +saleMaxDelayDay = 59 +saleStartTime = "06:00:00" +saleStopTime = "23:00:00" +rushRefreshMinTimeIntval = 2000 +rushRefreshMaxTimeIntval = 3600000 +rushRefreshTimeIntval = 100 + +RS_SUC = 0 +RS_TIMEOUT = 1 +RS_JSON_ERROR = 2 +RS_OTHER_ERROR = 3 + + +def getNowTimestamp(): + return time.time() + + +def getMinimumDate(): + return time.localtime(getNowTimestamp() + saleMinDelayDay * 24 * 3600)[:3] + + +def getMaximumDate(): + return time.localtime(getNowTimestamp() + saleMaxDelayDay * 24 * 3600)[:3] + + +def getMinimumTime(): + return [int(x) for x in saleStartTime.split(":")] + + +def getMaximumTime(): + return [int(x) for x in saleStopTime.split(":")] + + +def decMakeDir(func): + def handleFunc(*args, **kwargs): + dirname = func(*args, **kwargs) + if not os.path.exists(dirname): + os.makedirs(dirname) + elif not os.path.isdir(dirname): + pass + + return dirname + + return func + + +def getWorkDir(): + return os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +@decMakeDir +def getTmpDir(): + return os.path.join(getWorkDir(), "tmp") + + +@decMakeDir +def getLogDir(): + return os.path.join(getTmpDir(), "log") + + +@decMakeDir +def getCacheDir(): + return os.path.join(getTmpDir(), "cache") + + +@decMakeDir +def getVCodeDir(): + return os.path.join(getTmpDir(), "vcode") + + +def getVCodeImageFile(imageName): + return os.path.join(getVCodeDir(), imageName + ".jpg") + + +def getCacheFile(cacheType): + return os.path.join(getCacheDir(), cacheType + ".cache") diff --git a/config/emailConf.py b/config/emailConf.py index 97025b6..442ed01 100644 --- a/config/emailConf.py +++ b/config/emailConf.py @@ -23,7 +23,7 @@ def sendEmail(msg): host = email_conf["email_conf"]["host"] s = "{0}".format(msg) - msg = MIMEText(s, 'text', 'utf-8') # 中文需参数‘utf-8’,单字节字符不需要 + msg = MIMEText(s, 'plain', 'utf-8') # 中文需参数‘utf-8’,单字节字符不需要 msg['Subject'] = Header(subject, 'utf-8') msg['From'] = sender msg['To'] = receiver diff --git a/config/logger.py b/config/logger.py new file mode 100644 index 0000000..7687876 --- /dev/null +++ b/config/logger.py @@ -0,0 +1,61 @@ +#coding: utf-8 + +import os +import time +import logging + +from config import configCommon + +logger = None +loggerHandler = None +dateStr = '' #默认拥有日期后缀 +suffix = '' #除了日期外的后缀 + +def setSuffix(s): + global suffix + suffix = s + +def getTodayDateStr(): + return time.strftime("%Y-%m-%d", time.localtime(configCommon.getNowTimestamp())) + +def setDateStr(s): + global dateStr + dateStr = s + +def isAnotherDay(s): + global dateStr + return dateStr != s + +def getLogFile(): + global dateStr, suffix + rtn = os.path.join(configCommon.getLogDir(), dateStr) + if suffix: + rtn += "_" + suffix + return rtn + ".log" + +def log(msg, func = "info"): + global logger + if not logger: + logger = logging.getLogger() + logger.setLevel(logging.INFO) + + todayStr = getTodayDateStr() + if isAnotherDay(todayStr): + setDateStr(todayStr) + logger.removeHandler(loggerHandler) + + fh = logging.FileHandler(getLogFile()) + fm = logging.Formatter(u'[%(asctime)s][%(levelname)8s] --- %(message)s (%(filename)s:%(lineno)s)') + fh.setFormatter(fm) + + logger.addHandler(fh) + + levels = { + "debug": logger.debug, + "info": logger.info, + "warning": logger.warning, + "error": logger.error, + "critical": logger.critical + } + + levels[func](msg) \ No newline at end of file diff --git a/config/ticketConf.py b/config/ticketConf.py index ed282b5..f77d9f0 100644 --- a/config/ticketConf.py +++ b/config/ticketConf.py @@ -1,6 +1,5 @@ # -*- coding: utf8 -*- __author__ = 'MR.wen' - import os import yaml import PyQt5 diff --git a/config/ticket_config.yaml b/config/ticket_config.yaml index 1da5d17..5f6aac1 100644 --- a/config/ticket_config.yaml +++ b/config/ticket_config.yaml @@ -22,6 +22,7 @@ #ticke_peoples: 乘客 #damatu:打码兔账号,用于自动登录 #is_aotu_code是否自动打码,如果选择Ture,则调用打码兔打码,默认不使用打码兔 +#aotu_code_type 1为打码兔,2为若快 #is_email: 是否需要邮件通知 ex: True or False 切记,邮箱加完一定到config目录下测试emailConf功能是否正常 #邮箱配置 列举163 @@ -39,9 +40,22 @@ set: - station_date: "2018-02-12" + station_dates: +# - "2018-01-27" +# - "2018-01-28" + - "2018-02-09" + - "2018-02-10" +# - "2018-02-09" station_trains: + - "G4741" + - "G2365" + - "G1371" + - "G1337" - "G1377" + - "G1329" +# - "G1302" +# - "G1372" +# - "G1326" # - "K4300" # - "K5226" # - "K7772" @@ -51,35 +65,36 @@ set: # - "G1373" # - "G1363" # - "G4933" - from_station: "昆山" - to_station: "长沙" + from_station: "上海" + to_station: "邵阳" set_type: - "二等座" is_more_ticket: True ticke_peoples: - - "宋倩倩" + - "文贤平" # - "彭淑杰" 12306count: # - uesr: "" # - pwd: "apple1995" - - uesr: "" - - pwd: "songyu1995" + - uesr: "931128603@qq.com" + - pwd: "QWERTY" -select_refresh_interval: 0.1 -expect_refresh_interval: 0.3 +select_refresh_interval: 0.5 +expect_refresh_interval: 0.1 ticket_black_list_time: 3 -is_aotu_code: False +is_aotu_code: True +aotu_code_type: 2 #enable_proxy: False damatu: - uesr: "" - pwd: "wen1995" + uesr: "931128603" + pwd: "qazWSX1995" email_conf: - is_email: False - email: "@qq.com " - notice_email_list: "@qq.com" - username: "" + is_email: True + email: "931128603@qq.com " + notice_email_list: "61995120@qq.com" + username: "931128603" password: "xwopwxbkupbqbfgb" host: "smtp.qq.com" diff --git a/config/urlConf.py b/config/urlConf.py index d2e7651..3f4bbf1 100644 --- a/config/urlConf.py +++ b/config/urlConf.py @@ -84,6 +84,11 @@ urls = { "initNoCompleteUrl": { "req_url": "https://kyfw.12306.cn/otn/queryOrder/initNoComplete", "req_type": "post" + }, + "cancelNoCompleteMyOrder": { + "req_url": "https://kyfw.12306.cn/otn/queryOrder/cancelNoCompleteMyOrder", + "req_type": "post" } + } \ No newline at end of file diff --git a/damatuCode/damatuWeb.py b/damatuCode/damatuWeb.py index 5069799..e166b90 100644 --- a/damatuCode/damatuWeb.py +++ b/damatuCode/damatuWeb.py @@ -22,7 +22,7 @@ class DamatuApi(): KEY = 'ca9507e17e8d5ddf7c57cd18d8d33010' HOST = 'http://api.dama2.com:7766/app/' - def __init__(self, username, password, file_path): + def __init__(self, username, password, file_path=None): self.username = username self.password = password self.file_path = file_path @@ -108,13 +108,10 @@ class DamatuApi(): return jres['ret'] def main(self): - balance = self.getBalance() - if int(balance) > 40: - result = self.decode(287) - img_code = result.replace('|', ',') if not isinstance(result, int) else "" - return img_code - else: - raise balanceException('余额不足,当前余额为: {}'.format(balance)) + result = self.decode(287) + img_code = result.replace('|', ',') if not isinstance(result, int) else "" + print("验证码识别坐标为{0}".format(img_code)) + return img_code # # 调用类型实例: # # 1.实例化类型 参数是打码兔用户账号和密码 diff --git a/damatuCode/ruokuai.py b/damatuCode/ruokuai.py new file mode 100644 index 0000000..c1e4be7 --- /dev/null +++ b/damatuCode/ruokuai.py @@ -0,0 +1,55 @@ +# coding:utf-8 +import requests +from hashlib import md5 + + +class RClient(object): + + def __init__(self, username, password): + self.username = username + self.password = md5(password).hexdigest() + self.soft_id = '96061' + self.soft_key = '6facb9da7bb645ad9c4a229464b2cf89' + 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 = RClient('931128603', 'qazWSX1995',) + im = open('tkcode', 'rb').read() + print rc.rk_create(im, 6113) + diff --git a/init/login.py b/init/login.py index 6fbb34d..74b2170 100644 --- a/init/login.py +++ b/init/login.py @@ -9,15 +9,19 @@ from time import sleep from config.ticketConf import _get_yaml from PIL import Image from damatuCode.damatuWeb import DamatuApi +from damatuCode.ruokuai import RClient from myException.UserPasswordException import UserPasswordException +from myException.balanceException import balanceException from myUrllib import myurllib2 class GoLogin: - def __init__(self, httpClint, urlConf): + def __init__(self, httpClint, urlConf, is_aotu_code, aotu_code_type): self.httpClint = httpClint self.randCode = "" self.urlConf = urlConf + self.is_aotu_code = is_aotu_code + self.aotu_code_type = aotu_code_type def cookietp(self): print("正在获取cookie") @@ -28,7 +32,7 @@ class GoLogin: # for index, c in enumerate(myurllib2.cookiejar): # stoidinput(c) - def readImg(self): + def readImg(self, code_url): """ 增加手动打码,只是登录接口,完全不用担心提交订单效率 思路 @@ -38,28 +42,39 @@ class GoLogin: :return: """ print ("下载验证码...") - codeimgUrl = self.urlConf["getCodeImg"]["req_url"] + codeimgUrl = code_url img_path = './tkcode' - result = self.httpClint.send(codeimgUrl) + result = self.httpClint.send(codeimgUrl, is_logger=False) try: open(img_path, 'wb').write(result) - if _get_yaml()["is_aotu_code"]: - self.randCode = DamatuApi(_get_yaml()["damatu"]["uesr"], _get_yaml()["damatu"]["pwd"], img_path).main() + if self.is_aotu_code: + if self.aotu_code_type == 1: + return DamatuApi(_get_yaml()["damatu"]["uesr"], _get_yaml()["damatu"]["pwd"], img_path).main() + elif self.aotu_code_type == 2: + rc = RClient(_get_yaml()["damatu"]["uesr"], _get_yaml()["damatu"]["pwd"]) + im = open('./tkcode', 'rb').read() + Result = rc.rk_create(im, 6113) + if "Result" in Result: + return self.codexy(Ofset=",".join(list(Result["Result"])), is_raw_input=False) + else: + if "Error" in Result and Result["Error"]: + print Result["Error"] + return "" else: img = Image.open('./tkcode') img.show() - self.codexy() + return self.codexy() except OSError as e: print (e) - pass + return "" - def codexy(self): + def codexy(self, Ofset=None, is_raw_input=True): """ 获取验证码 :return: str """ - - Ofset = raw_input("请输入验证码: ") + if is_raw_input: + Ofset = raw_input("请输入验证码: ") select = Ofset.split(',') post = [] offsetsX = 0 # 选择的答案的left值,通过浏览器点击8个小图的中点得到的,这样基本没问题 @@ -93,7 +108,9 @@ class GoLogin: pass post.append(offsetsX) post.append(offsetsY) - self.randCode = str(post).replace(']', '').replace('[', '').replace("'", '').replace(' ', '') + randCode = str(post).replace(']', '').replace('[', '').replace("'", '').replace(' ', '') + print("验证码识别坐标为{0}".format(randCode)) + return randCode def auth(self): """认证""" @@ -149,7 +166,7 @@ class GoLogin: if messages.find("密码输入错误") is not -1: raise UserPasswordException("{0}".format(messages)) else: - print ("登录失败: {0}".format("".join(tresult))) + print ("登录失败: {0}".format(messages)) print ("尝试重新登陆") return False else: @@ -166,11 +183,16 @@ class GoLogin: uamauthclientUrl = self.urlConf["uamauthclient"]["req_url"] data = {"tk": uamtk} uamauthclientResult = self.httpClint.send(uamauthclientUrl, data) - if "result_code" in uamauthclientResult and uamauthclientResult["result_code"] == 0: - print("欢迎 {} 登录".format(uamauthclientResult["username"])) - return True + if uamauthclientResult: + if "result_code" in uamauthclientResult and uamauthclientResult["result_code"] == 0: + print("欢迎 {} 登录".format(uamauthclientResult["username"])) + return True + else: + return False else: - return False + self.httpClint.send(uamauthclientUrl, data) + url = self.urlConf["getUserInfo"]["req_url"] + self.httpClint.send(url) def go_login(self): """ @@ -179,19 +201,25 @@ class GoLogin: :param passwd: 密码 :return: """ + if self.is_aotu_code and self.aotu_code_type == 1: + balance = DamatuApi(_get_yaml()["damatu"]["uesr"], _get_yaml()["damatu"]["pwd"]).getBalance() + if int(balance) < 40: + raise balanceException('余额不足,当前余额为: {}'.format(balance)) user, passwd = _get_yaml()["set"]["12306count"][0]["uesr"], _get_yaml()["set"]["12306count"][1]["pwd"] + if not user or not passwd: + raise UserPasswordException("温馨提示: 用户名或者密码为空,请仔细检查") login_num = 0 while True: self.cookietp() self.httpClint.set_cookies(_jc_save_wfdc_flag="dc", _jc_save_fromStation="%u4E0A%u6D77%u8679%u6865%2CAOH", _jc_save_toStation="%u5170%u5DDE%u897F%2CLAJ", _jc_save_fromDate="2018-02-14", _jc_save_toDate="2018-01-16", RAIL_DEVICEID="EN_3_EGSe2GWGHXJeCkFQ52kHvNCrNlkz9n1GOqqQ1wR0i98WsD8Gj-a3YHZ-XYKeESWgCiJyyucgSwkFOzVHhHqfpidLPcm2vK9n83uzOPuShO3Pl4lCydAtQu4BdFqz-RVmiduNFixrcrN_Ny43135JiEtqLaI") - self.readImg() + self.randCode = self.readImg(self.urlConf["getCodeImg"]["req_url"]) login_num += 1 self.auth() if self.codeCheck(): uamtk = self.baseLogin(user, passwd) if uamtk: - if self.getUserName(uamtk): - break + self.getUserName(uamtk) + break def logout(self): url = 'https://kyfw.12306.cn/otn/login/loginOut' diff --git a/init/select_ticket_info.py b/init/select_ticket_info.py index 8754487..1ac496b 100644 --- a/init/select_ticket_info.py +++ b/init/select_ticket_info.py @@ -1,17 +1,14 @@ # -*- coding=utf-8 -*- import json import datetime -import random import re import socket import urllib import sys import time from collections import OrderedDict - from config import urlConf from init import login - from config.emailConf import sendEmail from config.ticketConf import _get_yaml from damatuCode.damatuWeb import DamatuApi @@ -29,7 +26,9 @@ sys.setdefaultencoding('utf-8') class select: def __init__(self): - self.from_station, self.to_station, self.station_date, self._station_seat, self.is_more_ticket, self.ticke_peoples, self.select_refresh_interval, self.station_trains, self.expect_refresh_interval, self.ticket_black_list_time = self.get_ticket_info() + self.from_station, self.to_station, self.station_dates, self._station_seat, self.is_more_ticket, self.ticke_peoples, self.select_refresh_interval, self.station_trains, self.expect_refresh_interval, self.ticket_black_list_time = self.get_ticket_info() + self.is_aotu_code = _get_yaml()["is_aotu_code"] + self.aotu_code_type = _get_yaml()["aotu_code_type"] self.order_request_params = {} # 订单提交时的参数 self.ticketInfoForPassengerForm = {} # 初始化当前页面参数 self.current_seats = {} # 席别信息 @@ -41,6 +40,7 @@ class select: self.is_check_user = dict() self.httpClint = HTTPClient() self.confUrl = urlConf.urls + self.login = GoLogin(self.httpClint, self.confUrl, self.is_aotu_code, self.aotu_code_type) def get_ticket_info(self): """ @@ -50,7 +50,7 @@ class select: ticket_info_config = _get_yaml() from_station = ticket_info_config["set"]["from_station"].encode("utf8") to_station = ticket_info_config["set"]["to_station"].encode("utf8") - station_date = ticket_info_config["set"]["station_date"].encode("utf8") + station_dates = ticket_info_config["set"]["station_dates"] set_type = ticket_info_config["set"]["set_type"] is_more_ticket = ticket_info_config["set"]["is_more_ticket"] ticke_peoples = ticket_info_config["set"]["ticke_peoples"] @@ -63,7 +63,7 @@ class select: ( from_station, to_station, - station_date, + station_dates, ",".join(set_type), is_more_ticket, ",".join(ticke_peoples), @@ -73,7 +73,7 @@ class select: ticket_black_list_time, ) print "*"*20 - return from_station, to_station, station_date, set_type, is_more_ticket, ticke_peoples, select_refresh_interval, station_trains, expect_refresh_interval, ticket_black_list_time + return from_station, to_station, station_dates, set_type, is_more_ticket, ticke_peoples, select_refresh_interval, station_trains, expect_refresh_interval, ticket_black_list_time def get_order_request_params(self): return self.order_request_params @@ -141,7 +141,7 @@ class select: 获取提交车票请求token :return: token """ - initdc_url = self.confUrl["initdc_url"]["req+url"] + initdc_url = self.confUrl["initdc_url"]["req_url"] initdc_result = self.httpClint.send(initdc_url) token_name = re.compile(r"var globalRepeatSubmitToken = '(\S+)'") ticketInfoForPassengerForm_name = re.compile(r'var ticketInfoForPassengerForm=(\{.+\})?') @@ -173,7 +173,7 @@ class select: 'normal_passengers']: normal_passengers = jsonData['data']['normal_passengers'] _normal_passenger = [normal_passengers[i] for i in range(len(normal_passengers))if normal_passengers[i]["passenger_name"] in self.ticke_peoples] - return _normal_passenger if _normal_passenger else normal_passengers[0] # 如果配置乘车人没有在账号,则默认返回第一个用户 + return _normal_passenger if _normal_passenger else [normal_passengers[0]] # 如果配置乘车人没有在账号,则默认返回第一个用户 else: if 'data' in jsonData and 'exMsg' in jsonData['data'] and jsonData['data']['exMsg']: print(jsonData['data']['exMsg']) @@ -185,9 +185,9 @@ class select: def submitOrderRequestFunc(self, from_station, to_station, station_date=None): select_url = self.confUrl["select_url"]["req_url"].format( - self.station_date if station_date is None else station_date, from_station, to_station) - station_ticket = self.httpClint.send(select_url) - return station_ticket + station_date, from_station, to_station) + station_ticket = self.httpClint.send(select_url, is_logger=False) + return json.loads(station_ticket) def submitOrderRequestImplement(self, from_station, to_station,): """ @@ -204,44 +204,45 @@ class select: } 参照station_seat()方法 :return: """ - station_ticket = self.submitOrderRequestFunc(from_station, to_station) - value = station_ticket['data'] - if not value: - print ('{0}-{1} 车次坐席查询为空...'.format(self.from_station, self.to_station)) - else: - if value['result']: - for i in value['result']: - ticket_info = i.split('|') - if ticket_info[11] == "Y" and ticket_info[1].encode("utf8") == "预订": # 筛选未在开始时间内的车次 - for j in range(len(self._station_seat)): - is_ticket_pass = ticket_info[self.station_seat(self._station_seat[j].encode("utf8"))] - # print self._station_seat[j] - if is_ticket_pass != '' and is_ticket_pass != '无' and ticket_info[3] in self.station_trains and is_ticket_pass != '*': # 过滤有效目标车次 - # tiket_values = [k for k in value['map'].values()] - self.secretStr = ticket_info[0] - train_no = ticket_info[3] - print ('车次: ' + train_no + ' 始发车站: ' + self.from_station + ' 终点站: ' + - self.to_station + ' ' + self._station_seat[j].encode("utf8") + ':' + ticket_info[self.station_seat(self._station_seat[j].encode("utf8"))]) - if self.ticket_black_list.has_key(train_no) and (datetime.datetime.now() - self.ticket_black_list[train_no]).seconds/60 < int(self.ticket_black_list_time): - print("该车次{} 正在被关小黑屋,跳过此车次".format(train_no)) - break - else: - print ('正在尝试提交订票...') - # self.submitOrderRequestFunc(from_station, to_station, self.time()) - self.submit_station() - self.getPassengerTicketStr(self._station_seat[j].encode("utf8")) - self.getRepeatSubmitToken() - if not self.user_info: # 修改每次都调用用户接口导致用户接口不能用 - self.user_info = self.getPassengerDTOs() - if self.checkOrderInfo(train_no, self._station_seat[j].encode("utf8")): - break - else: - pass - else: - pass - time.sleep(self.expect_refresh_interval) + station_tickets = [self.submitOrderRequestFunc(from_station, to_station, station_date) for station_date in self.station_dates] + for station_ticket in station_tickets: + value = station_ticket['data'] + if not value: + print ('{0}-{1} 车次坐席查询为空...'.format(self.from_station, self.to_station)) else: - print "车次配置信息有误,或者返回数据异常,请检查 {}".format(station_ticket) + if value['result']: + for i in value['result']: + ticket_info = i.split('|') + if ticket_info[11] == "Y" and ticket_info[1].encode("utf8") == "预订": # 筛选未在开始时间内的车次 + for j in range(len(self._station_seat)): + is_ticket_pass = ticket_info[self.station_seat(self._station_seat[j].encode("utf8"))] + # print self._station_seat[j] + if is_ticket_pass != '' and is_ticket_pass != '无' and ticket_info[3] in self.station_trains and is_ticket_pass != '*': # 过滤有效目标车次 + # tiket_values = [k for k in value['map'].values()] + self.secretStr = ticket_info[0] + train_no = ticket_info[3] + print ('车次: ' + train_no + ' 始发车站: ' + self.from_station + ' 终点站: ' + + self.to_station + ' ' + self._station_seat[j].encode("utf8") + ':' + ticket_info[self.station_seat(self._station_seat[j].encode("utf8"))]) + if self.ticket_black_list.has_key(train_no) and (datetime.datetime.now() - self.ticket_black_list[train_no]).seconds/60 < int(self.ticket_black_list_time): + print("该车次{} 正在被关小黑屋,跳过此车次".format(train_no)) + break + else: + print ('正在尝试提交订票...') + # self.submitOrderRequestFunc(from_station, to_station, self.time()) + self.submit_station() + self.getPassengerTicketStr(self._station_seat[j].encode("utf8")) + self.getRepeatSubmitToken() + if not self.user_info: # 修改每次都调用用户接口导致用户接口不能用 + self.user_info = self.getPassengerDTOs() + if self.checkOrderInfo(train_no, self._station_seat[j].encode("utf8")): + break + else: + pass + else: + pass + time.sleep(self.expect_refresh_interval) + else: + print "车次配置信息有误,或者返回数据异常,请检查 {}".format(station_ticket) def check_user(self): """ @@ -249,11 +250,11 @@ class select: :return: """ check_user_url = self.confUrl["check_user_url"]["req_url"] - data = dict(_json_att=None) + data = {"_json_att": ""} check_user = self.httpClint.send(check_user_url, data) check_user_flag = check_user['data']['flag'] if check_user_flag is True: - return True + self.is_check_user["user_time"] = datetime.datetime.now() else: if check_user['messages']: print ('用户检查失败:%s,可能未登录,可能session已经失效' % check_user['messages'][0]) @@ -464,11 +465,7 @@ class select: print("正在使用自动识别验证码功能") checkRandCodeAnsyn = self.confUrl["checkRandCodeAnsyn"]["req_url"] codeImgByOrder = self.confUrl["codeImgByOrder"]["req_url"] - result = self.httpClint.send(codeImgByOrder) - img_path = './tkcode' - open(img_path, 'wb').write(result) - randCode = DamatuApi(_get_yaml()["damatu"]["uesr"], _get_yaml()["damatu"]["pwd"], - img_path).main() + randCode = self.login.readImg(codeImgByOrder) randData = { "randCode": randCode, "rand": "randp", @@ -516,6 +513,9 @@ class select: num += 1 if num > 30: print("超出排队时间,自动放弃,正在重新刷票") + order_id = self.queryMyOrderNoComplete() # 排队失败,自动取消排队订单 + if order_id: + self.cancelNoCompleteMyOrder(order_id) break try: data = {"random": _random, "tourFlag": "dc"} @@ -526,8 +526,8 @@ class select: if queryOrderWaitTimeResult: if "status" in queryOrderWaitTimeResult and queryOrderWaitTimeResult["status"]: if "orderId" in queryOrderWaitTimeResult["data"] and queryOrderWaitTimeResult["data"]["orderId"] is not None: - sendEmail("恭喜您订票成功,订单号为:{0}, 请立即打开浏览器登录12306,访问‘未完成订单’,在30分钟内完成支付!".format(queryOrderWaitTimeResult["data"]["orderId"])) - raise ticketIsExitsException("恭喜您订票成功,订单号为:{0}, 请立即打开浏览器登录12306,访问‘未完成订单’,在30分钟内完成支付!".format(queryOrderWaitTimeResult["data"]["orderId"])) + sendEmail("恭喜您订票成功,订单号为:{0}, 请立即打开浏览器登录12306,访问‘未完成订单’,在30分钟内完成支付!".format(queryOrderWaitTimeResult["data"]["orderId"])) + raise ticketIsExitsException("恭喜您订票成功,订单号为:{0}, 请立即打开浏览器登录12306,访问‘未完成订单’,在30分钟内完成支付!".format(queryOrderWaitTimeResult["data"]["orderId"])) elif "msg" in queryOrderWaitTimeResult["data"] and queryOrderWaitTimeResult["data"]["msg"]: print queryOrderWaitTimeResult["data"]["msg"] break @@ -538,14 +538,10 @@ class select: elif "messages" in queryOrderWaitTimeResult and queryOrderWaitTimeResult["messages"]: print("排队等待失败: " + queryOrderWaitTimeResult["messages"]) else: - print("第{}次排队中,请耐心等待".format(num)) + print("第{}次排队中,请耐心等待".format(num+1)) else: print("排队中") - time.sleep(1) - order_id = self.queryMyOrderNoComplete() # 尝试查看订单列表,如果有订单,则判断成功,不过一般可能性不大 - if order_id: - sendEmail("恭喜您订票成功,订单号为:{0}, 请立即打开浏览器登录12306,访问‘未完成订单’,在30分钟内完成支付!".format(order_id)) - raise ticketIsExitsException("恭喜您订票成功,订单号为:{0}, 请立即打开浏览器登录12306,访问‘未完成订单’,在30分钟内完成支付!".format(order_id)) + time.sleep(2) else: print(ticketNumOutException("订单提交失败!,正在重新刷票")) @@ -557,7 +553,7 @@ class select: """ self.initNoComplete() queryMyOrderNoCompleteUrl = self.confUrl["queryMyOrderNoCompleteUrl"]["req_url"] - data = {"_json_att": None} + data = {"_json_att": ""} try: queryMyOrderNoCompleteResult = self.httpClint.send(queryMyOrderNoCompleteUrl, data) except ValueError: @@ -584,10 +580,31 @@ class select: 获取订单前需要进入订单列表页,获取订单列表页session :return: """ + self.httpClint.set_cookies(acw_tc="AQAAAEnFJnekLwwAtGHjZZCr79B6dpXk", current_captcha_type="Z") initNoCompleteUrl = self.confUrl["initNoCompleteUrl"]["req_url"] - data = {"_json_att": None} + data = {"_json_att": ""} self.httpClint.send(initNoCompleteUrl, data) + def cancelNoCompleteMyOrder(self, sequence_no): + """ + 取消订单 + :param sequence_no: 订单编号 + :return: + """ + cancelNoCompleteMyOrderUrl = self.confUrl["cancelNoCompleteMyOrder"]["req_url"] + cancelNoCompleteMyOrderData = { + "sequence_no": sequence_no, + "cancel_flag": "cancel_order", + "_json_att": "" + } + cancelNoCompleteMyOrderResult = self.httpClint.send(cancelNoCompleteMyOrderUrl, cancelNoCompleteMyOrderData) + if "data" in cancelNoCompleteMyOrderResult and "existError" in cancelNoCompleteMyOrderResult["data"] and cancelNoCompleteMyOrderResult["data"]["existError"] == "N": + print("排队超时,已为您自动取消订单,订单编号: {0}".format(sequence_no)) + time.sleep(2) + return True + else: + print("排队超时,取消订单失败, 订单号{0}".format(sequence_no)) + # def call_submit_ticket(self, function_name=None): # """ # 订票失败回调方法,默认执行submitOrderRequest() @@ -600,21 +617,27 @@ class select: # else: # self.submitOrderRequest() - def call_login(self): - """登录回调方法""" - login = GoLogin(self.httpClint, self.confUrl) - login.go_login() + def call_login(self, auth=False): + """ + 登录回调方法 + :return: + """ + if auth: + return self.login.auth() + else: + self.login.go_login() def main(self): self.call_login() from_station, to_station = self.station_table(self.from_station, self.to_station) - # if self.leftTicketLog(from_station, to_station): + self.check_user() + time.sleep(0.1) num = 1 while 1: try: num += 1 - if "user_time" in self.is_check_user and (datetime.datetime.now() - self.is_check_user["user_time"]).seconds/60 > 10: - # 十分钟调用一次检查用户是否登录 + if "user_time" in self.is_check_user and (datetime.datetime.now() - self.is_check_user["user_time"]).seconds/60 > 5: + # 5分钟检查一次用户是否登录 self.check_user() time.sleep(self.select_refresh_interval) if time.strftime('%H:%M:%S', time.localtime(time.time())) > "23:00:00": @@ -623,7 +646,7 @@ class select: self.call_login() start_time = datetime.datetime.now() self.submitOrderRequestImplement(from_station, to_station) - print "正在第{0}次查询 乘车日期: {1} 车次{2} 查询无票 代理设置 无 总耗时{3}ms".format(num, self.station_date, ",".join(self.station_trains), (datetime.datetime.now()-start_time).microseconds/1000) + print "正在第{0}次查询 乘车日期: {1} 车次{2} 查询无票 代理设置 无 总耗时{3}ms".format(num, ",".join(self.station_dates), ",".join(self.station_trains), (datetime.datetime.now()-start_time).microseconds/1000) except PassengerUserException as e: print e.message break @@ -647,13 +670,11 @@ class select: except KeyError as e: print(e.message) except TypeError as e: - print(e.message) + print("12306接口无响应,正在重试 {0}".format(e.message)) except socket.error as e: print(e.message) - - if __name__ == '__main__': login() # a = select('上海', '北京') diff --git a/myUrllib/httpUtils.py b/myUrllib/httpUtils.py index 45a9c13..c17b7b0 100644 --- a/myUrllib/httpUtils.py +++ b/myUrllib/httpUtils.py @@ -2,9 +2,12 @@ import datetime import json import socket +from time import sleep import requests +import sys +from config import logger class HTTPClient(object): @@ -57,6 +60,10 @@ class HTTPClient(object): self._s.headers.update(headers) return self + def resetHeaders(self): + self._s.headers.clear() + self._s.headers.update(self._set_header()) + def getHeadersHost(self): return self._s.headers["Host"] @@ -71,26 +78,41 @@ class HTTPClient(object): self._s.headers.update({"Referer": referer}) return self - def send(self, url, data=None, **kwargs): + def send(self, url, data=None, is_logger=True, **kwargs): """send request to url.If response 200,return response, else return None.""" - method = "post"if data else "get" - response = self._s.request(method=method, - url=url, - data=data, - **kwargs) - try: - if response.content: - return json.loads(response.content) if method == "post" else response.content - else: - return "" - except ValueError as e: - if e.message == "No JSON object could be decoded": - print("12306接口无响应,正在重试") - else: - print(e.message) - except KeyError as e: - print(e.message) - except TypeError as e: - print(e.message) - except socket.error as e: - print(e.message) \ No newline at end of file + allow_redirects = False + error_data = {"code": 99999, "message": "重试次数达到上限"} + if data: + method = "post" + self.setHeaders({"Content-Length": "{0}".format(len(data))}) + else: + method = "get" + self.resetHeaders() + if is_logger: + logger.log( + u"url: {0}\n入参: {1}\n请求方式: {2}\n".format(url,data,method,)) + for i in range(10): + try: + response = self._s.request(method=method, + timeout=10, + url=url, + data=data, + allow_redirects=allow_redirects, + **kwargs) + if response.status_code == 200: + if response.content: + if is_logger: + logger.log( + u"出参:{0}".format(response.content)) + return json.loads(response.content) if method == "post" else response.content + else: + logger.log( + u"url: {} 返回参数为空".format(url)) + return error_data + else: + sleep(0.1) + except (requests.exceptions.Timeout, requests.exceptions.ReadTimeout, requests.exceptions.ConnectionError): + pass + except socket.error: + pass + return error_data diff --git a/tkcode b/tkcode index d7eb6d9..e3e73b3 100644 Binary files a/tkcode and b/tkcode differ diff --git a/tmp/__init__.py b/tmp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tmp/log/__init__.py b/tmp/log/__init__.py new file mode 100644 index 0000000..e69de29