fix: 识别失效商品链接返回首页 (#111)
This commit is contained in:
@@ -21,6 +21,22 @@ from .pdd_device_service import (
|
||||
current_thread_device_service,
|
||||
)
|
||||
from .performance_timing import current_performance_trace
|
||||
from .pdd_page_classifier import (
|
||||
ACTION_NETWORK_ERROR,
|
||||
ACTION_READY,
|
||||
ACTION_REOPEN,
|
||||
ACTION_UNAVAILABLE,
|
||||
PAGE_CAPTCHA,
|
||||
PAGE_GOODS,
|
||||
PAGE_HOME,
|
||||
PAGE_LOGIN_REQUIRED,
|
||||
PAGE_NETWORK_ERROR,
|
||||
PAGE_PAYMENT,
|
||||
PAGE_RISK_CONTROL,
|
||||
GoodsOpenTracker,
|
||||
PddPageObservation,
|
||||
classify_pdd_page,
|
||||
)
|
||||
from .pdd_purchase_adapter import (
|
||||
PddLivePurchaseAdapter,
|
||||
PddPurchaseAdapter,
|
||||
@@ -103,22 +119,7 @@ def _goods_id_from_url(goods_url: str) -> str:
|
||||
|
||||
|
||||
def _page_kind(root: ET.Element, current_package: str) -> str:
|
||||
combined = " ".join(_labels(root))
|
||||
if any(marker in combined for marker in _CAPTCHA_MARKERS):
|
||||
return "captcha"
|
||||
if any(marker in combined for marker in _LOGIN_MARKERS):
|
||||
return "login_required"
|
||||
if any(marker in combined for marker in _RISK_MARKERS):
|
||||
return "risk_control"
|
||||
if any(marker in combined for marker in _PAYMENT_MARKERS):
|
||||
return "payment"
|
||||
if any(marker in combined for marker in _FINAL_SUBMIT_MARKERS):
|
||||
return "order_confirmation"
|
||||
if current_package == PDD_PACKAGE_NAME and any(
|
||||
marker in combined for marker in _READY_MARKERS
|
||||
):
|
||||
return "goods"
|
||||
return "unknown"
|
||||
return classify_pdd_page(root, current_package).kind
|
||||
|
||||
|
||||
def _selected(root: ET.Element, target: str) -> bool:
|
||||
@@ -270,6 +271,7 @@ class U2PddPurchaseAdapter(PddPurchaseAdapter):
|
||||
self._session = self._device_service.connect(self._device_address)
|
||||
self._device = self._session.__enter__()
|
||||
current = self._session.initial_app_state
|
||||
before_open = self._read_observation(current)
|
||||
trace = current_performance_trace()
|
||||
if current.get("package") != PDD_PACKAGE_NAME:
|
||||
stage = trace.stage("pdd_start_or_wait") if trace else nullcontext()
|
||||
@@ -287,7 +289,7 @@ class U2PddPurchaseAdapter(PddPurchaseAdapter):
|
||||
stage = trace.stage("open_url") if trace else nullcontext()
|
||||
with stage:
|
||||
self._device.open_url(goods_url)
|
||||
self._wait_for_page("goods", self._page_timeout)
|
||||
self._wait_for_goods_page(goods_url, before_open)
|
||||
except PddPurchaseError:
|
||||
raise
|
||||
except PddDeviceError as exc:
|
||||
@@ -445,12 +447,21 @@ class U2PddPurchaseAdapter(PddPurchaseAdapter):
|
||||
if session is not None:
|
||||
session.__exit__(None, None, None)
|
||||
|
||||
def _wait_for_page(self, expected: str, timeout: float) -> str:
|
||||
def _wait_for_goods_page(
|
||||
self,
|
||||
goods_url: str,
|
||||
before_open: Optional[PddPageObservation],
|
||||
) -> str:
|
||||
"""确认本次深链进入新商品页;稳定首页时只重开一次。"""
|
||||
|
||||
trace = current_performance_trace()
|
||||
ready_stage = trace.stage("goods_page_ready") if trace else nullcontext()
|
||||
with ready_stage:
|
||||
deadline = self._monotonic() + timeout
|
||||
opened_at = self._monotonic()
|
||||
deadline = opened_at + self._page_timeout
|
||||
tracker = GoodsOpenTracker(before_open, opened_at)
|
||||
last_kind = "unknown"
|
||||
last_recorded = ""
|
||||
first_dump = True
|
||||
while self._monotonic() < deadline:
|
||||
self._check_cancelled("purchase_open_goods")
|
||||
@@ -463,26 +474,94 @@ class U2PddPurchaseAdapter(PddPurchaseAdapter):
|
||||
else:
|
||||
xml_data = self._dump_hierarchy()
|
||||
root = _parse_xml(xml_data)
|
||||
last_kind = _page_kind(root, str(current.get("package") or ""))
|
||||
if last_kind == expected:
|
||||
observation = classify_pdd_page(
|
||||
root, str(current.get("package") or "")
|
||||
)
|
||||
last_kind = observation.kind
|
||||
if trace is not None and last_kind != last_recorded:
|
||||
trace.record(
|
||||
"pdd_page_transition",
|
||||
0,
|
||||
f"attempt_{tracker.attempt}_{last_kind}",
|
||||
)
|
||||
last_recorded = last_kind
|
||||
if last_kind in {
|
||||
PAGE_CAPTCHA,
|
||||
PAGE_LOGIN_REQUIRED,
|
||||
PAGE_RISK_CONTROL,
|
||||
PAGE_PAYMENT,
|
||||
}:
|
||||
self._raise_special_page(last_kind)
|
||||
decision = tracker.observe(observation, self._monotonic())
|
||||
if decision.action == ACTION_READY:
|
||||
if trace is not None:
|
||||
trace.checkpoint("end_to_end_total")
|
||||
return xml_data
|
||||
if last_kind in {
|
||||
"captcha",
|
||||
"login_required",
|
||||
"risk_control",
|
||||
"payment",
|
||||
}:
|
||||
self._raise_special_page(last_kind)
|
||||
if decision.action == ACTION_REOPEN:
|
||||
with (
|
||||
trace.stage("open_url_retry")
|
||||
if trace is not None
|
||||
else nullcontext()
|
||||
):
|
||||
device.open_url(goods_url)
|
||||
tracker.reopened(self._monotonic())
|
||||
last_recorded = ""
|
||||
continue
|
||||
if decision.action == ACTION_UNAVAILABLE:
|
||||
raise PddPurchaseError(
|
||||
"PDD_GOODS_UNAVAILABLE",
|
||||
"商品链接已失效,PDD 无法打开商品详情页并返回了首页",
|
||||
step="purchase_open_goods",
|
||||
retryable=False,
|
||||
diagnostics={
|
||||
"page_kind": PAGE_HOME,
|
||||
"open_attempts": 2,
|
||||
},
|
||||
)
|
||||
if decision.action == ACTION_NETWORK_ERROR:
|
||||
raise PddPurchaseError(
|
||||
"PDD_PAGE_NETWORK_ERROR",
|
||||
"PDD 商品页网络或服务异常,请稍后重试",
|
||||
step="purchase_open_goods",
|
||||
retryable=True,
|
||||
diagnostics={"page_kind": PAGE_NETWORK_ERROR},
|
||||
)
|
||||
self._sleep(0.25)
|
||||
if tracker.stale_goods_seen:
|
||||
raise PddPurchaseError(
|
||||
"PDD_GOODS_IDENTITY_UNCONFIRMED",
|
||||
"打开商品链接后仍停留在原商品页,无法确认本次目标商品",
|
||||
step="purchase_open_goods",
|
||||
retryable=True,
|
||||
diagnostics={
|
||||
"page_kind": PAGE_GOODS,
|
||||
"open_attempts": tracker.attempt,
|
||||
},
|
||||
)
|
||||
raise PddPurchaseError(
|
||||
"PDD_PAGE_TIMEOUT",
|
||||
f"等待 PDD {expected} 页面超时,最后页面为 {last_kind}",
|
||||
f"等待 PDD 商品详情页超时,最后页面为 {last_kind}",
|
||||
step="purchase_open_goods",
|
||||
retryable=True,
|
||||
diagnostics={
|
||||
"page_kind": last_kind,
|
||||
"open_attempts": tracker.attempt,
|
||||
},
|
||||
)
|
||||
|
||||
def _read_observation(
|
||||
self, current: Mapping[str, Any]
|
||||
) -> Optional[PddPageObservation]:
|
||||
"""读取打开前页面;失败时不阻止后续按新页面重新判断。"""
|
||||
|
||||
try:
|
||||
root = _parse_xml(self._dump_hierarchy())
|
||||
return classify_pdd_page(
|
||||
root, str(current.get("package") or "")
|
||||
)
|
||||
except (PddPurchaseError, RuntimeError, OSError):
|
||||
return None
|
||||
|
||||
def _wait_for_confirmation_panel(self) -> str:
|
||||
deadline = self._monotonic() + self._panel_timeout
|
||||
while self._monotonic() < deadline:
|
||||
|
||||
Reference in New Issue
Block a user