python-qdairlines-helper 0.1.4__py3-none-any.whl → 0.2.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_qdairlines_helper-0.1.4.dist-info → python_qdairlines_helper-0.2.6.dist-info}/METADATA +1 -1
- {python_qdairlines_helper-0.1.4.dist-info → python_qdairlines_helper-0.2.6.dist-info}/RECORD +10 -10
- qdairlines_helper/controller/book_payment.py +2 -3
- qdairlines_helper/controller/book_search.py +59 -31
- qdairlines_helper/controller/order_detail.py +1 -1
- qdairlines_helper/controller/pay_success.py +1 -1
- qdairlines_helper/po/book_search_page.py +44 -25
- {python_qdairlines_helper-0.1.4.dist-info → python_qdairlines_helper-0.2.6.dist-info}/WHEEL +0 -0
- {python_qdairlines_helper-0.1.4.dist-info → python_qdairlines_helper-0.2.6.dist-info}/licenses/LICENSE +0 -0
- {python_qdairlines_helper-0.1.4.dist-info → python_qdairlines_helper-0.2.6.dist-info}/top_level.txt +0 -0
{python_qdairlines_helper-0.1.4.dist-info → python_qdairlines_helper-0.2.6.dist-info}/RECORD
RENAMED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
python_qdairlines_helper-0.
|
|
1
|
+
python_qdairlines_helper-0.2.6.dist-info/licenses/LICENSE,sha256=WtjCEwlcVzkh1ziO35P2qfVEkLjr87Flro7xlHz3CEY,11556
|
|
2
2
|
qdairlines_helper/__init__.py,sha256=bN1FqEK_jvefO_HU_8HFJpjuOzHTsuhmRe5KAzmraR4,479
|
|
3
3
|
qdairlines_helper/config/__init__.py,sha256=JjIRocePx3gwFNpdauuiwBU6B2Ug565xaHLEMu5Oo0I,479
|
|
4
4
|
qdairlines_helper/config/url_const.py,sha256=Ro-8r3dRxuloJC5NIpwUMQ7Pezg2cDyZOqNUfTqCOFQ,2086
|
|
5
5
|
qdairlines_helper/controller/__init__.py,sha256=oyeciEBlDLNpqHblB0R-nXQG3HsI8L5mmlWulQ7Y38Q,482
|
|
6
6
|
qdairlines_helper/controller/add_passenger.py,sha256=GxaoSoLOH_XXTR79bDtSEbnmM_dSpBko6OzlrQtuT7U,5159
|
|
7
|
-
qdairlines_helper/controller/book_payment.py,sha256=
|
|
8
|
-
qdairlines_helper/controller/book_search.py,sha256=
|
|
7
|
+
qdairlines_helper/controller/book_payment.py,sha256=x_jrgmZ4REMn-Zii7ZUQpuX3j8YAEFe8omt1Fm1z7YY,6059
|
|
8
|
+
qdairlines_helper/controller/book_search.py,sha256=5Tnuolm92HwbIgwhg1SF7e7bPbV2ZRluVTfVxjna-oI,9153
|
|
9
9
|
qdairlines_helper/controller/cash_pax_info.py,sha256=V5QKlvbWp8eF6gRos818w-9MyuienjgECkKKZkxCys8,2511
|
|
10
10
|
qdairlines_helper/controller/home.py,sha256=KzTyl7vvI3_Blc-XQ8ItueGR1hnV6f9gEdYalfXG9Ws,1394
|
|
11
11
|
qdairlines_helper/controller/nhlms_cashdesk.py,sha256=Fai8zZiqhUJl5-RWdkaGOdB3GTd5UQ7fpXDt4Emv65w,4312
|
|
12
|
-
qdairlines_helper/controller/order_detail.py,sha256=
|
|
12
|
+
qdairlines_helper/controller/order_detail.py,sha256=LEYrxcGx3kcYuJ9IZa-crRhCJLYTppdMVUu8hEXGrnI,3154
|
|
13
13
|
qdairlines_helper/controller/order_query.py,sha256=JzNrJj6HRHSJMQhszucWPf6nWQl4Y5B7oE1N9SpD47k,1907
|
|
14
14
|
qdairlines_helper/controller/order_verify.py,sha256=G9CC4a6KZg-VdWH6hzHDN7DkaoW2J9_yc7-mE2fL7Ms,2273
|
|
15
|
-
qdairlines_helper/controller/pay_success.py,sha256=
|
|
15
|
+
qdairlines_helper/controller/pay_success.py,sha256=tegkKa2b2R8ZqYTtGI6LqqW7h3SShpvM26cf_syi14E,3180
|
|
16
16
|
qdairlines_helper/controller/user_login.py,sha256=7PBbzP7doeUEy4M_Igb32IhjT4GzY_I-U3YimxekR_Q,4735
|
|
17
17
|
qdairlines_helper/http/__init__.py,sha256=96AJPf2zgw_rm3Wv-boIBltWFgZH3Yo-fn5M9SRK280,489
|
|
18
18
|
qdairlines_helper/http/flight_order.py,sha256=tC25YNk1pDzMmk4Zy6v120PwNuuaDhlHFh5xPBxtKco,3665
|
|
19
19
|
qdairlines_helper/po/__init__.py,sha256=HoouLJGLu_dI6IAKTVjBRraGHMcAUgntayTph-BUBAE,481
|
|
20
20
|
qdairlines_helper/po/add_passenger_page.py,sha256=XpaU6g7YSLhA8h3bHWJ_6CwnwgUmRtmLulPoRhkQ6_I,8000
|
|
21
21
|
qdairlines_helper/po/air_order_page.py,sha256=0Ux0B2hUGKCoFLRpJqGHS2w690o0A2PDnvRf9-UKfJE,930
|
|
22
|
-
qdairlines_helper/po/book_search_page.py,sha256=
|
|
22
|
+
qdairlines_helper/po/book_search_page.py,sha256=6EgA-yAqwzyT6FIMQD901-LmVmht5eBFiwLiKCE_MEs,10848
|
|
23
23
|
qdairlines_helper/po/cash_pax_info_page.py,sha256=iJlmIxD8IKnMyTNcylmoz9jV0k4u-B5ALXZEUcRajY4,3401
|
|
24
24
|
qdairlines_helper/po/home_page.py,sha256=KvoOZOYwKjlJjZOT522K_8ic3_2zaYOPWkuklUHVSlM,907
|
|
25
25
|
qdairlines_helper/po/login_page.py,sha256=SdWmkP_47GeosbFRcfvXLeQxxdM7b9UESvmajWq4-nA,2975
|
|
@@ -29,7 +29,7 @@ qdairlines_helper/po/pay_success_page.py,sha256=XGddo8as8wf1kCCdvemqHIRWOPIPv1JG
|
|
|
29
29
|
qdairlines_helper/utils/__init__.py,sha256=sti2S709puM2mQbQisk0KGq_YNjW6T4zz2vyYXonNB0,479
|
|
30
30
|
qdairlines_helper/utils/exception_utils.py,sha256=rXPao6h9sW5uL3lUA36FS3WTlePAm5EZ6MBjWeJJTUs,3934
|
|
31
31
|
qdairlines_helper/utils/po_utils.py,sha256=74ZVyyxPmPk1tcGQoMauXhBE3qsZJej8urni1ch27io,2417
|
|
32
|
-
python_qdairlines_helper-0.
|
|
33
|
-
python_qdairlines_helper-0.
|
|
34
|
-
python_qdairlines_helper-0.
|
|
35
|
-
python_qdairlines_helper-0.
|
|
32
|
+
python_qdairlines_helper-0.2.6.dist-info/METADATA,sha256=aTq22fxpTiqZKIUBftm2IPxgGj2vxmrO9OxSGppduqU,14424
|
|
33
|
+
python_qdairlines_helper-0.2.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
34
|
+
python_qdairlines_helper-0.2.6.dist-info/top_level.txt,sha256=MRbQkBMdSG4f5mx2RWfu6aXoDSyM-2KwL-YFqNBdbng,18
|
|
35
|
+
python_qdairlines_helper-0.2.6.dist-info/RECORD,,
|
|
@@ -106,11 +106,10 @@ async def book_payment_callback(
|
|
|
106
106
|
)
|
|
107
107
|
|
|
108
108
|
# 11. 检查是否支付成功
|
|
109
|
-
|
|
109
|
+
await two_check_pay_success(
|
|
110
110
|
page=page, logger=logger, timeout=timeout, query_dto=query_dto, retry=retry, enable_log=enable_log,
|
|
111
111
|
callback_get_proxy=callback_get_proxy, cookie_jar=cookie_jar
|
|
112
112
|
)
|
|
113
|
-
|
|
114
|
-
return payment_result_dto
|
|
113
|
+
return payment_result_dto
|
|
115
114
|
else:
|
|
116
115
|
raise PaymentChannelMissError()
|
|
@@ -11,13 +11,14 @@
|
|
|
11
11
|
"""
|
|
12
12
|
import asyncio
|
|
13
13
|
from logging import Logger
|
|
14
|
-
from typing import Dict, Any
|
|
14
|
+
from typing import Dict, Any, List
|
|
15
15
|
from playwright.async_api import Page, Locator
|
|
16
16
|
import qdairlines_helper.config.url_const as url_const
|
|
17
17
|
from qdairlines_helper.utils.exception_utils import IPBlockError
|
|
18
18
|
from qdairlines_helper.po.book_search_page import BookSearchPage
|
|
19
19
|
from qdairlines_helper.utils.po_utils import get_ip_access_blocked_msg
|
|
20
20
|
from flight_helper.models.dto.booking import OneWayBookingDTO, BookingInputDTO
|
|
21
|
+
from playwright.async_api import Error as PlaywrightError, TimeoutError as PlaywrightTimeoutError
|
|
21
22
|
from qdairlines_helper.utils.exception_utils import NotEnoughTicketsError, ExcessiveProfitdError, ExcessiveLossesError
|
|
22
23
|
|
|
23
24
|
|
|
@@ -25,7 +26,7 @@ async def open_book_search_page(
|
|
|
25
26
|
*, page: Page, logger: Logger, protocol: str, domain: str, timeout: float = 60.0
|
|
26
27
|
) -> BookSearchPage:
|
|
27
28
|
url_prefix = f"{protocol}://{domain}"
|
|
28
|
-
book_search_url = url_prefix + url_const.
|
|
29
|
+
book_search_url = url_prefix + url_const.book_search_url
|
|
29
30
|
await page.goto(book_search_url)
|
|
30
31
|
|
|
31
32
|
book_search_po = BookSearchPage(page=page, url=book_search_url)
|
|
@@ -37,65 +38,92 @@ async def open_book_search_page(
|
|
|
37
38
|
return book_search_po
|
|
38
39
|
|
|
39
40
|
|
|
41
|
+
async def _book_search_dialog_handle(
|
|
42
|
+
*, logger: Logger, page: BookSearchPage, flight_query_btn: Locator, timeout: float = 60.0
|
|
43
|
+
):
|
|
44
|
+
try:
|
|
45
|
+
for _ in range(3):
|
|
46
|
+
# 1. 获取页面的空数据状态, 看看当前默认状态是不是空数据页
|
|
47
|
+
await page.get_empyt_data_page(timeout=timeout)
|
|
48
|
+
logger.warning(f"航班预订查询页面,【查询机票】按钮点击完成后,没有加出来数据,尝试再次点击机票查询")
|
|
49
|
+
# 2. 页面数据没有加载出来,尝试点击 查询按钮,重新再来
|
|
50
|
+
await flight_query_btn.click(button="left")
|
|
51
|
+
await asyncio.sleep(3)
|
|
52
|
+
logger.warning(f"航班预订查询页面,【查询机票】按钮再次点击完成")
|
|
53
|
+
except (PlaywrightError, PlaywrightTimeoutError, RuntimeError, EnvironmentError, Exception,):
|
|
54
|
+
try:
|
|
55
|
+
# 2. 点击继续购票按钮
|
|
56
|
+
continue_book_btn = await page.get_reminder_dialog_continue_book_btn(timeout=timeout)
|
|
57
|
+
await continue_book_btn.click(button="left")
|
|
58
|
+
logger.info("航班预订查询页面,出现温馨提醒弹框,【继续购票】按钮点击完成")
|
|
59
|
+
except (PlaywrightTimeoutError, RuntimeError, EnvironmentError, Exception,):
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
|
|
40
63
|
async def book_search(
|
|
41
64
|
*, book_search_page: BookSearchPage, logger: Logger, passengers: int, book_input_dto: BookingInputDTO,
|
|
42
65
|
one_way_booking_dto: OneWayBookingDTO, timeout: float = 60.0
|
|
43
66
|
) -> None:
|
|
44
|
-
|
|
45
|
-
# 1. 判断是否存在温馨提醒弹框
|
|
46
|
-
continue_book_btn = await book_search_page.get_reminder_dialog_continue_book_btn(timeout=timeout)
|
|
47
|
-
await continue_book_btn.click(button="left")
|
|
48
|
-
logger.info("航班预订查询页面,出现温馨提醒弹框,【继续购票】按钮点击完成")
|
|
49
|
-
except (Exception,):
|
|
50
|
-
pass
|
|
51
|
-
|
|
52
|
-
# 2.搜索栏输入起飞城市
|
|
67
|
+
# 1.搜索栏输入起飞城市
|
|
53
68
|
depart_city_input = await book_search_page.get_depart_city_input(timeout=timeout)
|
|
54
|
-
await depart_city_input.fill(value=one_way_booking_dto.
|
|
69
|
+
await depart_city_input.fill(value=one_way_booking_dto.dep_code)
|
|
55
70
|
await asyncio.sleep(delay=1)
|
|
56
71
|
await depart_city_input.press("Enter")
|
|
57
|
-
logger.info(
|
|
72
|
+
logger.info(
|
|
73
|
+
f"航班预订查询页面,搜索栏-起飞城市<{one_way_booking_dto.dep_city} {one_way_booking_dto.dep_code}>输入完成")
|
|
58
74
|
|
|
59
|
-
#
|
|
75
|
+
# 2.搜索栏输入抵达城市
|
|
60
76
|
arrive_city_input = await book_search_page.get_arrive_city_input(timeout=timeout)
|
|
61
|
-
await arrive_city_input.fill(value=one_way_booking_dto.
|
|
77
|
+
await arrive_city_input.fill(value=one_way_booking_dto.arr_code)
|
|
62
78
|
await asyncio.sleep(delay=1)
|
|
63
79
|
await arrive_city_input.press("Enter")
|
|
64
|
-
logger.info(
|
|
80
|
+
logger.info(
|
|
81
|
+
f"航班预订查询页面,搜索栏-抵达城市<{one_way_booking_dto.arr_city} {one_way_booking_dto.arr_code}>输入完成")
|
|
65
82
|
|
|
66
|
-
#
|
|
83
|
+
# 3.搜索栏输入起飞时间
|
|
67
84
|
depart_date_input = await book_search_page.get_depart_date_input(timeout=timeout)
|
|
68
85
|
await depart_date_input.fill(value=one_way_booking_dto.dep_date)
|
|
69
86
|
await asyncio.sleep(delay=1)
|
|
70
87
|
await depart_date_input.press("Enter")
|
|
71
88
|
logger.info(f"航班预订查询页面,搜索栏-起飞日期<{one_way_booking_dto.dep_date}>输入完成")
|
|
72
89
|
|
|
73
|
-
#
|
|
90
|
+
# 4.点击【查询机票】按钮
|
|
74
91
|
flight_query_btn = await book_search_page.get_flight_query_btn(timeout=timeout)
|
|
75
92
|
await flight_query_btn.click(button="left")
|
|
76
93
|
logger.info(f"航班预订查询页面,【查询机票】按钮点击完成")
|
|
77
94
|
|
|
78
|
-
#
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
95
|
+
# 5.点击查询后,再次处理是否有弹框
|
|
96
|
+
await _book_search_dialog_handle(
|
|
97
|
+
logger=logger, page=book_search_page, flight_query_btn=flight_query_btn, timeout=timeout
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# 6.获取航班基本信息plane locator
|
|
101
|
+
flight_info_plane: Locator = await book_search_page.get_flight_info_plane(
|
|
102
|
+
flight_no=one_way_booking_dto.flight_no, timeout=timeout
|
|
103
|
+
)
|
|
83
104
|
|
|
84
|
-
#
|
|
105
|
+
# 8. 获取产品类型
|
|
85
106
|
flight_product_nav: Locator = await book_search_page.get_flight_product_nav(
|
|
86
107
|
locator=flight_info_plane, product_type=book_input_dto.product_type, timeout=timeout
|
|
87
108
|
)
|
|
88
109
|
await flight_product_nav.click(button="left")
|
|
89
110
|
logger.info(f"航班预订查询页面,产品类型【{book_input_dto.product_type}】选择完成")
|
|
90
111
|
|
|
91
|
-
#
|
|
92
|
-
|
|
112
|
+
# 9. 点击获取更多产品和价格的按钮
|
|
113
|
+
more_product_btn = await book_search_page.get_more_product_btn(locator=flight_info_plane, timeout=timeout)
|
|
114
|
+
await more_product_btn.click(button="left")
|
|
115
|
+
logger.info(f"航班预订查询页面,航班<{one_way_booking_dto.flight_no}>产品列表,【更多舱位及价格】按钮点击完成")
|
|
116
|
+
|
|
117
|
+
# 9. 获取所有的产品
|
|
118
|
+
products: List[Dict[str, Any]] = await book_search_page.get_flight_products(
|
|
119
|
+
locator=flight_info_plane, flight_no=one_way_booking_dto.flight_no, logger=logger
|
|
120
|
+
)
|
|
93
121
|
cabin_product: Dict[str, Any] = products.get(one_way_booking_dto.cabin)
|
|
94
|
-
#
|
|
122
|
+
# 9.1 判断是否存在该舱位
|
|
95
123
|
if not cabin_product:
|
|
96
124
|
raise RuntimeError(
|
|
97
125
|
f"航班预订查询页面,没有搜索到航班<{one_way_booking_dto.flight_no}>的{one_way_booking_dto.cabin}舱数据")
|
|
98
|
-
#
|
|
126
|
+
# 9.2 判断余座
|
|
99
127
|
seats_status = cabin_product.get("seats_status")
|
|
100
128
|
logger.info(
|
|
101
129
|
f"航班预订查询页面,航班<{one_way_booking_dto.flight_no}>舱位<{one_way_booking_dto.cabin}>的座位情况:{seats_status}")
|
|
@@ -103,10 +131,10 @@ async def book_search(
|
|
|
103
131
|
raise NotEnoughTicketsError(
|
|
104
132
|
flight_no=one_way_booking_dto.flight_no, seats_status=seats_status, passengers=passengers
|
|
105
133
|
)
|
|
106
|
-
#
|
|
134
|
+
# 9.3. 判断货币符号,是否为 ¥(人民币结算)
|
|
107
135
|
# 目前都为国内航班,暂不考虑货币种类
|
|
108
136
|
|
|
109
|
-
#
|
|
137
|
+
# 9.4 判断销售价格是否满足预订需要
|
|
110
138
|
amounts: Dict[str, Any] = cabin_product.get("amounts")
|
|
111
139
|
amount: float = amounts.get("amount")
|
|
112
140
|
if amount > one_way_booking_dto.standard_price + book_input_dto.standard_increase_threshold:
|
|
@@ -133,7 +161,7 @@ async def book_search(
|
|
|
133
161
|
reduction_threshold=book_input_dto.sale_reduction_threshold, asset="销售价"
|
|
134
162
|
)
|
|
135
163
|
|
|
136
|
-
#
|
|
164
|
+
# 10. 点击【购票】按钮
|
|
137
165
|
booking_btn: Locator = cabin_product.get("booking_btn")
|
|
138
166
|
await booking_btn.click(button="left")
|
|
139
167
|
logger.info(f"航班预订查询页面,【购票】按钮点击完成")
|
|
@@ -13,7 +13,7 @@ import re
|
|
|
13
13
|
from aiohttp import CookieJar
|
|
14
14
|
from typing import Dict, Any, Optional, List
|
|
15
15
|
from qdairlines_helper.http.flight_order import FlightOrder
|
|
16
|
-
from flight_helper.models.dto.itinerary import QueryItineraryResponseDTO, QueryItineraryRequestDTO
|
|
16
|
+
from flight_helper.models.dto.itinerary import QueryItineraryResponseDTO, QueryItineraryRequestDTO
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
async def get_order_detail(
|
|
@@ -63,4 +63,4 @@ async def two_check_pay_success(
|
|
|
63
63
|
)
|
|
64
64
|
order_status = (order_detail.get("orderStatus", "")).upper()
|
|
65
65
|
if order_status not in ("TICKED",):
|
|
66
|
-
raise PaymentFailedError(pre_order_no=query_dto.pre_order_no)
|
|
66
|
+
raise PaymentFailedError(pre_order_no=query_dto.pre_order_no, order_status=order_status)
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
# ---------------------------------------------------------------------------------------------------------
|
|
11
11
|
"""
|
|
12
12
|
import re
|
|
13
|
+
from logging import Logger
|
|
13
14
|
from typing import Optional, Dict, Any, List
|
|
14
15
|
from playwright.async_api import Page, Locator
|
|
15
16
|
from playwright_helper.libs.base_po import BasePo
|
|
@@ -27,6 +28,15 @@ class BookSearchPage(BasePo):
|
|
|
27
28
|
super().__init__(page, url or url_const.book_search_url)
|
|
28
29
|
self.__page = page
|
|
29
30
|
|
|
31
|
+
async def get_empyt_data_page(self, timeout: float = 5.0) -> Locator:
|
|
32
|
+
"""
|
|
33
|
+
打开搜索页后,看看当前默认状态是不是空数据页,非空数据页,会有弹框
|
|
34
|
+
:param timeout: 超时时间(秒)
|
|
35
|
+
:return: (是否存在, 错误信息 | 元素对象)
|
|
36
|
+
"""
|
|
37
|
+
selector: str = '//strong[contains(text(), "没有在飞航班")]'
|
|
38
|
+
return await self.get_locator(selector=selector, timeout=timeout)
|
|
39
|
+
|
|
30
40
|
async def get_reminder_dialog_continue_book_btn(self, timeout: float = 5.0) -> Locator:
|
|
31
41
|
"""
|
|
32
42
|
获取预订搜索页的温馨提醒弹框中的【继续购票】按钮
|
|
@@ -72,25 +82,18 @@ class BookSearchPage(BasePo):
|
|
|
72
82
|
selector: str = '//div[contains(@class, "flight_search_form")]//button[@class="search_btn"]'
|
|
73
83
|
return await self.get_locator(selector=selector, timeout=timeout)
|
|
74
84
|
|
|
75
|
-
async def get_flight_info_plane(self, timeout: float = 5.0) -> Locator:
|
|
85
|
+
async def get_flight_info_plane(self, flight_no: str, timeout: float = 5.0) -> Locator:
|
|
76
86
|
"""
|
|
77
87
|
获取预订搜索页航班内容栏航班基本信息
|
|
88
|
+
:param flight_no: 航班编号
|
|
78
89
|
:param timeout: 超时时间(秒)
|
|
79
90
|
:return: (是否存在, 错误信息|元素对象)
|
|
80
91
|
"""
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
获取预订搜索页航班编号
|
|
87
|
-
:param timeout: 超时时间(秒)
|
|
88
|
-
:param locator: flight_info_plane Locator 对象
|
|
89
|
-
:return: (是否存在, 错误信息|元素对象)
|
|
90
|
-
"""
|
|
91
|
-
selector: str = 'xpath=(.//div[contains(@class, "flight_qda")]/span)[2]'
|
|
92
|
-
sub_locator: Locator = await self.get_sub_locator(locator=locator, selector=selector, timeout=timeout)
|
|
93
|
-
return (await sub_locator.inner_text(timeout=timeout)).strip()
|
|
92
|
+
try:
|
|
93
|
+
selector: str = f'//div[contains(@class, "flight_qda")]/span[contains(text(), "{flight_no}")]/../../../..'
|
|
94
|
+
return await self.get_locator(selector=selector, timeout=timeout)
|
|
95
|
+
except (Exception,):
|
|
96
|
+
raise RuntimeError(f"航班预订查询页面,没有搜索到航班<{flight_no}>数据")
|
|
94
97
|
|
|
95
98
|
async def get_flight_product_nav(self, locator: Locator, product_type: str, timeout: float = 5.0) -> Locator:
|
|
96
99
|
"""
|
|
@@ -111,26 +114,41 @@ class BookSearchPage(BasePo):
|
|
|
111
114
|
selector: str = f'xpath=(.//div[contains(@class, "nav_item el-col el-col-24")])[{index}]'
|
|
112
115
|
return await self.get_sub_locator(locator=locator, selector=selector, timeout=timeout)
|
|
113
116
|
|
|
114
|
-
async def
|
|
117
|
+
async def get_more_product_btn(self, locator: Locator, timeout: float = 5.0) -> Locator:
|
|
115
118
|
"""
|
|
116
|
-
|
|
119
|
+
获取预订搜索页航班列表更多产品
|
|
117
120
|
:param timeout: 超时时间(秒)
|
|
121
|
+
:param locator: flight_info_plane Locator 对象
|
|
122
|
+
:return: (是否存在, 错误信息|元素对象)
|
|
123
|
+
"""
|
|
124
|
+
selector: str = f'xpath=.//span[@class="fa el-icon-caret-bottom"]'
|
|
125
|
+
return await self.get_sub_locator(locator=locator, selector=selector, timeout=timeout)
|
|
126
|
+
|
|
127
|
+
async def get_flight_products(self, locator: Locator, logger: Logger, flight_no: str) -> List[Dict[str, Any]]:
|
|
128
|
+
"""
|
|
129
|
+
获取预订搜索页航班内容栏所有航班产品
|
|
130
|
+
:param locator: flight_info_plane Locator 对象
|
|
131
|
+
:param logger: Logger 对象,日志记录
|
|
132
|
+
:param flight_no: 航班编号
|
|
118
133
|
:return: (是否存在, 错误信息|元素对象)
|
|
119
134
|
"""
|
|
120
|
-
|
|
121
|
-
|
|
135
|
+
timeout: float = 1.0
|
|
136
|
+
selector: str = 'xpath=.//div[@class="text-center cabinClasses_info el-row" and not(@style="display: none;")]//table/tbody/tr[not(@class)]/td[@class]/..'
|
|
137
|
+
products_locator: Locator = await self.get_sub_locator(locator=locator, selector=selector, timeout=timeout)
|
|
122
138
|
locators: List[Locator] = await products_locator.all()
|
|
123
|
-
flight_products =
|
|
139
|
+
flight_products = list()
|
|
140
|
+
logger.info(f"当前找到了关于航班<{flight_no}>的<{len(locators)}>条产品数据")
|
|
124
141
|
for locator in locators:
|
|
125
142
|
try:
|
|
126
143
|
booking_btn: Locator = await self._get_flight_product_booking_btn(locator=locator, timeout=timeout)
|
|
127
144
|
amounts: Dict[str, Any] = await self._get_flight_product_price(locator=locator, timeout=timeout)
|
|
128
145
|
seats_status: int = await self._get_flight_product_seats_status(locator=locator, timeout=timeout)
|
|
129
146
|
cabin = await self._get_flight_product_cabin(locator=locator, timeout=timeout)
|
|
130
|
-
flight_products
|
|
147
|
+
flight_products.append(dict(
|
|
131
148
|
amounts=amounts, cabin=cabin, seats_status=seats_status, booking_btn=booking_btn
|
|
132
|
-
)
|
|
149
|
+
))
|
|
133
150
|
except (PlaywrightError, PlaywrightTimeoutError, EnvironmentError, RuntimeError, Exception) as e:
|
|
151
|
+
logger.error(e)
|
|
134
152
|
continue
|
|
135
153
|
return flight_products
|
|
136
154
|
|
|
@@ -156,7 +174,7 @@ class BookSearchPage(BasePo):
|
|
|
156
174
|
:param locator: flight_product Locator 对象
|
|
157
175
|
:return: (是否存在, 错误信息|元素对象)
|
|
158
176
|
"""
|
|
159
|
-
selector: str = 'xpath=.//div[@class="price_flex_top"]
|
|
177
|
+
selector: str = 'xpath=.//div[@class="price_flex_top"]'
|
|
160
178
|
locator: Locator = await self.get_sub_locator(locator=locator, selector=selector, timeout=timeout)
|
|
161
179
|
amount_text: str = (await locator.inner_text(timeout=timeout)).strip()
|
|
162
180
|
return convert_order_amount_text(amount_text=amount_text)
|
|
@@ -168,7 +186,7 @@ class BookSearchPage(BasePo):
|
|
|
168
186
|
:param locator: flight_product Locator 对象
|
|
169
187
|
:return: (是否存在, 错误信息|元素对象)
|
|
170
188
|
"""
|
|
171
|
-
selector: str = 'xpath
|
|
189
|
+
selector: str = 'xpath=(.//td[@class="ticket_num"]/div/span)[1]'
|
|
172
190
|
return await self.get_sub_locator(locator=locator, selector=selector, timeout=timeout)
|
|
173
191
|
|
|
174
192
|
async def _get_flight_product_seats_status(self, locator: Locator, timeout: float = 5.0) -> int:
|
|
@@ -178,11 +196,12 @@ class BookSearchPage(BasePo):
|
|
|
178
196
|
:param locator: flight_product Locator 对象
|
|
179
197
|
:return: (是否存在, 错误信息|元素对象)
|
|
180
198
|
"""
|
|
181
|
-
selector: str = 'xpath
|
|
199
|
+
selector: str = 'xpath=(.//td[@class="ticket_num"]/div/span)[2]'
|
|
182
200
|
locator: Locator = await self.get_sub_locator(locator=locator, selector=selector, timeout=timeout)
|
|
183
201
|
more_seats_text: str = (await locator.inner_text(timeout=timeout)).strip()
|
|
184
202
|
match = re.search(r'\d+', more_seats_text)
|
|
185
203
|
if match:
|
|
186
|
-
|
|
204
|
+
# ⚠️ 正则 \d+ 没有捕获组,只能用 group(0) 或 group()
|
|
205
|
+
return safe_convert_advanced(value=match.group().strip())
|
|
187
206
|
else:
|
|
188
207
|
return 999999
|
|
File without changes
|
|
File without changes
|
{python_qdairlines_helper-0.1.4.dist-info → python_qdairlines_helper-0.2.6.dist-info}/top_level.txt
RENAMED
|
File without changes
|