xpzouying/xiaohongshu-mcp · error

failed to unmarshal feeds

Error message

failed to unmarshal feeds: %w

What it means

Search() reads the final results from window.__INITIAL_STATE__.search.feeds via page.MustEval and JSON-stringifies them in the browser. That JSON is unmarshalled into []Feed in Go; if the browser-side payload does not match the Feed struct (schema drift), unmarshalling fails and the raw json error is wrapped with this prefix.

Solutions

  1. Dump the raw result JSON (log it before Unmarshal) and diff against the Feed struct; update struct tags to match the new schema
  2. Add json.RawMessage / flexible types (e.g. custom UnmarshalJSON for id that accepts string|number) for fields XHS toggles
  3. Upgrade the library/package version if a newer release already tracks the schema change
  4. Unmarshal into map[string]any first as a diagnostic step when the error occurs in production

Example fix

// before
var feeds []Feed
if err := json.Unmarshal([]byte(result), &feeds); err != nil { ... }
// after
type Feed struct {
    ID flexibleString `json:"id"` // accepts string or number
    ...
}
type flexibleString string
func (f *flexibleString) UnmarshalJSON(b []byte) error {
    b = bytes.Trim(b, "\"")
    *f = flexibleString(b); return nil
}
Defensive patterns

Strategy: try-catch

Validate before calling

// sanity-check the payload shape before strict unmarshal
var probe []map[string]any
if err := json.Unmarshal([]byte(result), &probe); err != nil {
    return fmt.Errorf("feeds payload is not a JSON array of objects: %w", err)
}
if len(probe) > 0 {
    if _, ok := probe[0]["id"]; !ok {
        log.Printf("warning: feeds items lack 'id' field — schema drift suspected")
    }
}

Try / catch

feeds, err := action.Search(ctx, keyword, filters...)
if err != nil && strings.Contains(err.Error(), "failed to unmarshal feeds") {
    log.Printf("XHS feeds schema drift: %v", err)
    // degrade gracefully: report empty result + alert instead of failing the job
    return nil, fmt.Errorf("search results unreadable, library likely outdated: %w", err)
}

Prevention

When it happens

Trigger: Search completes and returns non-empty feeds JSON, but XHS changed the feeds schema (renamed/retyped fields, e.g. id/type/card fields) so it no longer fits []Feed; or nested structures changed shape.

Common situations: XHS frontend update changing __INITIAL_STATE__ field names or types (string id vs number); new required-ish fields with different types breaking strict decoding; loading an outdated package version against the current XHS site.

Related errors


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

Appendix: source

Thrown at xiaohongshu/search.go:162

		if (window.__INITIAL_STATE__ &&
		    window.__INITIAL_STATE__.search &&
		    window.__INITIAL_STATE__.search.feeds) {
			const feeds = window.__INITIAL_STATE__.search.feeds;
			const feedsData = feeds.value !== undefined ? feeds.value : feeds._value;
			if (feedsData) {
				return JSON.stringify(feedsData);
			}
		}
		return "";
	}`).String()

	if result == "" {
		return nil, errors.ErrNoFeeds
	}

	var feeds []Feed
	if err := json.Unmarshal([]byte(result), &feeds); err != nil {
		return nil, fmt.Errorf("failed to unmarshal feeds: %w", err)
	}

	return onlyNotes(feeds), nil
}

// feedIDsJS 读当前结果集的 id 列表,用来判断数据有没有换一批。
const feedIDsJS = `() => {
	const f = window.__INITIAL_STATE__?.search?.feeds;
	const v = f ? (f.value !== undefined ? f.value : f._value) : null;
	return v ? v.map(x => x.id).join(",") : "";
}`

func readFeedIDs(page *rod.Page) string {
	res, err := page.Eval(feedIDsJS)
	if err != nil {
		return ""
	}
	return res.Value.Str()

View on GitHub (pinned to 332d196854)