| name | xiaohongshu-hub |
| description | A skill for reading and writing Xiaohongshu (XHS) data using Python + UV, powered by httpx + pycryptodome. Cookies are auto-acquired via browser_use get_cookies for authentication โ no manual copying needed. Supports search notes/users/topics, reading note details and comments, recommendation feed, trending lists, social operations (follow/favorite), interactions (like/comment/reply), notification queries, creator note management, etc. Trigger when user mentions "Xiaohongshu", "XHS", "scrape Xiaohongshu", "search Xiaohongshu notes", "Xiaohongshu comments", "xiaohongshu-hub", "read Xiaohongshu data", "Xiaohongshu cookie", or any scenario requiring programmatic access to Xiaohongshu content.
|
xiaohongshu-hub
Forked from: jackwener/xiaohongshu-cli (Apache-2.0)
This skill simplifies and adapts the original repo as follows:
- Removed
browser-cookie3 / click / rich / PyYAML / qrcode dependencies
- Cookie auth changed to accept
dict directly or read from env vars โ no browser auto-extraction
- Removed CLI layer (commands/) and QR login module (qr_login.py)
signing.py retains the full reverse-engineered signing algorithm, pure stdlib (no third-party deps)
creator_signing.py retains AES-128-CBC signing (requires pycryptodome)
- In Minis environment, cookies auto-acquired via
browser_use get_cookies
File Structure
/var/minis/skills/xiaohongshu-hub/
โโโ SKILL.md
โโโ pyproject.toml # UV project config (httpx + pycryptodome only)
โโโ scripts/
โโโ __init__.py
โโโ constants.py # Constants (Host, UA, SDK version, etc.)
โโโ exceptions.py # Structured exceptions (6 error types)
โโโ signing.py # Main API signing (x-s / x-s-common / x-t) pure stdlib
โโโ creator_signing.py # Creator API signing (AES-128-CBC)
โโโ client.py # XhsClient core class (all API methods)
Authentication
Xiaohongshu Web API uses three key cookies:
| Cookie | Description |
|---|
a1 | User identity token, core parameter for signing algorithm (required) |
web_session | Login session (required) |
webId | Device ID (recommended) |
Method 1: browser_use Auto-Acquire (Minis Preferred)
In Minis, use the browser_use tool to navigate to Xiaohongshu, then get_cookies to auto-read.
Cookie raw values never appear in the conversation, safely passed through offload env files.
Steps:
browser_use navigate to https://www.xiaohongshu.com, confirm logged in
browser_use get_cookies to get a1, web_session, webId
- Tool returns offload env file path (e.g.
/var/minis/offloads/env_cookies_xxx.sh)
- Cookie raw values not exposed in conversation context
- Load the env file and use:
. /var/minis/offloads/env_cookies_xxx.sh
export XHS_A1="$COOKIE_A1"
export XHS_WEB_SESSION="$COOKIE_WEB_SESSION"
export XHS_WEBID="$COOKIE_WEBID"
Note: get_cookies only works for the current page domain. Navigate to https://www.xiaohongshu.com first before calling it.
Method 2: Manual from Browser DevTools
- Log into Xiaohongshu, open DevTools โ Application โ Cookies โ
https://www.xiaohongshu.com
- Find
a1, web_session, webId values
- Store in Minis env vars (Settings โ Environments):
XHS_A1 / XHS_WEB_SESSION / XHS_WEBID
Cookie Passing (Three Ways, Priority High to Low)
- Environment variables:
XHS_A1 + XHS_WEB_SESSION + XHS_WEBID (recommended)
- Direct code pass:
XhsClient({"a1": ..., "web_session": ..., "webId": ...})
- Script args: via
-a1 / --web-session etc.
Quick Start
Environment Setup
cd /var/minis/skills/xiaohongshu-hub
uv sync
Using as Python Library (Recommended)
import os, json, sys
sys.path.insert(0, "/var/minis/skills/xiaohongshu-hub")
from scripts.client import XhsClient
client = XhsClient.from_env()
client = XhsClient({
"a1": os.environ["XHS_A1"],
"web_session": os.environ["XHS_WEB_SESSION"],
"webId": os.environ["XHS_WEBID"],
})
with client:
me = client.get_self_info()
print("User:", me.get("nickname"))
results = client.search_notes("food", page=1)
for item in results.get("items", [])[:5]:
note = item.get("note_card", {})
print(f" - {note.get('display_title', '')}")
feed = client.get_home_feed()
print(f"Recommended feed: {len(feed.get('items', []))} items")
hot = client.get_hot_feed("homefeed.travel_v3")
print(f"Trending travel: {len(hot.get('items', []))} items")
Via Script (Shell Environment)
cd /var/minis/skills/xiaohongshu-hub
uv run python -c "
import os, json, sys
sys.path.insert(0, '.')
from scripts.client import XhsClient
with XhsClient.from_env() as c:
r = c.search_notes('travel', page=1)
print(json.dumps(r, ensure_ascii=False, indent=2))
"
uv run python -c "
import os, json, sys
sys.path.insert(0, '.')
from scripts.client import XhsClient
with XhsClient.from_env() as c:
print(json.dumps(c.get_self_info(), ensure_ascii=False, indent=2))
"
API Reference
User
| Method | Description |
|---|
get_self_info() | Get current logged-in user info |
get_user_info(user_id) | Get specified user profile |
get_user_notes(user_id, cursor="") | Get user's published notes |
Search
| Method | Description |
|---|
search_notes(keyword, page=1, sort="general", note_type=0) | Search notes |
search_users(keyword, page=1) | Search users |
search_topics(keyword) | Search topics/tags |
sort options: "general" / "popularity_descending" / "time_descending"
note_type options: 0=all / 1=video / 2=image-text
Note
| Method | Description |
|---|
get_note_by_id(note_id, xsec_token="") | Get note details |
get_comments(note_id, cursor="", xsec_token="") | Get comments (single page) |
get_all_comments(note_id, xsec_token="", max_pages=20) | Auto-paginate to get all comments |
get_sub_comments(note_id, comment_id, cursor="", xsec_token="") | Get comment replies |
Feed / Discovery
| Method | Description |
|---|
get_home_feed(category="homefeed_recommend") | Recommended feed |
get_hot_feed(category="homefeed.food_v3") | Trending notes (by category) |
Trending categories: fashion_v3 / food_v3 / cosmetics_v3 / movie_and_tv_v3 /
career_v3 / love_v3 / household_product_v3 / gaming_v3 / travel_v3 / fitness_v3
Social
| Method | Description |
|---|
follow_user(user_id) | Follow user |
unfollow_user(user_id) | Unfollow user |
get_user_favorites(user_id, cursor="") | Get user favorites |
Interactions
| Method | Description |
|---|
like_note(note_id, xsec_token="") | Like note |
unlike_note(note_id, xsec_token="") | Unlike note |
collect_note(note_id, xsec_token="") | Collect note |
uncollect_note(note_id, xsec_token="") | Uncollect note |
post_comment(note_id, content, xsec_token="") | Post comment |
reply_comment(note_id, comment_id, content, xsec_token="") | Reply to comment |
delete_comment(note_id, comment_id) | Delete own comment |
Notifications
| Method | Description |
|---|
get_unread_count() | Unread notification count |
get_notifications_mentions(cursor="") | Comment / @ notifications |
get_notifications_likes(cursor="") | Like / collect notifications |
get_notifications_connections(cursor="") | New follower notifications |
Creator
| Method | Description |
|---|
get_my_notes(page=0) | Get own published notes list |
delete_note(note_id) | Delete note (experimental) |
Error Handling
from scripts.exceptions import (
NeedVerifyError,
SessionExpiredError,
IpBlockedError,
SignatureError,
XhsApiError,
)
try:
result = client.search_notes("food")
except NeedVerifyError:
print("CAPTCHA triggered, complete verification in browser and retry")
except SessionExpiredError:
print("Cookie expired, please re-acquire")
except IpBlockedError:
print("IP blocked, please change network")
except XhsApiError as e:
print(f"API error: {e} (code={e.code})")
Anti-Detection Mechanism
This skill inherits the full anti-detection implementation from the original repo:
- Gaussian jitter: Request intervals use truncated Gaussian distribution (not fixed intervals) to simulate real browsing rhythm
- Random long pauses: ~5% of requests wait an additional 2-5 seconds to simulate reading behavior
- Exponential backoff: HTTP 429/5xx auto-retry (max 3 times)
- CAPTCHA cooldown: After CAPTCHA trigger, auto-wait 5โ10โ20โ30 seconds, permanently double request interval
- Browser fingerprint consistency: macOS Chrome UA, session-level GPU/resolution/CPU fixed
- Full signing:
x-s / x-s-common / x-t signatures (reverse-engineered from Web client)
Notes
- Cookie validity is typically days to weeks; re-acquire via
browser_use get_cookies when expired
- Recommended to use a dedicated account to avoid affecting main account
- Write operations (comments, likes, etc.) have higher anti-spam risk than reads; use sparingly
get_all_comments defaults to max 20 pages; adjust via max_pages