"""首页 Feed 列表,对应 Go xiaohongshu/feeds.go。""" from __future__ import annotations import logging from .browse_feeds import human_browse_feed_list from .browse_plan_session import BrowsePlanSession from .cdp import Page from .collect_buffer import is_cancelled, persist_collected, raise_if_cancelled from .errors import BrowseCancelledError, NoFeedsError from .extract_feeds_state import extract_feeds_list, merge_feed_dicts from .human import _sleep_interruptible as sleep_interruptible_sec from .types import Feed from .urls import HOME_URL logger = logging.getLogger(__name__) def list_feeds( page: Page, *, scroll_speed: str = "normal", on_progress=None, ) -> tuple[list[Feed], bool, dict]: """获取首页 Feed 列表(按拟人计划滑动/点开,滚动过程中多次采集)。""" session = BrowsePlanSession("home") def progress(msg: str) -> None: logger.info(msg) if on_progress: on_progress(msg) collected: dict[str, dict] = {} def absorb(label: str) -> int: items = extract_feeds_list(page) added = merge_feed_dicts(collected, items) if added: logger.info("%s: 新增 %d 条,累计 %d 条", label, added, len(collected)) persist_collected(collected) raise_if_cancelled() return added meta = { "browse_scroll_target": session.scroll_target, "browse_click_target": session.click_target, "browse_targets": session.targets.to_dict(), "collect_elapsed_sec": 0.0, "plan_completed": False, } try: progress(session.start_message()) progress("打开首页并加载推荐…") page.navigate(HOME_URL) page.wait_for_load() page.wait_dom_stable() list_url = page.evaluate("window.location.href") or HOME_URL progress("打开首页完成") absorb("首页初次加载") def on_scroll_collect() -> None: absorb("滚动中") human_browse_feed_list( page, list_url, scroll_speed=scroll_speed, browse_mode="list", on_progress=on_progress, after_scroll=on_scroll_collect, run_targets=session.targets, ) if not is_cancelled(): parse_sec = session.targets.parse_sec if parse_sec > 0: progress( f"模拟浏览:提取并解析列表数据(约 {parse_sec:.1f} 秒)…" ) sleep_interruptible_sec(parse_sec) progress("模拟浏览:提取并解析列表数据…") absorb("滚动结束后") progress("模拟浏览:解析完成") if not collected: raise NoFeedsError() meta["plan_completed"] = True meta["collect_elapsed_sec"] = round(session.elapsed_sec(), 1) progress(session.summary(len(collected), plan_completed=True)) return [Feed.from_dict(f) for f in collected.values()], False, meta except BrowseCancelledError: meta["collect_elapsed_sec"] = round(session.elapsed_sec(), 1) if collected: progress( session.summary( len(collected), plan_completed=False, user_cancelled=True, ) ) return [Feed.from_dict(f) for f in collected.values()], True, meta raise