Xiaohongshu
Search notes, read note details, view comments, browse user profiles, and get the home feed from Xiaohongshu (小红书).
Skill Directory
<SKILL_DIR> is the directory containing this SKILL.md file. Determine it ONCE at the start and reuse it.
Setup (run FIRST — every time, before any operation)
You MUST complete this setup before running any script. Do NOT skip.
sig status xiaohongshu 2>&1
Check the JSON output fields configured and valid:
configured: false→ run Provider Setup below.valid: false(but configured: true) → runsig login xiaohongshu --mode visible, complete the captcha, then re-check.valid: true→ run Vendor Setup (one-time per machine), then execute the user's request.
Provider Setup
- Read
<SKILL_DIR>/references/provider-config.yaml - Append the provider block to
~/.sig/config.yamlunderproviders: - Run
sig login xiaohongshu --mode visible— a browser opens; scan the QR code with the Xiaohongshu app and complete the slider/image captcha if prompted - Verify:
sig status xiaohongshushould showvalid: true
Why the validateRule is so picky — three response shapes
/api/sns/web/unread_count is a permissive endpoint that hides a "partially expired cookie" state behind what looks like a successful response. The provider's validateRule is built specifically to reject it:
| State | /api/sns/web/unread_count returns |
Detected as |
|---|---|---|
| Healthy cookie | {code:0, success:true, data:{unread_count:N, likes:N, ...}} |
✅ valid |
| Cookie partially expired | {code:0, success:true, data:{}} |
❌ rejected by Object.keys(data).length > 0 |
| Cookie fully expired / logged out | {code:-101, success:false, msg:"无登录信息"} |
❌ rejected by code === 0 |
If sig login exits without prompting you, but later API calls fail with empty data — the partially-expired-cookie state was bypassed by a stale rule. Re-pull references/provider-config.yaml and refresh the rule in ~/.sig/config.yaml, then re-login.
What this rule does NOT catch.
unread_countis more permissive than search/feed APIs. A cookie that passes the rule (state #1) may still fail on search if the risk-control / session-token portion has aged out. Ifsig statusshowsvalid:truebut search keeps returning empty data, you're in this gray zone — the only fix issig logout xiaohongshu && sig login xiaohongshu --mode visible. See Diagnosis below for how to confirm.
Vendor Setup (one-time per machine)
Unlike most skills, xiaohongshu signs every request via JS files at <SKILL_DIR>/vendor/static/, evaluated through PyExecJS. The skill ships those source files but not their npm/pip deps — install them once:
node --version # must be >= 18; install from https://nodejs.org if missing
cd <SKILL_DIR>/vendor && npm install # installs crypto-js, jsdom
pip install --user -r <SKILL_DIR>/requirements.txt # PyExecJS, requests, loguru, retry
If pip complains about externally-managed Python (PEP 668 on macOS/Debian), use a venv or pipx.
Smoke-test signing (no network, no cookie needed):
cd <SKILL_DIR>/vendor && python3 -c "
import sys; sys.path.insert(0, '.')
from xhs_utils.xhs_util import generate_x_rap_param, generate_xs_xs_common
xs, xt, xsc = generate_xs_xs_common('a1=test', '/api/sns/web/v1/feed', '', 'POST')
rap = generate_x_rap_param('/api/sns/web/v1/user_posted', '')
print('OK' if xs and rap else 'FAIL')
"
Expect OK. If it fails, see Error Handling.
Running Scripts
All scripts output JSON to stdout. Read operations need a cookie (the public site does not allow anonymous access to most endpoints).
Recommended — sig run injects the cookie as SIG_XIAOHONGSHU_COOKIE:
sig run xiaohongshu -- bash -c 'python3 <SKILL_DIR>/scripts/xiaohongshu_search_note.py --keyword "AI" --page 1'
Alternative — pass --cookie explicitly:
COOKIE=$(sig get xiaohongshu --no-redaction --format value)
python3 <SKILL_DIR>/scripts/xiaohongshu_search_note.py --keyword "AI" --cookie "$COOKIE"
Scripts Reference
All scripts are in this skill's scripts/ directory. Output is JSON to stdout.
| Script | Purpose | Auth |
|---|---|---|
xiaohongshu_search_note.py |
Search notes by keyword | Cookie |
xiaohongshu_get_note_info.py |
Get full note detail by URL | Cookie |
xiaohongshu_get_note_comments.py |
Get top-level comments on a note | Cookie |
xiaohongshu_get_user_info.py |
Get a user's profile | Cookie |
xiaohongshu_get_user_notes.py |
Get notes published by a user (page) | Cookie |
xiaohongshu_get_homefeed.py |
Get home feed recommendations (page) | Cookie |
Script Arguments
xiaohongshu_search_note.py
--keyword(required) — search keyword--page— page number, default1--sort—0general (default),1newest,2most-liked,3most-commented,4most-collected--note-type—0any (default),1video,2image--cookie— override env var
xiaohongshu_get_note_info.py
--url(required) — full note URL likehttps://www.xiaohongshu.com/explore/<id>?xsec_token=...&xsec_source=pc_search--cookie
xiaohongshu_get_note_comments.py
--note-id(required)--xsec-token(required) — comes from a search/feed result--cursor— pagination, default empty--cookie
xiaohongshu_get_user_info.py
--user-id(required)--cookie
xiaohongshu_get_user_notes.py
--user-id(required)--cursor— pagination, default empty--xsec-token,--xsec-source— pass through if available--cookie
xiaohongshu_get_homefeed.py
--category— channel id, defaulthomefeed_recommend--cursor-score— pagination--refresh-type,--note-index— feed iteration state--cookie
Key Concepts
- xsec_token / xsec_source: per-note security tokens issued by the search/feed endpoints. To call
get_note_infoorget_note_comments, you must first callsearch_noteto obtain these tokens (they are embedded in the note URL returned). - Why Node.js? XHS signs every request with
x-s,x-t,x-s-common,x-rap-param, andx-xray-traceidderived from JSVMP-obfuscated JavaScript. The skill ships those JS files (<SKILL_DIR>/vendor/static/) and runs them via PyExecJS, which shells out tonode. Pure-Python signing libraries (e.g. xhshow) currently missx-rap-paramand fail on data APIs with HTTP 406.
Error Handling
| Error | Meaning | Fix |
|---|---|---|
AUTH_REQUIRED |
No cookie available | sig login xiaohongshu --mode visible |
VENDOR_MISSING |
<SKILL_DIR>/vendor/ not populated |
Run <SKILL_DIR>/scripts/sync-vendor.sh |
NODE_MODULES_MISSING |
vendor/node_modules/ missing |
cd <SKILL_DIR>/vendor && npm install |
API_ERROR with msg="'msg'" |
Vendor parser hit KeyError: 'msg' because the API returned {code:0, success:true, data:{}}. The cookie has expired enough to fail on search even though unread_count may still pass. This is the most common failure mode in normal use — sessions age out faster than unread_count's acceptance criteria. |
sig logout xiaohongshu && sig login xiaohongshu --mode visible. If sig login exits immediately without prompting (because the old unread_count-passing cookie is still present), check Diagnosis below first to confirm. |
API_ERROR with code=-101 |
无登录信息 — session is gone |
sig login xiaohongshu --mode visible |
API_ERROR (other) |
Account flagged, vendor schema drift, etc. | sig logout xiaohongshu && sig login xiaohongshu --mode visible. If it persists after re-login, run <SKILL_DIR>/scripts/sync-vendor.sh to refresh the signing JS. |
HTTP_<code> |
Network / transport failure | Check connectivity |
Workflow Examples
Find a topic and read the top result with comments
# 1. Search — output is JSON with a list of notes
sig run xiaohongshu -- bash -c \
'python3 <SKILL_DIR>/scripts/xiaohongshu_search_note.py --keyword "城市探索" --sort 2' \
> /tmp/xhs_search.json
# 2. Extract note_id + xsec_token from the first result.
# Path: items[0].id and items[0].xsec_token
NOTE_ID=$(jq -r '.items[0].id' /tmp/xhs_search.json)
XSEC=$(jq -r '.items[0].xsec_token' /tmp/xhs_search.json)
URL="https://www.xiaohongshu.com/explore/${NOTE_ID}?xsec_token=${XSEC}&xsec_source=pc_search"
# 3. Get full detail
sig run xiaohongshu -- bash -c \
"python3 <SKILL_DIR>/scripts/xiaohongshu_get_note_info.py --url '$URL'"
# 4. Get comments
sig run xiaohongshu -- bash -c \
"python3 <SKILL_DIR>/scripts/xiaohongshu_get_note_comments.py --note-id '$NOTE_ID' --xsec-token '$XSEC'"
Diagnosis: when search fails, do NOT jump to conclusions
sig status xiaohongshu showing valid: true is necessary but not sufficient for search to work. The validateUrl is permissive on purpose (so it can run without XHS request signing), which means a cookie that has aged out enough to fail on search may still pass validation. Before re-logging in or "fixing" anything, triangulate with three probes:
# Probe 1 — what sig itself thinks
sig status xiaohongshu
# Probe 2 — what the validateUrl says (permissive endpoint)
sig run xiaohongshu -- bash -c \
'curl -sL -H "Cookie: $SIG_XIAOHONGSHU_COOKIE" -H "User-Agent: Mozilla/5.0" \
https://edith.xiaohongshu.com/api/sns/web/unread_count'
# Probe 3 — what a stricter authenticated endpoint says
sig run xiaohongshu -- bash -c \
'curl -sL -H "Cookie: $SIG_XIAOHONGSHU_COOKIE" -H "User-Agent: Mozilla/5.0" \
https://edith.xiaohongshu.com/api/sns/web/v2/user/me'
Read the three answers together:
| sig status | unread_count | v2/user/me | Most likely cause | Fix |
|---|---|---|---|---|
| valid:true | code:0 + non-empty data | code:0 + your real user_id | Cookie is healthy. Search itself is failing for another reason — vendor schema drift, transient network, account-level flag. Do NOT re-login first. | Re-run sync-vendor.sh, retry once, only re-login if that doesn't help |
| valid:true | code:0 + non-empty data | code:0 + guest:false |
Cookie passes the rule but search still fails | Cookie has aged into the gray zone. sig logout && sig login --mode visible |
| valid:true | code:0 + data:{} |
code:-1 / 401 | Local config has the old loose validateRule — sigcli accepted a half-cooked cookie | Refresh ~/.sig/config.yaml from references/provider-config.yaml, then sig logout && sig login |
| valid:false | — | — | Cookie fully expired or never written | sig login xiaohongshu --mode visible |
The mistake the previous version of this skill encouraged: see KeyError: 'msg' → assume captcha → tell the user to re-login + solve captcha. Most of the time captcha never appears, the real cause is cookie aging, and re-login does fix it — but for the wrong reason. Document the right reason so the next debugger doesn't get misled.
Two-tier verification. Run tier 1 unconditionally; tier 2 only after tier 1 passes.
Tier 1: prerequisite checks (read-only, safe)
# 1. Provider authenticated and validated against the strict rule
sig status xiaohongshu
# Expect: configured: true, valid: true
# 2. Probe the validateUrl directly — must return non-empty data object
sig run xiaohongshu -- bash -c 'curl -sL --max-time 10 \
-H "Cookie: $SIG_XIAOHONGSHU_COOKIE" \
-H "User-Agent: Mozilla/5.0" \
https://edith.xiaohongshu.com/api/sns/web/unread_count'
# Expect: {"code":0,"success":true,"msg":"成功","data":{"unread_count":N,...}}
# If `data` is `{}` → captcha not solved. Re-login with --mode visible and complete the captcha.
# If code is -101 → session gone. Re-login.
# 3. Vendor signing JS deps installed
test -d <SKILL_DIR>/vendor/node_modules && echo "OK: node_modules present" || echo "FAIL: cd vendor && npm install"
# 4. Smoke-test signature generation (no network, no cookie)
cd <SKILL_DIR>/vendor && python3 -c "
import sys; sys.path.insert(0, '.')
from xhs_utils.xhs_util import generate_x_rap_param, generate_xs_xs_common
xs, xt, xsc = generate_xs_xs_common('a1=test', '/api/sns/web/v1/feed', '', 'POST')
rap = generate_x_rap_param('/api/sns/web/v1/user_posted', '')
print('OK' if xs and rap else 'FAIL')
"
# Expect: OK
Tier 2: live execution test
Run a tiny search and assert that items is a non-empty list. This is the canary for "everything actually works":
sig run xiaohongshu -- bash -c \
'python3 <SKILL_DIR>/scripts/xiaohongshu_search_note.py --keyword "AI"' \
| python3 -c "
import json, sys
d = json.load(sys.stdin)
items = d.get('items', [])
assert isinstance(items, list) and len(items) > 0, f'expected items list, got: {d}'
print(f'OK: {len(items)} items')
"
# Expect: OK: N items
# If you see {'error': 'API_ERROR', 'message': \"code=0, msg=\\\"'msg'\\\"\"} → captcha-not-solved
# soft-reject. The cookie is half-cooked even though sig status shows valid:true.
# Refresh references/provider-config.yaml into ~/.sig/config.yaml, then re-login with captcha.
This skill bundles a minimal slice of cv-cat/Spider_XHS (MIT) at <SKILL_DIR>/vendor/. To bump the vendored version:
<SKILL_DIR>/scripts/sync-vendor.sh # latest master
<SKILL_DIR>/scripts/sync-vendor.sh <ref> # specific commit/tag
License and upstream metadata are in <SKILL_DIR>/vendor/LICENSE and <SKILL_DIR>/vendor/UPSTREAM.md.
Source: sigcli/sigcli — distributed by TomeVault.