perf: 优化颜色采集等待时间 (#44)

This commit is contained in:
chengma
2026-08-09 10:10:15 +08:00
parent 6d40c1e986
commit ed72e62eda
2 changed files with 238 additions and 29 deletions
+105 -29
View File
@@ -7,6 +7,7 @@
from __future__ import annotations
import hashlib
import math
import re
import time
import xml.etree.ElementTree as ET
@@ -638,6 +639,10 @@ class PddCollectService:
max_goods_page_swipes: int = 12,
max_spec_swipes: int = 12,
max_sku_count: int = 200,
color_poll_interval: float = 0.1,
color_selection_timeout: float = 0.6,
color_price_timeout: float = 1.2,
horizontal_swipe_settle_interval: float = 0.1,
artifact_directory: Optional[Path] = None,
) -> None:
self._device_service = device_service
@@ -654,6 +659,21 @@ class PddCollectService:
self._max_goods_page_swipes = max_goods_page_swipes
self._max_spec_swipes = max_spec_swipes
self._max_sku_count = max_sku_count
timing_values = {
"颜色轮询间隔": color_poll_interval,
"颜色选中超时": color_selection_timeout,
"颜色价格超时": color_price_timeout,
"水平滑动稳定间隔": horizontal_swipe_settle_interval,
}
for name, value in timing_values.items():
if value <= 0:
raise ValueError(f"{name}必须大于 0 秒")
self._color_poll_interval = color_poll_interval
self._color_selection_timeout = color_selection_timeout
self._color_price_timeout = color_price_timeout
self._horizontal_swipe_settle_interval = (
horizontal_swipe_settle_interval
)
self._artifact_directory = artifact_directory
self._last_goods_xml: Optional[str] = None
self._goods_screens_checked = 0
@@ -953,13 +973,15 @@ class PddCollectService:
current_row_colors: list[DimensionValue] = []
stable_edge_reads = 0
previous_signature: Optional[tuple[tuple[str, Bounds], ...]] = None
pending_xml: Optional[str] = None
for swipe_count in range(self._max_spec_swipes + 1):
self._check_cancelled()
# 每次只处理一个节点;点击可能让列表自动移动,下一项必须
# 从最新 XML 重新计算,不能继续使用点击前的旧坐标。
while True:
xml_data = self._dump_hierarchy(device)
xml_data = pending_xml or self._dump_hierarchy(device)
pending_xml = None
rows = self._visible_color_rows(xml_data)
if row_index >= len(rows):
raise PddCollectError(
@@ -998,7 +1020,7 @@ class PddCollectService:
if stable_edge_reads >= 2 or swipe_count >= self._max_spec_swipes:
break
root = _parse_xml(self._dump_hierarchy(device))
root = _parse_xml(xml_data)
region = self._horizontal_region(root)
if region is None:
break
@@ -1008,7 +1030,9 @@ class PddCollectService:
horizontal=True,
reverse=not move_right,
)
self._sleep(0.35)
pending_xml = self._wait_for_horizontal_change(
device, signature
)
# 操作采用蛇形以减少无效滑动;输出仍恢复成页面自然的
# “每行从左到右”顺序,方便 Admin 下拉框稳定展示。
@@ -1030,13 +1054,10 @@ class PddCollectService:
previous_signature: Optional[tuple[tuple[str, Bounds], ...]] = None
stable_edge_reads = 0
xml_data = self._dump_hierarchy(device)
for _ in range(self._max_spec_swipes):
self._check_cancelled()
xml_data = self._dump_hierarchy(device)
rows = self._visible_color_rows(xml_data)
signature = tuple(
(item.text, item.bounds) for row in rows for item in row
)
signature = self._color_view_signature(xml_data)
if signature == previous_signature:
stable_edge_reads += 1
else:
@@ -1052,7 +1073,7 @@ class PddCollectService:
self._swipe_region(
device, region, horizontal=True, reverse=True
)
self._sleep(0.35)
xml_data = self._wait_for_horizontal_change(device, signature)
def _click_and_sample_color(
self, device: Any, target: str
@@ -1080,11 +1101,20 @@ class PddCollectService:
(bounds[1] + bounds[3]) // 2,
)
selection_deadline = self._monotonic() + self._color_selection_timeout
selection_sleep_limit = math.ceil(
self._color_selection_timeout / self._color_poll_interval
)
selection_sleeps = 0
price_deadline: Optional[float] = None
price_sleep_limit = math.ceil(
self._color_price_timeout / self._color_poll_interval
)
price_sleeps = 0
previous_price: Optional[tuple[int, Optional[str], Optional[int]]] = None
stable_price_reads = 0
for _ in range(8):
while True:
self._check_cancelled()
self._sleep(0.2)
latest_xml = self._dump_hierarchy(device)
latest_root = _parse_xml(latest_xml)
snapshot = parse_spec_panel(latest_xml)
@@ -1097,28 +1127,74 @@ class PddCollectService:
# 只能以完整可见可点击节点未报错的点击作为降级证据。
selection_confirmed = selected or not selection_exposed
if snapshot.price_cent is None or not selection_confirmed:
now = self._monotonic()
if not selection_confirmed:
stable_price_reads = 0
previous_price = None
continue
current_price = (
snapshot.price_cent,
snapshot.raw_price,
snapshot.list_price_cent,
)
if current_price == previous_price:
stable_price_reads += 1
else:
stable_price_reads = 1
previous_price = current_price
if stable_price_reads >= 2:
return ColorPriceSample(
snapshot.price_cent,
snapshot.raw_price,
snapshot.list_price_cent,
selection_timed_out = (
now >= selection_deadline
or selection_sleeps >= selection_sleep_limit
)
if selection_timed_out:
return ColorPriceSample(None, None, None)
else:
if price_deadline is None:
price_deadline = now + self._color_price_timeout
if snapshot.price_cent is None:
stable_price_reads = 0
previous_price = None
else:
current_price = (
snapshot.price_cent,
snapshot.raw_price,
snapshot.list_price_cent,
)
if current_price == previous_price:
stable_price_reads += 1
else:
stable_price_reads = 1
previous_price = current_price
if stable_price_reads >= 2:
return ColorPriceSample(
snapshot.price_cent,
snapshot.raw_price,
snapshot.list_price_cent,
)
if now >= price_deadline or price_sleeps >= price_sleep_limit:
return ColorPriceSample(None, None, None)
return ColorPriceSample(None, None, None)
self._sleep(self._color_poll_interval)
if selection_confirmed:
price_sleeps += 1
else:
selection_sleeps += 1
def _color_view_signature(
self, xml_data: str | bytes
) -> tuple[tuple[str, Bounds], ...]:
"""返回颜色视口签名,用于判断水平滑动是否已经更新页面。"""
return tuple(
(item.text, item.bounds)
for row in self._visible_color_rows(xml_data)
for item in row
)
def _wait_for_horizontal_change(
self,
device: Any,
previous_signature: tuple[tuple[str, Bounds], ...],
) -> str:
"""水平滑动后短暂等待;首次未变化时只补等一次。"""
latest_xml = ""
for _ in range(2):
self._check_cancelled()
self._sleep(self._horizontal_swipe_settle_interval)
latest_xml = self._dump_hierarchy(device)
if self._color_view_signature(latest_xml) != previous_signature:
break
return latest_xml
def _collect_size_dimension(self, device: Any) -> Optional[SpecDimension]:
"""颜色采价完成后只滚动并收集尺码文字,不点击尺码。"""