diff --git a/README.md b/README.md index ef646fa6..21a875b5 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ### 12306 购票小助手 #### python版本 - [ ] 2.7.10 - 2.7.15 - - [x] 3.6 - 3.7.4 + - [x] 3.7 - 3.7.4 - [ ] 2.7.9 #### 已有功能 diff --git a/TickerConfig.py b/TickerConfig.py index faee155d..d4b098c9 100644 --- a/TickerConfig.py +++ b/TickerConfig.py @@ -154,4 +154,4 @@ MIN_TIME = 1 # 软件版本 -RE_VERSION = "1.2.004" +RE_VERSION = "1.2.005" diff --git a/Update.md b/Update.md index cc5c979b..00fbd667 100644 --- a/Update.md +++ b/Update.md @@ -188,4 +188,7 @@ - 2019.09.18更新 - 修改下单问题 - - 优化车次打印 \ No newline at end of file + - 优化车次打印 + +- 2019.12.30更新 + - 使用python3.7协程加速cdn测试过程 \ No newline at end of file diff --git a/agency/cdn_utils.py b/agency/cdn_utils.py index 01b95bc1..bc4f2a88 100644 --- a/agency/cdn_utils.py +++ b/agency/cdn_utils.py @@ -1,38 +1,38 @@ # encoding=utf8 -import datetime import operator import os -import requests -from config import urlConf -import threading +import asyncio +from concurrent.futures import ThreadPoolExecutor from config.urlConf import urls from myUrllib.httpUtils import HTTPClient -cdn_list = [] +finish_count = 0 -class CDNProxy(threading.Thread): - def __init__(self, cdns): - super().__init__() - self.cdns = cdns - self.urlConf = urlConf.urls - self.httpClint = requests - self.city_list = [] - self.timeout = 5 +def calculate_rtt_blocking(cdn): + global finish_count + http = HTTPClient(0) + url = urls["loginInitCdn"] + http._cdn = cdn + rtt_dict = {"rtt": 9999} + http.send(url, elapsed=rtt_dict) + rtt = rtt_dict["rtt"] + finish_count += 1 + # 过滤逻辑为当前cdn响应值小于1000毫秒 + if rtt < 2000: + print(f"ip:{cdn},延迟:{rtt},已测试个数: {finish_count}") + return {"ip": cdn, "time": rtt} + else: + print(f"ip:{cdn},延迟:N/A,已测试个数: {finish_count}") + return None - def run(self): - for cdn in self.cdns: - http = HTTPClient(0) - url = urls["loginInitCdn"] - http._cdn = cdn.replace("\n", "") - start_time = datetime.datetime.now() - rep = http.send(url) - retTime = (datetime.datetime.now() - start_time).microseconds / 1000 - if rep and "message" not in rep and retTime < 3000: - if cdn.replace("\n", "") not in cdn_list: # 如果有重复的cdn,则放弃加入 - print(f"加入cdn: {cdn}") - cdn_list.append({"ip": cdn.replace("\n", ""), "time": retTime}) + +async def calculate_all_rtt_async(cdns, loop, executor): + done, _ = await asyncio.wait(fs=[loop.run_in_executor(executor, calculate_rtt_blocking, cdn) for cdn in cdns], + return_when=asyncio.ALL_COMPLETED) + cdn_list = [task.result() for task in done if task.exception() is None and task.result() is not None] + return cdn_list def open_cdn_file(cdnFile): @@ -52,7 +52,7 @@ def open_cdn_file(cdnFile): return cdn -def sortCdn(): +def sortCdn(cdn_list): """ 对cdn进行排序 :return: @@ -73,20 +73,11 @@ def filterCdn(): :return: """ cdns = open_cdn_file("cdn_list") - cdnss = [cdns[i:i + 50] for i in range(0, len(cdns), 50)] - cdnThread = [] - for cdn in cdnss: - t = CDNProxy(cdn) - cdnThread.append(t) - for cdn_t in cdnThread: - cdn_t.start() - - for cdn_j in cdnThread: - cdn_j.join() - + loop = asyncio.get_event_loop() + cdn_list = loop.run_until_complete(calculate_all_rtt_async(cdns, loop, ThreadPoolExecutor())) print(f"当前有效cdn个数为: {len(cdn_list)}") if cdn_list: - ips = sortCdn() + ips = sortCdn(cdn_list) path = os.path.join(os.path.dirname(__file__), f'../filter_cdn_list') f = open(path, "a+") f.seek(0) diff --git a/config/StatusCode.py b/config/StatusCode.py new file mode 100644 index 00000000..0ad5ef15 --- /dev/null +++ b/config/StatusCode.py @@ -0,0 +1,30 @@ +from enum import Enum + + +class StatusCode(Enum): + """ + 程序返回状态码 + name: 状态名 + value: 状态值 + description: 状态描述 + """ + OK = 0, u"正常" + RetryTimeHasReachedMaxValue = 101, u"重试次数达到上限" + CdnListEmpty = 102, u"cdn列表为空" + UnknownError = 999, u"未知错误" + + def __new__(cls, *args, **kwargs): + obj = object.__new__(cls) + obj._value_ = args[0] + return obj + + def __init__(self, _: str, description: str = None): + self._description_ = description + + def __str__(self): + return str(self.value) + + # description is read-only + @property + def description(self): + return self._description_ diff --git a/config/TicketEnmu.py b/config/TicketEnmu.py index 9e9f294f..91ca24f0 100644 --- a/config/TicketEnmu.py +++ b/config/TicketEnmu.py @@ -6,8 +6,6 @@ class ticket(object): QUERY_C = u"查询到有余票,尝试提交订单" QUERY_IN_BLACK_LIST = u"该车次{} 正在被关小黑屋,跳过此车次" - SUCCESS_CODE = 000000 - FAIL_CODE = 999999 AUTO_SUBMIT_ORDER_REQUEST_C = u"提交订单成功" AUTO_SUBMIT_ORDER_REQUEST_F = u"提交订单失败,重新刷票中" AUTO_SUBMIT_NEED_CODE = u"需要验证码" diff --git a/docker-compose.yml b/docker-compose.yml index 502059c8..924015ce 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,7 @@ services: build: context: . dockerfile: ./Dockerfile37 - image: ticket:v1.2.004 + image: ticket:v1.2.005 environment: - PYTHONUNBUFFERED=1 - CAPTCHALOCAL=1 diff --git a/init/select_ticket_info.py b/init/select_ticket_info.py index 1dc01425..8cbccdf6 100755 --- a/init/select_ticket_info.py +++ b/init/select_ticket_info.py @@ -8,11 +8,12 @@ import time import TickerConfig import wrapcache -from agency.cdn_utils import CDNProxy, open_cdn_file +from agency.cdn_utils import open_cdn_file from config import urlConf, configCommon from config.TicketEnmu import ticket from config.configCommon import seat_conf_2, seat_conf from config.getCookie import getDrvicesID +from config.StatusCode import StatusCode from init.login import GoLogin from inter.AutoSubmitOrderRequest import autoSubmitOrderRequest from inter.ChechFace import chechFace @@ -60,8 +61,8 @@ def get_ticket_info(): print(u"*" * 50) print(f"检查当前版本为: {TickerConfig.RE_VERSION}") version = sys.version.split(" ")[0] - print(u"检查当前python版本为:{},目前版本只支持3.6以上".format(version)) - if version < "3.6.0": + print(u"检查当前python版本为:{},目前版本只支持3.7以上".format(version)) + if version < "3.7.0": raise Exception print(u"12306刷票小助手,最后更新于2019.09.18,请勿作为商业用途,交流群号:" u" 1群:286271084(已满)\n" @@ -116,6 +117,9 @@ def call_login(self, auth=False): configCommon.checkSleepTime(self) # 防止网上启动晚上到点休眠 self.login.go_login() + def read_cdn_list(self): + self.cdn_list = open_cdn_file("filter_cdn_list") + def main(self): l = liftTicketInit(self) l.reqLiftTicketInit() @@ -161,6 +165,17 @@ def main(self): ticke_peoples_num=len(TickerConfig.TICKET_PEOPLES), ) queryResult = q.sendQuery() + + # 检查结果状态 + status_value = queryResult.get("code", StatusCode.OK.value) + if status_value != StatusCode.OK.value: + # 状态出错 + if status_value == StatusCode.CdnListEmpty.value: + from agency.cdn_utils import filterCdn + filterCdn() + self.read_cdn_list() + continue + # 查询接口 if queryResult.get("status"): train_no = queryResult.get("train_no", "") diff --git a/inter/GetPassengerDTOs.py b/inter/GetPassengerDTOs.py index ba4bb593..b25d6b6d 100644 --- a/inter/GetPassengerDTOs.py +++ b/inter/GetPassengerDTOs.py @@ -2,6 +2,7 @@ import json from config.TicketEnmu import ticket +from config.StatusCode import StatusCode from myException.PassengerUserException import PassengerUserException import wrapcache import TickerConfig @@ -116,7 +117,7 @@ def getPassengerTicketStrListAndOldPassengerStr(self, secretStr, secretList): "passengerTicketStrList": set_type + "," + ",".join(passengerTicketStrList), "passengerTicketStrByAfterLate": "".join(tickers), "oldPassengerStr": "".join(oldPassengerStr), - "code": ticket.SUCCESS_CODE, + "code": StatusCode.OK.value, "set_type": set_type, "status": True, "user_info": user_info, diff --git a/inter/Query.py b/inter/Query.py index 26c04ae7..b3fd75ec 100644 --- a/inter/Query.py +++ b/inter/Query.py @@ -6,6 +6,7 @@ from config.TicketEnmu import ticket from myUrllib.httpUtils import HTTPClient from config.configCommon import seat_conf_2 +from config.StatusCode import StatusCode import TickerConfig @@ -70,6 +71,10 @@ def sendQuery(self): select_url["req_url"] = select_url["req_url"].format(station_date, self.from_station, self.to_station, self.session.queryUrl) station_ticket = self.httpClint.send(select_url) + status_code = station_ticket.get("code", StatusCode.OK.value) + if status_code != StatusCode.OK.value: + return station_ticket + value = station_ticket.get("data", "") if not value: print(u'{0}-{1} 车次坐席查询为空,查询url: https://kyfw.12306.cn{2}, 可以手动查询是否有票'.format( @@ -146,7 +151,7 @@ def sendQuery(self): "seat": seat, "leftTicket": leftTicket, "train_location": train_location, - "code": ticket.SUCCESS_CODE, + "code": StatusCode.OK.value, "is_more_ticket_num": is_more_ticket_num, "cdn": self.httpClint.cdn, "status": True, @@ -182,7 +187,7 @@ def sendQuery(self): else: print(u"车次配置信息有误,或者返回数据异常,请检查 {}".format(station_ticket)) self.session.flag = False - return {"code": ticket.FAIL_CODE, "status": False, "cdn": self.httpClint.cdn, } + return {"code": StatusCode.UnknownError.value, "status": False, "cdn": self.httpClint.cdn, } if __name__ == "__main__": diff --git a/myUrllib/httpUtils.py b/myUrllib/httpUtils.py index 77b23188..82dbded4 100755 --- a/myUrllib/httpUtils.py +++ b/myUrllib/httpUtils.py @@ -9,6 +9,7 @@ import TickerConfig from agency.agency_tools import proxy from config import logger +from config.StatusCode import StatusCode def _set_header_default(): @@ -123,7 +124,7 @@ def cdn(self): def cdn(self, cdn): self._cdn = cdn - def send(self, urls, data=None, **kwargs): + def send(self, urls, data=None, elapsed=None, **kwargs): """send request to url.If response 200,return response, else return None.""" allow_redirects = False is_logger = urls.get("is_logger", False) @@ -132,7 +133,8 @@ def send(self, urls, data=None, **kwargs): s_time = urls.get("s_time", 0) is_cdn = urls.get("is_cdn", False) is_test_cdn = urls.get("is_test_cdn", False) - error_data = {"code": 99999, "message": u"重试次数达到上限"} + retry_error = StatusCode.RetryTimeHasReachedMaxValue + error_data = {"code": retry_error.value, "message": retry_error.description} if data: method = "post" self.setHeaders({"Content-Length": "{0}".format(len(data))}) @@ -173,6 +175,8 @@ def send(self, urls, data=None, **kwargs): allow_redirects=allow_redirects, verify=False, **kwargs) + if is_test_cdn: + elapsed["rtt"] = round(response.elapsed.total_seconds() * 1000, 3) if response.status_code == 200 or response.status_code == 302: if urls.get("not_decode", False): return response.content @@ -193,7 +197,12 @@ def send(self, urls, data=None, **kwargs): u"url: {} 返回参数为空".format(urls["req_url"])) if self.cdnList: # 如果下单或者登陆出现cdn 302的情况,立马切换cdn - url_host = self.cdnList.pop(random.randint(0, 4)) + if len(self.cdnList) >= 5: + url_host = self.cdnList.pop(random.randint(0, 4)) + else: + cdn_empty_error = StatusCode.CdnListEmpty + print(f"出错:{cdn_empty_error.description}") + return {"code": cdn_empty_error.value, "message": cdn_empty_error.description} continue else: sleep(urls["re_time"])