fix: scroll recommendation feed container

This commit is contained in:
wangshaoqing 2026-05-26 15:54:44 +08:00
parent d0f6c5e5ab
commit cc1109628f
2 changed files with 72 additions and 11 deletions

View File

@ -404,17 +404,58 @@ def create_human_scroll_plan(
)
def run_scroll_step(page: Any, distance: int) -> bool:
script = f"""
const distance = {distance};
function findMainScrollContainer() {{
const preferredSelectors = ['.tKqwmYAX', '.route-scroll-container', '.semi-tabs-content'];
for (const selector of preferredSelectors) {{
const el = document.querySelector(selector);
if (el && el.scrollHeight > el.clientHeight + 20) {{
return el;
}}
}}
const candidates = Array.from(document.querySelectorAll('*'))
.filter((el) => {{
const rect = el.getBoundingClientRect();
return rect.width > 300
&& rect.height > 200
&& el.scrollHeight > el.clientHeight + 20;
}})
.sort((a, b) => {{
const areaA = a.getBoundingClientRect().width * a.getBoundingClientRect().height;
const areaB = b.getBoundingClientRect().width * b.getBoundingClientRect().height;
return areaB - areaA;
}});
return candidates[0] || null;
}}
const scrollTarget = findMainScrollContainer();
if (scrollTarget) {{
scrollTarget.scrollBy(0, distance);
return true;
}}
return false;
"""
scrolled_container = bool(page.run_js(script))
if not scrolled_container:
page.run_js(f"window.scrollBy(0, {distance});")
return scrolled_container
def run_human_scroll_sequence(page: Any, plan: HumanScrollPlan) -> None:
page.run_js(f"window.scrollBy(0, {plan.down_distance});")
run_scroll_step(page, plan.down_distance)
print(f"[INFO] 向下滚动 {plan.down_distance}px停留 {plan.down_wait:.1f}s")
time.sleep(plan.down_wait)
if plan.reverse_distance > 0:
page.run_js(f"window.scrollBy(0, -{plan.reverse_distance});")
run_scroll_step(page, -plan.reverse_distance)
print(f"[INFO] 小幅回滚 {plan.reverse_distance}px停留 {plan.reverse_wait:.1f}s")
time.sleep(plan.reverse_wait)
forward_distance = plan.reverse_distance * 2
page.run_js(f"window.scrollBy(0, {forward_distance});")
run_scroll_step(page, forward_distance)
if plan.settle_wait > 0:
print(f"[INFO] 继续停留 {plan.settle_wait:.1f}s")

View File

@ -68,6 +68,18 @@ class FakeScrollPage:
self.scripts.append(script)
class FakeContainerScrollPage:
def __init__(self, container_found=True):
self.container_found = container_found
self.scripts = []
def run_js(self, script):
self.scripts.append(script)
if "findMainScrollContainer" in script:
return self.container_found
return None
class DouyinModuleTests(unittest.TestCase):
def test_module_can_import_without_optional_runtime_dependencies(self) -> None:
module = importlib.import_module("Douyin")
@ -184,16 +196,24 @@ class DouyinModuleTests(unittest.TestCase):
)
with mock.patch.object(module.time, "sleep") as mocked_sleep:
module.run_human_scroll_sequence(page, plan)
self.assertEqual(
page.scripts,
[
"window.scrollBy(0, 500);",
"window.scrollBy(0, -120);",
"window.scrollBy(0, 240);",
],
)
self.assertIn("window.scrollBy(0, 500);", page.scripts)
self.assertIn("window.scrollBy(0, -120);", page.scripts)
self.assertIn("window.scrollBy(0, 240);", page.scripts)
mocked_sleep.assert_has_calls([mock.call(2.5), mock.call(1.0), mock.call(3.0)])
def test_run_scroll_step_prefers_main_scroll_container(self) -> None:
module = importlib.import_module("Douyin")
page = FakeContainerScrollPage(container_found=True)
self.assertTrue(module.run_scroll_step(page, 500))
self.assertIn("const distance = 500;", page.scripts[-1])
self.assertIn("scrollTarget.scrollBy(0, distance);", page.scripts[-1])
def test_run_scroll_step_falls_back_to_window_when_container_is_missing(self) -> None:
module = importlib.import_module("Douyin")
page = FakeContainerScrollPage(container_found=False)
self.assertFalse(module.run_scroll_step(page, 500))
self.assertEqual(page.scripts[-1], "window.scrollBy(0, 500);")
def test_ensure_browser_debug_port_ready_accepts_open_port(self) -> None:
module = importlib.import_module("Douyin")
connection = mock.MagicMock()