fix: 识别失效商品链接返回首页 (#111)

This commit is contained in:
chengma
2026-08-10 18:20:34 +08:00
parent 3b659eba0a
commit 8c9dbfed44
15 changed files with 802 additions and 62 deletions
+108 -29
View File
@@ -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: