navigate.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. """步骤:在搜索结果中切换下一条视频。"""
  2. from __future__ import annotations
  3. from playwright.sync_api import Page
  4. from collectors.douyin.common import human_pause
  5. from collectors.douyin.panel import (
  6. _ensure_video_page_focus,
  7. close_comment_panel,
  8. )
  9. def _ensure_search_video_modal(page: Page) -> None:
  10. """确认仍在搜索结果的视频弹层,而不是作者作品流。"""
  11. url = page.url or ""
  12. if "/search/" not in url:
  13. raise RuntimeError(f"当前不在搜索结果页,无法在搜索列表中切视频: {url}")
  14. if "modal_id=" not in url and "/video/" not in url:
  15. # 有的状态只有 aweme 节点;仍要求停留在 search
  16. print(f"[切视频] 警告:URL 无 modal_id,但仍在搜索页: {url}")
  17. def _focus_search_video_player(page: Page) -> None:
  18. """把焦点放到搜索弹层左侧视频区,避免方向键作用在评论/AI 输入框。"""
  19. try:
  20. box = page.locator(
  21. '[data-e2e="feed-active-video"], [data-e2e="modal-video-container"]'
  22. ).first.bounding_box()
  23. if box:
  24. page.mouse.click(
  25. box["x"] + min(box["width"] * 0.35, 220),
  26. box["y"] + box["height"] * 0.45,
  27. )
  28. human_pause(0.4, 0.8)
  29. return
  30. except Exception:
  31. pass
  32. page.mouse.click(360, 360)
  33. human_pause(0.3, 0.6)
  34. def _read_current_video_id(page: Page) -> str:
  35. """读取当前活跃视频 ID(modal_id / aweme-id)。"""
  36. return (
  37. page.evaluate(
  38. """() => {
  39. const modalId = new URLSearchParams(location.search).get('modal_id') || '';
  40. const root = document.querySelector('[data-e2e="feed-active-video"]')
  41. || document.querySelector('[data-e2e="modal-video-container"]')
  42. || document;
  43. const infoEl = root.querySelector('[data-e2e="video-info"][data-e2e-aweme-id]')
  44. || root.querySelector('[data-e2e="video-info"]');
  45. const awemeId = infoEl ? (infoEl.getAttribute('data-e2e-aweme-id') || '') : '';
  46. return awemeId || modalId || '';
  47. }"""
  48. )
  49. or ""
  50. )
  51. def go_to_next_video(page: Page, prev_video_id: str = "") -> str:
  52. """
  53. 在「搜索结果」视频弹层中切换到下一条(不是作者作品流)。
  54. 返回新 video_id;失败抛错。
  55. """
  56. _ensure_search_video_modal(page)
  57. close_comment_panel(page)
  58. human_pause(0.8, 1.5)
  59. _focus_search_video_player(page)
  60. before = (prev_video_id or "").strip() or _read_current_video_id(page)
  61. def _changed() -> str:
  62. _ensure_search_video_modal(page)
  63. now = _read_current_video_id(page)
  64. return now if now and now != before else ""
  65. page.keyboard.press("ArrowDown")
  66. human_pause(1.5, 2.5)
  67. new_id = _changed()
  68. if new_id:
  69. print(f"已切换到下一条搜索视频: {new_id}")
  70. _ensure_video_page_focus(page)
  71. return new_id
  72. page.keyboard.press("ArrowDown")
  73. human_pause(1.5, 2.5)
  74. new_id = _changed()
  75. if new_id:
  76. print(f"已切换到下一条搜索视频(二次方向键): {new_id}")
  77. _ensure_video_page_focus(page)
  78. return new_id
  79. # 滚轮只作用在左侧视频区
  80. try:
  81. box = page.locator(
  82. '[data-e2e="feed-active-video"], [data-e2e="modal-video-container"]'
  83. ).first.bounding_box()
  84. if box:
  85. page.mouse.move(box["x"] + box["width"] * 0.3, box["y"] + box["height"] * 0.5)
  86. page.mouse.wheel(0, 1400)
  87. human_pause(1.5, 2.5)
  88. except Exception:
  89. pass
  90. new_id = _changed()
  91. if new_id:
  92. print(f"已切换到下一条搜索视频(滚轮): {new_id}")
  93. _ensure_video_page_focus(page)
  94. return new_id
  95. raise RuntimeError(f"未能在搜索结果中切换到下一条视频(仍停留在 {before or '未知'})")