xpzouying/xiaohongshu-mcp · error

悬停筛选按钮失败: %w

Error message

悬停筛选按钮失败: %w

What it means

Search() applies filters by hovering the div.filter button to open the panel. humanize.Hover wraps rod's mouse-move with human-like behavior; any underlying rod error (element detached, page navigated, context timeout, overlay intercept) is wrapped with this prefix.

Source

Thrown at xiaohongshu/search.go:117

	if err != nil {
		return nil, err
	}

	// 注意 .Context(ctx) 会替换掉 NewSearchAction 里设的 60s deadline,必须在其后重新 Timeout,
	// 否则搜索页不 stable 时 MustWaitStable/MustWait 会永久挂起(无 deadline 可依赖)。
	page := s.page.Context(ctx).Timeout(60 * time.Second)

	searchURL := makeSearchURL(keyword)
	page.MustNavigate(searchURL)
	page.MustWaitStable()
	page.MustWait(`() => window.__INITIAL_STATE__ !== undefined`)
	humanize.Delay(ctx, humanize.AfterNavigate)

	if len(pending) > 0 {
		// 悬停在筛选按钮上展开面板
		filterButton := page.MustElement(`div.filter`)
		if err := humanize.Hover(filterButton); err != nil {
			return nil, fmt.Errorf("悬停筛选按钮失败: %w", err)
		}
		humanize.Delay(ctx, humanize.BeforeClick)

		// 等待筛选面板出现
		page.MustWait(`() => document.querySelector('div.filter-panel') !== null`)

		// 记下筛选前的结果,用来判断筛选后的数据什么时候到位
		before := readFeedIDs(page)

		// 用 ClickNoWait:筛选面板是 hover 浮层,rod 的 WaitInteractable 会误判被遮挡而死等;
		// ClickNoWait 移进面板内选项(维持 hover、面板不关)再点。
		for _, pf := range pending {
			option, err := findFilterOption(page, pf)
			if err != nil {
				return nil, err
			}
			humanize.Delay(ctx, humanize.BeforeClick)
			if err := humanize.ClickNoWait(option); err != nil {

View on GitHub (pinned to 332d196854)

Solutions

  1. Inspect the live search page: confirm div.filter still exists; update the selector if XHS changed markup
  2. Add an explicit wait for the filter button before hovering instead of relying on MustWaitStable
  3. Increase page timeout for slow networks
  4. Check you are actually logged in — a login wall replaces the results page and breaks all selectors

Example fix

// before
filterButton := page.MustElement(`div.filter`)
if err := humanize.Hover(filterButton); err != nil { ... }
// after
filterButton, err := page.Timeout(10*time.Second).Element(`div.filter`)
if err != nil { return nil, fmt.Errorf("筛选按钮未出现,页面结构可能已变化: %w", err) }
if err := humanize.Hover(filterButton); err != nil { ... }
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-flight: make sure the results page actually rendered the filter button
if _, err := page.Timeout(10*time.Second).Element(`div.filter`); err != nil {
    return fmt.Errorf("search page missing filter button — likely login wall or UI change: %w", err)
}

Try / catch

res, err := action.Search(ctx, keyword, filters...)
if err != nil && strings.Contains(err.Error(), "悬停筛选按钮失败") {
    // once: re-navigate and retry with fresh page state
    log.Printf("filter hover failed; retrying once with fresh page")
    return action.Search(ctx, keyword, filters...)
}

Prevention

When it happens

Trigger: Search called with non-empty filters when: the search results page layout changed so div.filter is missing/covered, the page did not finish rendering before hover, the 60s page timeout fired, or a modal/overlay intercepts the pointer.

Common situations: XHS A/B test removing or renaming the filter button; slow network leaving the page unstable; running headless with a tiny viewport where the button is off-screen or covered; login wall appearing instead of results.

Related errors


AI-assisted analysis of xpzouying/xiaohongshu-mcp@332d196854 (2026-09-05). Data as JSON: /api/errors/973d2d3f553a3477. Report an issue: GitHub.