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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python_qdairlines_helper
3
- Version: 0.1.4
3
+ Version: 0.2.6
4
4
  Summary: qdairlines helper python package
5
5
  Author-email: ckf10000 <ckf10000@sina.com>
6
6
  License: Apache License
@@ -1,25 +1,25 @@
1
- python_qdairlines_helper-0.1.4.dist-info/licenses/LICENSE,sha256=WtjCEwlcVzkh1ziO35P2qfVEkLjr87Flro7xlHz3CEY,11556
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=ygIHu6LGnLLihDM4PrpNDr3ZkTEZiQb-taqk4DuvANk,6104
8
- qdairlines_helper/controller/book_search.py,sha256=Q1KZKYeRmJh26nK1G9d8sWJF7_kCUS-9mJjTyoWCXpE,7598
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=6h6TyS_-yU2AHdFxbQS16TyjKWGWeCu1uVlsz9wANDE,3172
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=UiIn6rMpD4BIsBbbiZH4xuWJd5eeuHp0sb-qmo3MJGg,3153
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=cHcPhHPPE0xuGfi6fnue-lH33COWU_nyLH2tVUQGI7U,9782
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.1.4.dist-info/METADATA,sha256=SIlFOHl1g6nCQGSubFYF6rnaXyF1fq-zg8KmLY0RMvA,14424
33
- python_qdairlines_helper-0.1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
- python_qdairlines_helper-0.1.4.dist-info/top_level.txt,sha256=MRbQkBMdSG4f5mx2RWfu6aXoDSyM-2KwL-YFqNBdbng,18
35
- python_qdairlines_helper-0.1.4.dist-info/RECORD,,
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
- is_success = await two_check_pay_success(
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
- if is_success:
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.login_url
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
- try:
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.dep_city)
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(f"航班预订查询页面,搜索栏-起飞城市<{one_way_booking_dto.dep_city}>输入完成")
72
+ logger.info(
73
+ f"航班预订查询页面,搜索栏-起飞城市<{one_way_booking_dto.dep_city} {one_way_booking_dto.dep_code}>输入完成")
58
74
 
59
- # 3.搜索栏输入抵达城市
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.arr_city)
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(f"航班预订查询页面,搜索栏-抵达城市<{one_way_booking_dto.arr_city}>输入完成")
80
+ logger.info(
81
+ f"航班预订查询页面,搜索栏-抵达城市<{one_way_booking_dto.arr_city} {one_way_booking_dto.arr_code}>输入完成")
65
82
 
66
- # 4.搜索栏输入起飞时间
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
- # 5.点击【查询机票】按钮
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
- # 6. 获取航班基本信息plane locator
79
- flight_info_plane: Locator = await book_search_page.get_flight_info_plane(timeout=timeout)
80
- page_flight_no: str = await book_search_page.get_flight_no(locator=flight_info_plane, timeout=timeout)
81
- if one_way_booking_dto.flight_no not in page_flight_no:
82
- raise RuntimeError(f"航班预订查询页面,没有搜索到航班<{one_way_booking_dto.flight_no}>数据")
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
- # 7. 获取产品类型
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
- # 8. 获取所有的产品
92
- products: Dict[str, Any] = await book_search_page.get_flight_products(timeout=timeout)
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
- # 8.1 判断是否存在该舱位
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
- # 8.2 判断余座
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
- # 8.3. 判断货币符号,是否为 ¥(人民币结算)
134
+ # 9.3. 判断货币符号,是否为 ¥(人民币结算)
107
135
  # 目前都为国内航班,暂不考虑货币种类
108
136
 
109
- # 8.4 判断销售价格是否满足预订需要
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
- # 9. 点击【购票】按钮
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, ItineraryInfoDTO
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
- selector: str = '//div[@class="airlines_cont"]//div[@class="text-center el-row"]'
82
- return await self.get_locator(selector=selector, timeout=timeout)
83
-
84
- async def get_flight_no(self, locator: Locator, timeout: float = 5.0) -> str:
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 get_flight_products(self, timeout: float = 5.0) -> Dict[str, Any]:
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
- selector: str = '//div[@class="airlines_cont"]//div[@class="text-center cabinClasses_info el-row" and not(@style="display: none;")]/table/tbody/tr'
121
- products_locator: Locator = await self.get_locator(selector=selector, timeout=timeout)
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 = dict()
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[cabin] = dict(
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"]/span'
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=(.//td[@class="ticket_num"]/div/span)[1]'
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=(.//td[@class="ticket_num"]/div/span)[2]'
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
- return safe_convert_advanced(value=match.group(1).strip())
204
+ # ⚠️ 正则 \d+ 没有捕获组,只能用 group(0) 或 group()
205
+ return safe_convert_advanced(value=match.group().strip())
187
206
  else:
188
207
  return 999999