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
+104 -26
View File
@@ -21,6 +21,22 @@ from urllib.parse import parse_qs, urlparse
from .pdd_device_service import PDD_PACKAGE_NAME, PddDeviceError, PddDeviceService
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 .util.get_size_panle_coord import get_size_panel_coord
@@ -742,13 +758,15 @@ class PddCollectService:
"规格面板没有生成可提交的规格组合",
)
except PddCollectError as exc:
if not exc.diagnostics and self._last_goods_xml:
if self._last_goods_xml:
artifact = self._save_xml("collect-failed", self._last_goods_xml)
if artifact:
exc.diagnostics = {
"artifacts": [artifact],
"goods_screens_checked": self._goods_screens_checked,
}
diagnostics = dict(exc.diagnostics)
diagnostics.setdefault("artifacts", []).append(artifact)
diagnostics.setdefault(
"goods_screens_checked", self._goods_screens_checked
)
exc.diagnostics = diagnostics
raise
except PddDeviceError as exc:
raise PddCollectError(exc.code, exc.message) from exc
@@ -797,6 +815,7 @@ class PddCollectService:
if initial_app_state is not None
else device.app_current()
)
before_open = self._read_page_observation(device, current)
if current.get("package") != PDD_PACKAGE_NAME:
stage = trace.stage("pdd_start_or_wait") if trace else nullcontext()
with stage:
@@ -822,8 +841,12 @@ class PddCollectService:
ready_stage = trace.stage("goods_page_ready") if trace else nullcontext()
with ready_stage:
deadline = self._monotonic() + self._page_timeout
opened_at = self._monotonic()
deadline = opened_at + self._page_timeout
tracker = GoodsOpenTracker(before_open, opened_at)
first_dump = True
last_kind = "unknown"
last_recorded = ""
while self._monotonic() < deadline:
self._check_cancelled()
current = device.app_current()
@@ -834,29 +857,84 @@ class PddCollectService:
else:
xml_data = self._dump_hierarchy(device)
root = _parse_xml(xml_data)
last_labels = _all_labels(root)
pdd_node_count = sum(
1
for node in root.iter("node")
if node.get("package") == PDD_PACKAGE_NAME
observation = classify_pdd_page(
root, str(current.get("package") or "")
)
# 部分 OPPO/ColorOS 设备会一直把无线调试设置页报告为焦点,
# 即使屏幕和无障碍树已经是 PDD。此时以树中真实包名为准。
is_pdd_hierarchy = pdd_node_count >= 3
is_pdd_focused = current.get("package") == PDD_PACKAGE_NAME
if is_pdd_focused or is_pdd_hierarchy:
_raise_special_page(last_labels)
combined = " ".join(last_labels)
loading = "加载中" in combined or "正在加载" in combined
if not loading and any(
marker in combined for marker in _READY_MARKERS
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
self._raise_classified_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
if decision.action == ACTION_REOPEN:
with (
trace.stage("open_url_retry")
if trace is not None
else nullcontext()
):
if trace is not None:
trace.checkpoint("end_to_end_total")
return
self._sleep(0.5)
device.open_url(goods_url)
tracker.reopened(self._monotonic())
last_recorded = ""
continue
if decision.action == ACTION_UNAVAILABLE:
raise PddCollectError(
"PDD_GOODS_UNAVAILABLE",
"商品链接已失效,PDD 无法打开商品详情页并返回了首页",
{"page_kind": PAGE_HOME, "open_attempts": 2},
)
if decision.action == ACTION_NETWORK_ERROR:
raise PddCollectError(
"PDD_PAGE_NETWORK_ERROR",
"PDD 商品页网络或服务异常,请稍后重试",
{"page_kind": PAGE_NETWORK_ERROR},
)
self._sleep(0.25)
if tracker.stale_goods_seen:
raise PddCollectError(
"PDD_GOODS_IDENTITY_UNCONFIRMED",
"打开商品链接后仍停留在原商品页,无法确认本次目标商品",
{"page_kind": PAGE_GOODS, "open_attempts": tracker.attempt},
)
raise PddCollectError(
"PDD_PAGE_TIMEOUT", "等待 PDD 商品详情页加载超时"
"PDD_PAGE_TIMEOUT",
f"等待 PDD 商品详情页加载超时,最后页面为 {last_kind}",
{"page_kind": last_kind, "open_attempts": tracker.attempt},
)
def _read_page_observation(
self, device: Any, current: Mapping[str, Any]
) -> Optional[PddPageObservation]:
"""读取深链打开前页面;读取失败不妨碍后续打开目标链接。"""
try:
root = _parse_xml(self._dump_hierarchy(device))
return classify_pdd_page(
root, str(current.get("package") or "")
)
except (PddCollectError, RuntimeError, OSError):
return None
@staticmethod
def _raise_classified_special_page(kind: str) -> None:
if kind == PAGE_CAPTCHA:
raise PddCollectError(
"PDD_PAGE_CAPTCHA", "PDD 出现安全验证,需要人工处理"
)
if kind == PAGE_LOGIN_REQUIRED:
raise PddCollectError(
"PDD_PAGE_LOGIN_REQUIRED", "PDD 登录已失效,需要人工重新登录"
)
if kind in {PAGE_RISK_CONTROL, PAGE_PAYMENT}:
raise PddCollectError(
"PDD_PAGE_UNKNOWN", "PDD 出现风控或支付页面,已停止采集"
)
def _collect_goods_details(self, device: Any) -> GoodsSnapshot: