feat: 滚动采集评价数量和店铺名 (#40)
This commit is contained in:
@@ -60,6 +60,25 @@ class FakeCollectDevice:
|
||||
self.swipe_panel_states.append(self.panel_open)
|
||||
|
||||
|
||||
class GoodsMetadataScrollDevice(FakeCollectDevice):
|
||||
"""模拟评价和店铺分别出现在商品页后续屏幕。"""
|
||||
|
||||
def __init__(self, pages, spec_xml):
|
||||
super().__init__(pages[0], spec_xml)
|
||||
self.pages = pages
|
||||
self.page_index = 0
|
||||
|
||||
def dump_hierarchy(self):
|
||||
if self.panel_open:
|
||||
return self.spec_xml
|
||||
return self.pages[self.page_index]
|
||||
|
||||
def swipe(self, *args, **kwargs):
|
||||
super().swipe(*args, **kwargs)
|
||||
if not self.panel_open:
|
||||
self.page_index = min(self.page_index + 1, len(self.pages) - 1)
|
||||
|
||||
|
||||
class LoadingDevice(FakeCollectDevice):
|
||||
def dump_hierarchy(self):
|
||||
return '<hierarchy><node text="正在加载" /></hierarchy>'
|
||||
@@ -251,6 +270,25 @@ class PddCollectParserTest(unittest.TestCase):
|
||||
self.assertTrue(result.sales.approximate)
|
||||
self.assertEqual(result.reviews.value, 2356)
|
||||
|
||||
def test_parse_review_title_and_shop_name_next_to_enter_shop(self):
|
||||
xml_data = """<hierarchy>
|
||||
<node class="android.widget.FrameLayout" bounds="[0,0][1080,2000]">
|
||||
<node class="android.widget.TextView" text="商品评价(191)"
|
||||
bounds="[30,700][300,760]" />
|
||||
<node class="android.widget.TextView" text="承诺假一赔十"
|
||||
bounds="[30,1050][300,1110]" />
|
||||
<node class="android.widget.TextView" text="小熊服饰店"
|
||||
bounds="[300,1200][650,1260]" />
|
||||
<node class="android.widget.TextView" text="进店"
|
||||
bounds="[850,1200][1030,1260]" />
|
||||
</node>
|
||||
</hierarchy>"""
|
||||
|
||||
result = parse_goods_page(xml_data)
|
||||
|
||||
self.assertEqual(result.reviews.value, 191)
|
||||
self.assertEqual(result.shop_name, "小熊服饰店")
|
||||
|
||||
def test_parse_spec_panel_uses_generic_dimensions_and_cents(self):
|
||||
result = parse_spec_panel(self.spec_xml)
|
||||
self.assertEqual(result.price_cent, 1000)
|
||||
@@ -328,6 +366,70 @@ class PddCollectParserTest(unittest.TestCase):
|
||||
self.assertEqual(data["source"]["device_address"], "USB-001")
|
||||
self.assertIsNone(data["purchase"])
|
||||
|
||||
def test_collect_scrolls_before_spec_panel_and_accumulates_metadata(self):
|
||||
initial_page = self.home_xml.replace(
|
||||
'<node class="android.widget.TextView" text="2356条评价" '
|
||||
'bounds="[330,950][600,1010]" visible-to-user="true" '
|
||||
'enabled="true" />',
|
||||
"",
|
||||
).replace(
|
||||
'<node class="android.widget.TextView" text="店铺:测试服饰旗舰店" '
|
||||
'bounds="[30,1050][600,1110]" visible-to-user="true" '
|
||||
'enabled="true" />',
|
||||
"",
|
||||
)
|
||||
buy_button = """
|
||||
<node class="android.view.ViewGroup" content-desc="¥10.00立即购买"
|
||||
clickable="true" bounds="[500,1700][1080,1950]"
|
||||
visible-to-user="true" enabled="true">
|
||||
<node class="android.widget.TextView" text="¥10.00"
|
||||
bounds="[650,1730][850,1810]"
|
||||
visible-to-user="true" enabled="true" />
|
||||
<node class="android.widget.TextView" text="立即购买"
|
||||
bounds="[650,1820][900,1900]"
|
||||
visible-to-user="true" enabled="true" />
|
||||
</node>
|
||||
"""
|
||||
review_page = f"""<hierarchy>
|
||||
<node class="android.widget.FrameLayout" bounds="[0,0][1080,2000]">
|
||||
<node class="android.widget.TextView" text="商品评价(191)"
|
||||
bounds="[30,700][300,760]" />
|
||||
{buy_button}
|
||||
</node>
|
||||
</hierarchy>"""
|
||||
shop_page = f"""<hierarchy>
|
||||
<node class="android.widget.FrameLayout" bounds="[0,0][1080,2000]">
|
||||
<node class="android.widget.TextView" text="小熊服饰店"
|
||||
bounds="[300,1200][650,1260]" />
|
||||
<node class="android.widget.TextView" text="进店"
|
||||
bounds="[850,1200][1030,1260]" />
|
||||
{buy_button}
|
||||
</node>
|
||||
</hierarchy>"""
|
||||
device = GoodsMetadataScrollDevice(
|
||||
[initial_page, review_page, shop_page],
|
||||
keep_only_one_sku(self.spec_xml),
|
||||
)
|
||||
service = PddCollectService(
|
||||
PddDeviceService(lambda _serial: device),
|
||||
"USB-001",
|
||||
"client-001",
|
||||
sleeper=lambda _seconds: None,
|
||||
max_spec_swipes=0,
|
||||
)
|
||||
|
||||
result = service.collect(
|
||||
FakeTask("https://mobile.yangkeduo.com/goods.html?goods_id=123")
|
||||
)
|
||||
|
||||
self.assertEqual(result.title, "测试纯棉短袖商品")
|
||||
self.assertEqual(result.sales.value, 12000)
|
||||
self.assertEqual(result.reviews.value, 191)
|
||||
self.assertEqual(result.shop_name, "小熊服饰店")
|
||||
self.assertEqual(device.swipe_panel_states[:2], [False, False])
|
||||
self.assertEqual(device.page_index, 2)
|
||||
self.assertTrue(device.clicks)
|
||||
|
||||
def test_pdd_hierarchy_is_ready_even_when_focused_package_is_settings(self):
|
||||
pdd_home_xml = self.home_xml.replace(
|
||||
"<node ", '<node package="com.xunmeng.pinduoduo" '
|
||||
@@ -593,7 +695,7 @@ class PddCollectParserTest(unittest.TestCase):
|
||||
)
|
||||
self.assertIsNone(result.shop_name)
|
||||
|
||||
def test_missing_reviews_does_not_scroll_before_opening_spec_panel(self):
|
||||
def test_missing_reviews_stops_when_goods_page_no_longer_changes(self):
|
||||
home_without_reviews = self.home_xml.replace(
|
||||
'<node class="android.widget.TextView" text="2356条评价" '
|
||||
'bounds="[330,950][600,1010]" visible-to-user="true" '
|
||||
@@ -617,7 +719,38 @@ class PddCollectParserTest(unittest.TestCase):
|
||||
|
||||
self.assertIsNone(result.reviews.raw)
|
||||
self.assertTrue(device.clicks)
|
||||
self.assertTrue(all(device.swipe_panel_states))
|
||||
self.assertEqual(device.swipe_panel_states[:2], [False, False])
|
||||
|
||||
def test_cancel_during_goods_page_scroll_stops_before_spec_panel(self):
|
||||
incomplete_home = self.home_xml.replace(
|
||||
'<node class="android.widget.TextView" text="2356条评价" '
|
||||
'bounds="[330,950][600,1010]" visible-to-user="true" '
|
||||
'enabled="true" />',
|
||||
"",
|
||||
).replace(
|
||||
'<node class="android.widget.TextView" text="店铺:测试服饰旗舰店" '
|
||||
'bounds="[30,1050][600,1110]" visible-to-user="true" '
|
||||
'enabled="true" />',
|
||||
"",
|
||||
)
|
||||
cancelled = {"value": False}
|
||||
device = FakeCollectDevice(incomplete_home, self.spec_xml)
|
||||
service = PddCollectService(
|
||||
PddDeviceService(lambda _serial: device),
|
||||
"USB-001",
|
||||
"client-001",
|
||||
sleeper=lambda _seconds: cancelled.update(value=True),
|
||||
cancelled=lambda: cancelled["value"],
|
||||
)
|
||||
|
||||
with self.assertRaises(PddCollectError) as raised:
|
||||
service.collect(
|
||||
FakeTask("https://mobile.yangkeduo.com/goods.html?goods_id=123")
|
||||
)
|
||||
|
||||
self.assertEqual(raised.exception.code, "PDD_CANCELLED")
|
||||
self.assertEqual(device.swipe_panel_states, [False])
|
||||
self.assertEqual(device.clicks, [])
|
||||
|
||||
def test_spec_panel_timeout_has_stable_code_and_xml_evidence(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
|
||||
Reference in New Issue
Block a user