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
- Inspect the live search page: confirm div.filter still exists; update the selector if XHS changed markup
- Add an explicit wait for the filter button before hovering instead of relying on MustWaitStable
- Increase page timeout for slow networks
- 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
- Use a normal desktop viewport in headless mode
- Verify login state before filtered searches
- After XHS UI deploys, smoke-test the filtered search path
- Keep timeouts generous on slow networks
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.