fix: 识别失效商品链接返回首页 (#111)
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user