Xiaohongshu Trending Short Videos
Purpose
Return the currently-trending short-video posts on Xiaohongshu (小红书 / RedNote) as a list of 1-line descriptions plus structured metadata (author, like-count, duration, canonical URL). The data source is the homefeed_recommend channel — Xiaohongshu's algorithmic "for-you" feed served to logged-out visitors of https://www.xiaohongshu.com/explore, which is the platform's de-facto trending surface for unauthenticated traffic. Read-only — never click into a note, like, follow, comment, or attempt the login QR/SMS flow.
When to Use
- "What's trending on Xiaohongshu right now?" / "Show me hot short-video posts."
- Daily / hourly snapshots of the explore feed for content-trend dashboards.
- Pre-screening trending videos by category (穿搭 / 美食 / 彩妆 / 影视 / 职场 / 情感 / 家居 / 游戏 / 旅行 / 健身) by switching the
categoryslug. - Anywhere you'd otherwise scrape rendered cards from
/explore. The cards are noise; the underlyingwindow.__INITIAL_STATE__.feed.feeds[]blob has everything in clean JSON.
Workflow
The Xiaohongshu explore page is server-side-rendered with the first batch of feed items embedded inline in window.__INITIAL_STATE__.feed.feeds[]. This is the cheap path — one page-load gives you 20–30 ranked items (mix of type: "video" and type: "normal" (image)) with author, like count, video duration, and the xsec_token needed to construct canonical note URLs. No homefeed API call is required and none is even fired during initial load — the homefeed XHR only fires on scroll-to-load-more, and that request is signed (X-S, X-S-Common, X-t, x-rap-param) by Xiaohongshu's obfuscated xhs-secsdk bundle, so it's not callable from outside a real browser without re-implementing their signature scheme.
Do NOT call browse cloud fetch against /explore and try to parse the result — the same URL served to a non-browser client returns the SSR shell with feeds: [] empty (verified 2026-05-21: shell is 596 KB but feed.feeds is empty arrays). The SSR server gates the populated feed on a real-browser fingerprint. Use browse open … --remote with a stealth + residential-proxy session.
Recommended path — SSR initial-state extraction
-
Create a stealth + residential-proxy Browserbase session. Both flags are mandatory; bare sessions render the shell but the feed comes back empty (the EdgeOne edge gates by TLS/UA fingerprint).
sid=$(browse cloud sessions create --keep-alive --verified --proxies \ | node -e "let s='';process.stdin.on('data',c=>s+=c).on('end',()=>process.stdout.write(JSON.parse(s).id))") -
Open the explore page. The login QR-code modal opens automatically — it's an overlay only; the feed is rendered behind it and the SSR JSON is already in the DOM. You can ignore the modal entirely (or press
Escapefor a clean screenshot — it doesn't affect data extraction).browse open "https://www.xiaohongshu.com/explore" --remote --session "$sid" browse wait load --remote --session "$sid" browse wait timeout 3000 --remote --session "$sid" # let hydration settle -
Grab the rendered HTML body.
browse get html body --remote --session "$sid" > /tmp/explore.html -
Extract
window.__INITIAL_STATE__and parsefeed.feeds[]. The blob is JSON-shaped (Vue SSR serialization), thefeeds:[ … ]array sits insidestate.feed. Walk brackets from the"feeds":[literal until depth returns to 0 — substring extraction is more reliable than regex on this 45 KB blob.const html = fs.readFileSync('/tmp/explore.html', 'utf8'); const m = html.match(/window\.__INITIAL_STATE__=(\{[\s\S]*?\})<\/script>/); const blob = m[1]; const fi = blob.indexOf('"feeds":['); let depth = 0, start = fi + 8, end = start; for (let i = start; i < blob.length; i++) { if (blob[i] === '[') depth++; else if (blob[i] === ']') { if (--depth === 0) { end = i + 1; break; } } } const feeds = JSON.parse(blob.slice(start, end)); -
Filter to videos and project the fields.
noteCard.type === "video"is the video filter;"normal"is an image-only post.displayTitleis the 1-line description we want.const videos = feeds .filter(f => f?.noteCard?.type === 'video') .map(f => ({ title: f.noteCard.displayTitle, // the 1-liner author: f.noteCard.user.nickname || f.noteCard.user.nickName, like_count: f.noteCard.interactInfo.likedCount, // pre-formatted CN string e.g. "1.4万" duration_seconds: f.noteCard.video?.capa?.duration, note_id: f.id, xsec_token: f.xsecToken, url: `https://www.xiaohongshu.com/explore/${f.id}?xsec_token=${f.xsecToken}&xsec_source=pc_feed`, note_type: 'video' })); -
Release the session.
browse cloud sessions update "$sid" --status REQUEST_RELEASE
Optional — category filter
To restrict the feed to a single category instead of the default homefeed_recommend mix, append ?channel_id={category} to the URL. Observed category ids (from state.channel.categories[] on the same page load):
channel_id | Category |
|---|---|
homefeed_recommend | 推荐 (default — mixed) |
homefeed.fashion_v3 | 穿搭 |
homefeed.food_v3 | 美食 |
homefeed.cosmetics_v3 | 彩妆 |
homefeed.movie_and_tv_v3 | 影视 |
homefeed.career_v3 | 职场 |
homefeed.love_v3 | 情感 |
homefeed.household_product_v3 | 家居 |
homefeed.gaming_v3 | 游戏 |
homefeed.travel_v3 | 旅行 |
homefeed.fitness_v3 | 健身 |
There is no homefeed.video_feed_v3 channel — all categories are mixed video+image, filter client-side on noteCard.type === "video".
Want more than 25 items? (Not recommended)
Each SSR load returns 20–30 ranked items. Subsequent items come from POST https://edith.xiaohongshu.com/api/sns/web/v1/homefeed, but every request must carry valid X-S, X-S-Common, X-t, and x-rap-param headers computed by Xiaohongshu's obfuscated client signature SDK. Reproducing those headers outside the browser is not viable — they rotate the algorithm regularly. If you genuinely need more than one SSR-page worth of items, drive the browser further: scroll the live page (browse mouse scroll 400 400 0 3000) and the JS will fire the signed homefeed POST itself, then re-extract feed.feeds[] from the updated DOM (it's mutated in place).
Site-Specific Gotchas
browse cloud fetchreturns the empty SSR shell. Same URL, same--proxiesflag, but the EdgeOne / Tencent CDN gates the populated feed on a real-browser fingerprint (TLS, UA, sec-ch-ua headers). Verified 2026-05-21: cloud fetch returned 596 KB of HTML with"feeds":[]in the initial state, while abrowse openbrowser session got the populated 25-item feed inline. Always use a full browser session.- Stealth + residential proxy is mandatory. A bare Browserbase session loads
/explorebut the SSR-embedded feed is empty. Both--verifiedand--proxiesflags are required. - The login QR modal is cosmetic, not a wall. It overlays the feed but the data is already in the DOM behind it. You do not need to dismiss it for extraction. Press
Escapeonly if you want a clean screenshot. likedCountis a pre-formatted Chinese-style string, not an integer. Values like"1.4万"(14,000),"9.2万"(92,000),"1185","2万"(20,000). If you need a numeric value, parse: trim"万"and multiply by 10,000;"亿"× 100,000,000.displayTitlemay be truncated. Cards show ~22 Chinese chars before truncation (the card is fixed-width). The SSR JSON does NOT contain a separate full-title field —displayTitleis what you get. For the full body text, you'd need to GET/explore/{noteId}?xsec_token=…and parse the note-detail page (out of scope for trending).xsec_tokenis mandatory in the canonical URL. Direct/explore/{noteId}without the token redirects to the explore root or returns a 404-like state. Always carry thexsecTokenfrom the same SSR snapshot — tokens are scoped to the request session and may stop working after a few hours.noteCard.typevalues observed:"video"(short video, hasvideo.capa.durationseconds) and"normal"(image post, no video block). No other types seen in the recommend feed. Default mix is roughly 50/50 video/image (verified iter-1: 12 video / 13 normal out of 25).user.nicknamevsuser.nickName— both fields exist on the same object and usually have the same value, but for some users onlynickNameis populated (camelCase) and for others onlynickname(lowercase). Always coalesce:user.nickname || user.nickName.- The
feedsarray starts at SSR index 0 but Xiaohongshu marks individually-loaded items withssrRendered: true. Items appended later by the signed homefeed XHR do not carry this flag — useful for telling "first paint" items apart from scroll-loaded items if you ever extend the skill. - No
homefeed.video_feed_v3channel exists despite plausible-looking task hints. Video isolation is client-side only. (The task prompt may suggest this URL — it returns the same mixed recommend feed.) - Direct calls to
POST /api/sns/web/v1/homefeedare non-viable. They requireX-S/X-S-Common/X-t/x-rap-paramsignatures generated byxhs-secsdk/mnscoreobfuscated JS. Don't waste time trying to reverse-engineer the signature scheme — it rotates. Use the browser path. - The
/website/hot-listendpoint (热搜 / hot search) is a different thing — it lists trending search keywords, not trending videos. Don't confuse it with this skill's surface. - Scrolling-triggered API calls return 204 first, then 200 — the 204 is the CORS preflight (OPTIONS), the 200 is the actual POST. Both must succeed for the in-browser scroll-to-load-more to populate the next batch.
- Geographic accessibility: Xiaohongshu is open globally without a CN-IP requirement (verified from US-region residential-proxy session, 2026-05-21). The login wall only blocks personalized features (search, follow, save) — public trending feed is anonymous-readable.
Expected Output
{
"success": true,
"source": "explore-ssr-initial-state",
"channel": "homefeed_recommend",
"fetched_at": "2026-05-21T23:02:30Z",
"video_count": 12,
"videos": [
{
"title": "李若彤|好吃不胖更抗炎~再不为三餐发愁",
"author": "李若彤",
"like_count": "8664",
"duration_seconds": 244,
"note_id": "6453db04000000001300c1bb",
"xsec_token": "ABmEUv4MbDhlwDICDI6NNR8RoC0SFuGxPHWnM2uq0iBuU=",
"url": "https://www.xiaohongshu.com/explore/6453db04000000001300c1bb?xsec_token=ABmEUv4MbDhlwDICDI6NNR8RoC0SFuGxPHWnM2uq0iBuU=&xsec_source=pc_feed",
"note_type": "video"
},
{
"title": "新疆旅行vlog🍃是我梦里才会出现的场景啊",
"author": "林森Live",
"like_count": "1.4万",
"duration_seconds": 286,
"note_id": "6479822c0000000013002886",
"xsec_token": "ABtkcQnFP-73URrNUeGSyeTRerpGI9UYTnt_w79xFVNqw=",
"url": "https://www.xiaohongshu.com/explore/6479822c0000000013002886?xsec_token=ABtkcQnFP-73URrNUeGSyeTRerpGI9UYTnt_w79xFVNqw=&xsec_source=pc_feed",
"note_type": "video"
},
{
"title": "加油啊,宝………",
"author": "鹿十元",
"like_count": "9.2万",
"duration_seconds": 287,
"note_id": "644fb4db0000000007038e63",
"xsec_token": "ABbpHsLUVDfzFGQhVGvLVuGtVX4bVBjsmPJILobMCubdg=",
"url": "https://www.xiaohongshu.com/explore/644fb4db0000000007038e63?xsec_token=ABbpHsLUVDfzFGQhVGvLVuGtVX4bVBjsmPJILobMCubdg=&xsec_source=pc_feed",
"note_type": "video"
}
],
"error_reasoning": null
}
Failure outcomes (less common — the SSR path is stable):
// Stealth-less session: shell renders but feed is empty
{ "success": false, "videos": [], "error_reasoning": "SSR returned empty feeds[] — session is missing --verified/--proxies stealth or was fingerprinted as bot." }
// Non-browser fetch path: same symptom as above
{ "success": false, "videos": [], "error_reasoning": "browse cloud fetch returns empty feed; must use browse open with a real browser session." }