xiaohongshu-hub
Modified from: jackwener/xiaohongshu-cli (Apache-2.0)
This skill simplifies and modifies the original repository as follows:
- Removes the
browser-cookie3 / click / rich / PyYAML / qrcode dependencies
- Changes Cookie authentication to pass a
dict directly or read from environment variables, without automatic browser extraction
- Removes the CLI layer (
commands/) and QR login module (qr_login.py)
- Keeps the complete reverse-engineered signing algorithm in
signing.py, implemented with the standard library only and no third-party dependencies
- Keeps AES-128-CBC signing in
creator_signing.py (depends on pycryptodome)
- In the Minis environment, cookies are automatically retrieved through
browser_use get_cookies
File Structure
/var/minis/skills/xiaohongshu-hub/
├── SKILL.md
├── pyproject.toml # UV project configuration (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), standard library only
├── creator_signing.py # Creator API signing (AES-128-CBC)
└── client.py # XhsClient core class (all API methods)
Authentication Methods
The Xiaohongshu Web API uses three key cookies:
| Cookie |
Description |
a1 |
User identity identifier and core signing algorithm parameter (required) |
web_session |
Login session (required) |
webId |
Device ID (recommended) |
Method 1: Automatically Retrieve with browser_use (Preferred in the Minis Environment)
In Minis, you can use the browser_use tool to navigate to Xiaohongshu, then use get_cookies to read the cookies automatically.
Raw Cookie values will not appear in the conversation. They are passed securely through an offload env file.
Steps:
- Use
browser_use navigate to open https://www.xiaohongshu.com and confirm that you are logged in.
- Use
browser_use get_cookies to retrieve a1, web_session, and webId separately.
- The tool returns an offload env file path, such as
/var/minis/offloads/env_cookies_xxx.sh.
- Raw Cookie values do not appear in the conversation context.
- Load the env file before use:
. /var/minis/offloads/env_cookies_xxx.sh
# The file exports variables such as COOKIE_A1 / COOKIE_WEB_SESSION / COOKIE_WEBID
export XHS_A1="$COOKIE_A1"
export XHS_WEB_SESSION="$COOKIE_WEB_SESSION"
export XHS_WEBID="$COOKIE_WEBID"
Note: get_cookies only applies to the current page's domain. Navigate to https://www.xiaohongshu.com before calling it.
Method 2: Manually Retrieve Cookies from Browser DevTools
- Log in to Xiaohongshu and open DevTools -> Application -> Cookies ->
https://www.xiaohongshu.com.
- Find the values for
a1, web_session, and webId.
- Save them to Minis environment variables (Settings -> Environments):
XHS_A1 / XHS_WEB_SESSION / XHS_WEBID.
Ways to Pass Cookies (Three Methods, Highest to Lowest Priority)
- Environment variables:
XHS_A1 + XHS_WEB_SESSION + XHS_WEBID (recommended)
- Pass directly in code:
XhsClient({"a1": ..., "web_session": ..., "webId": ...})
- Script arguments: Pass through arguments such as
-a1 / --web-session
Quick Start
Set Up the Environment
cd /var/minis/skills/xiaohongshu-hub
uv sync
Call as a Python Library (Recommended)
import os, json, sys
sys.path.insert(0, "/var/minis/skills/xiaohongshu-hub")
from scripts.client import XhsClient
# Method 1: Build from environment variables (recommended)
client = XhsClient.from_env()
# Method 2: Pass a Cookie dict directly
client = XhsClient({
"a1": os.environ["XHS_A1"],
"web_session": os.environ["XHS_WEB_SESSION"],
"webId": os.environ["XHS_WEBID"],
})
with client:
# Current user information
me = client.get_self_info()
print("User:", me.get("nickname"))
# Search notes
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', '')}")
# Recommendation feed
feed = client.get_home_feed()
print(f"Recommendation feed: {len(feed.get('items', []))} items")
# Trending notes (travel)
hot = client.get_hot_feed("homefeed.travel_v3")
print(f"Trending travel: {len(hot.get('items', []))} items")
Call Through a Script (Shell Environment)
# Search notes and output JSON
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))
"
# Get current user information
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 Method Quick Reference
User
| Method |
Description |
get_self_info() |
Get information about the currently logged-in user |
get_user_info(user_id) |
Get profile information for a specified user |
get_user_notes(user_id, cursor="") |
Get a list of notes posted by a user |
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 and text
Notes
| 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) |
Automatically page through and get all comments |
get_sub_comments(note_id, comment_id, cursor="", xsec_token="") |
Get comment replies |
Feed / Discover
| Method |
Description |
get_home_feed(category="homefeed_recommend") |
Recommendation 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 a user |
unfollow_user(user_id) |
Unfollow |
get_user_favorites(user_id, cursor="") |
Get a user's favorites |
Interaction
| Method |
Description |
like_note(note_id, xsec_token="") |
Like |
unlike_note(note_id, xsec_token="") |
Unlike |
collect_note(note_id, xsec_token="") |
Favorite |
uncollect_note(note_id, xsec_token="") |
Unfavorite |
post_comment(note_id, content, xsec_token="") |
Post a comment |
reply_comment(note_id, comment_id, content, xsec_token="") |
Reply to a comment |
delete_comment(note_id, comment_id) |
Delete your own comment |
Notifications
| Method |
Description |
get_unread_count() |
Number of unread notifications |
get_notifications_mentions(cursor="") |
Comments / @ notifications |
get_notifications_likes(cursor="") |
Like / favorite notifications |
get_notifications_connections(cursor="") |
New follower notifications |
Creator
| Method |
Description |
get_my_notes(page=0) |
Get a list of notes you posted |
delete_note(note_id) |
Delete a note (experimental) |
Error Handling
from scripts.exceptions import (
NeedVerifyError, # CAPTCHA triggered (HTTP 461/471)
SessionExpiredError, # Cookie expired (code -100)
IpBlockedError, # IP blocked (code 300012)
SignatureError, # Signing failed (code 300015)
XhsApiError, # Other API errors (base class)
)
try:
result = client.search_notes("Food")
except NeedVerifyError:
print("A CAPTCHA was triggered. Complete verification in the browser and try again.")
except SessionExpiredError:
print("Cookie has expired. Retrieve it again.")
except IpBlockedError:
print("IP is blocked. Switch networks.")
except XhsApiError as e:
print(f"API error: {e} (code={e.code})")
Anti-Risk-Control Mechanisms
This skill inherits the complete anti-risk-control implementation from the original repository:
- Gaussian jitter: Uses a truncated Gaussian distribution for request intervals (not fixed intervals) to simulate natural browsing rhythms
- Random long pauses: About 5% of requests wait an additional 2 to 5 seconds to simulate reading behavior
- Exponential backoff: Automatically retries HTTP 429/5xx (up to 3 times)
- CAPTCHA cooldown: After a CAPTCHA is triggered, automatically waits 5 -> 10 -> 20 -> 30 seconds and permanently doubles the request interval
- Browser fingerprint consistency: macOS Chrome UA, with session-level GPU/resolution/CPU kept fixed
- Complete signing:
x-s / x-s-common / x-t signing (reverse-engineered from the web client)
Notes
- Cookies are usually valid for several days to several weeks. After they expire, retrieve them again through
browser_use get_cookies.
- Use a dedicated account to avoid risk-control flags on your main account.
- Write operations (comments, likes, etc.) carry a higher risk-control risk than read operations. Use them with discretion.
get_all_comments pages through up to 20 pages by default. You can adjust this with max_pages.
1---2name: xiaohongshu-hub3description: Skill for reading and writing Xiaohongshu (XHS) data using Python + UV. It depends only on httpx + pycryptodome, automatically retrieves cookies via `browser_use get_cookies` to complete authentication, and requires no manual copying. It supports searching notes, users, and topics; reading note details and comments; recommendation feeds; trending lists; social actions (follow/favorite); interactions (like/comment/reply); notification queries; creator note management; and more. This skill must be triggered when the user mentions "Xiaohongshu," "XHS," "scraping Xiaohongshu," "searching Xiaohongshu notes," "Xiaohongshu comments," "xiaohongshu-hub," "reading Xiaohongshu data," "Xiaohongshu Cookie," or any scenario that requires programmatically reading or writing Xiaohongshu content.4---56# xiaohongshu-hub78> **Modified from**: [jackwener/xiaohongshu-cli](https://github.com/jackwener/xiaohongshu-cli) (Apache-2.0)9>10> This skill simplifies and modifies the original repository as follows:11> - Removes the `browser-cookie3` / `click` / `rich` / `PyYAML` / `qrcode` dependencies12> - Changes Cookie authentication to pass a `dict` directly or read from environment variables, without automatic browser extraction13> - Removes the CLI layer (`commands/`) and QR login module (`qr_login.py`)14> - Keeps the complete reverse-engineered signing algorithm in `signing.py`, implemented with the standard library only and no third-party dependencies15> - Keeps AES-128-CBC signing in `creator_signing.py` (depends on pycryptodome)16> - In the Minis environment, cookies are automatically retrieved through `browser_use get_cookies`1718---1920## File Structure2122```23/var/minis/skills/xiaohongshu-hub/24├── SKILL.md25├── pyproject.toml # UV project configuration (httpx + pycryptodome only)26└── scripts/27 ├── __init__.py28 ├── constants.py # Constants (Host, UA, SDK version, etc.)29 ├── exceptions.py # Structured exceptions (6 error types)30 ├── signing.py # Main API signing (x-s / x-s-common / x-t), standard library only31 ├── creator_signing.py # Creator API signing (AES-128-CBC)32 └── client.py # XhsClient core class (all API methods)33```3435---3637## Authentication Methods3839The Xiaohongshu Web API uses three key cookies:4041| Cookie | Description |42|--------|-------------|43| `a1` | User identity identifier and core signing algorithm parameter (required) |44| `web_session` | Login session (required) |45| `webId` | Device ID (recommended) |4647### Method 1: Automatically Retrieve with `browser_use` (Preferred in the Minis Environment)4849In Minis, you can use the `browser_use` tool to navigate to Xiaohongshu, then use `get_cookies` to read the cookies automatically.50**Raw Cookie values will not appear in the conversation**. They are passed securely through an offload env file.5152Steps:531. Use `browser_use navigate` to open `https://www.xiaohongshu.com` and confirm that you are logged in.542. Use `browser_use get_cookies` to retrieve `a1`, `web_session`, and `webId` separately.55 - The tool returns an offload env file path, such as `/var/minis/offloads/env_cookies_xxx.sh`.56 - Raw Cookie values do not appear in the conversation context.573. Load the env file before use:5859```bash60. /var/minis/offloads/env_cookies_xxx.sh61# The file exports variables such as COOKIE_A1 / COOKIE_WEB_SESSION / COOKIE_WEBID62export XHS_A1="$COOKIE_A1"63export XHS_WEB_SESSION="$COOKIE_WEB_SESSION"64export XHS_WEBID="$COOKIE_WEBID"65```6667> **Note**: `get_cookies` only applies to the current page's domain. Navigate to `https://www.xiaohongshu.com` before calling it.6869### Method 2: Manually Retrieve Cookies from Browser DevTools70711. Log in to Xiaohongshu and open DevTools -> Application -> Cookies -> `https://www.xiaohongshu.com`.722. Find the values for `a1`, `web_session`, and `webId`.733. Save them to Minis environment variables (Settings -> Environments): `XHS_A1` / `XHS_WEB_SESSION` / `XHS_WEBID`.7475### Ways to Pass Cookies (Three Methods, Highest to Lowest Priority)76771. **Environment variables**: `XHS_A1` + `XHS_WEB_SESSION` + `XHS_WEBID` (recommended)782. **Pass directly in code**: `XhsClient({"a1": ..., "web_session": ..., "webId": ...})`793. **Script arguments**: Pass through arguments such as `-a1` / `--web-session`8081---8283## Quick Start8485### Set Up the Environment8687```bash88cd /var/minis/skills/xiaohongshu-hub89uv sync90```9192### Call as a Python Library (Recommended)9394```python95import os, json, sys96sys.path.insert(0, "/var/minis/skills/xiaohongshu-hub")97from scripts.client import XhsClient9899# Method 1: Build from environment variables (recommended)100client = XhsClient.from_env()101102# Method 2: Pass a Cookie dict directly103client = XhsClient({104 "a1": os.environ["XHS_A1"],105 "web_session": os.environ["XHS_WEB_SESSION"],106 "webId": os.environ["XHS_WEBID"],107})108109with client:110 # Current user information111 me = client.get_self_info()112 print("User:", me.get("nickname"))113114 # Search notes115 results = client.search_notes("Food", page=1)116 for item in results.get("items", [])[:5]:117 note = item.get("note_card", {})118 print(f" - {note.get('display_title', '')}")119120 # Recommendation feed121 feed = client.get_home_feed()122 print(f"Recommendation feed: {len(feed.get('items', []))} items")123124 # Trending notes (travel)125 hot = client.get_hot_feed("homefeed.travel_v3")126 print(f"Trending travel: {len(hot.get('items', []))} items")127```128129### Call Through a Script (Shell Environment)130131```bash132# Search notes and output JSON133cd /var/minis/skills/xiaohongshu-hub134uv run python -c "135import os, json, sys136sys.path.insert(0, '.')137from scripts.client import XhsClient138with XhsClient.from_env() as c:139 r = c.search_notes('Travel', page=1)140 print(json.dumps(r, ensure_ascii=False, indent=2))141"142143# Get current user information144uv run python -c "145import os, json, sys146sys.path.insert(0, '.')147from scripts.client import XhsClient148with XhsClient.from_env() as c:149 print(json.dumps(c.get_self_info(), ensure_ascii=False, indent=2))150"151```152153---154155## API Method Quick Reference156157### User158159| Method | Description |160|--------|-------------|161| `get_self_info()` | Get information about the currently logged-in user |162| `get_user_info(user_id)` | Get profile information for a specified user |163| `get_user_notes(user_id, cursor="")` | Get a list of notes posted by a user |164165### Search166167| Method | Description |168|--------|-------------|169| `search_notes(keyword, page=1, sort="general", note_type=0)` | Search notes |170| `search_users(keyword, page=1)` | Search users |171| `search_topics(keyword)` | Search topics/tags |172173`sort` options: `"general"` / `"popularity_descending"` / `"time_descending"`174`note_type` options: `0` = all / `1` = video / `2` = image and text175176### Notes177178| Method | Description |179|--------|-------------|180| `get_note_by_id(note_id, xsec_token="")` | Get note details |181| `get_comments(note_id, cursor="", xsec_token="")` | Get comments (single page) |182| `get_all_comments(note_id, xsec_token="", max_pages=20)` | Automatically page through and get all comments |183| `get_sub_comments(note_id, comment_id, cursor="", xsec_token="")` | Get comment replies |184185### Feed / Discover186187| Method | Description |188|--------|-------------|189| `get_home_feed(category="homefeed_recommend")` | Recommendation feed |190| `get_hot_feed(category="homefeed.food_v3")` | Trending notes (by category) |191192Trending categories: `fashion_v3` / `food_v3` / `cosmetics_v3` / `movie_and_tv_v3` /193`career_v3` / `love_v3` / `household_product_v3` / `gaming_v3` / `travel_v3` / `fitness_v3`194195### Social196197| Method | Description |198|--------|-------------|199| `follow_user(user_id)` | Follow a user |200| `unfollow_user(user_id)` | Unfollow |201| `get_user_favorites(user_id, cursor="")` | Get a user's favorites |202203### Interaction204205| Method | Description |206|--------|-------------|207| `like_note(note_id, xsec_token="")` | Like |208| `unlike_note(note_id, xsec_token="")` | Unlike |209| `collect_note(note_id, xsec_token="")` | Favorite |210| `uncollect_note(note_id, xsec_token="")` | Unfavorite |211| `post_comment(note_id, content, xsec_token="")` | Post a comment |212| `reply_comment(note_id, comment_id, content, xsec_token="")` | Reply to a comment |213| `delete_comment(note_id, comment_id)` | Delete your own comment |214215### Notifications216217| Method | Description |218|--------|-------------|219| `get_unread_count()` | Number of unread notifications |220| `get_notifications_mentions(cursor="")` | Comments / @ notifications |221| `get_notifications_likes(cursor="")` | Like / favorite notifications |222| `get_notifications_connections(cursor="")` | New follower notifications |223224### Creator225226| Method | Description |227|--------|-------------|228| `get_my_notes(page=0)` | Get a list of notes you posted |229| `delete_note(note_id)` | Delete a note (experimental) |230231---232233## Error Handling234235```python236from scripts.exceptions import (237 NeedVerifyError, # CAPTCHA triggered (HTTP 461/471)238 SessionExpiredError, # Cookie expired (code -100)239 IpBlockedError, # IP blocked (code 300012)240 SignatureError, # Signing failed (code 300015)241 XhsApiError, # Other API errors (base class)242)243244try:245 result = client.search_notes("Food")246except NeedVerifyError:247 print("A CAPTCHA was triggered. Complete verification in the browser and try again.")248except SessionExpiredError:249 print("Cookie has expired. Retrieve it again.")250except IpBlockedError:251 print("IP is blocked. Switch networks.")252except XhsApiError as e:253 print(f"API error: {e} (code={e.code})")254```255256---257258## Anti-Risk-Control Mechanisms259260This skill inherits the complete anti-risk-control implementation from the original repository:261262- **Gaussian jitter**: Uses a truncated Gaussian distribution for request intervals (not fixed intervals) to simulate natural browsing rhythms263- **Random long pauses**: About 5% of requests wait an additional 2 to 5 seconds to simulate reading behavior264- **Exponential backoff**: Automatically retries HTTP 429/5xx (up to 3 times)265- **CAPTCHA cooldown**: After a CAPTCHA is triggered, automatically waits 5 -> 10 -> 20 -> 30 seconds and permanently doubles the request interval266- **Browser fingerprint consistency**: macOS Chrome UA, with session-level GPU/resolution/CPU kept fixed267- **Complete signing**: `x-s` / `x-s-common` / `x-t` signing (reverse-engineered from the web client)268269---270271## Notes272273- Cookies are usually valid for several days to several weeks. After they expire, retrieve them again through `browser_use get_cookies`.274- Use a dedicated account to avoid risk-control flags on your main account.275- Write operations (comments, likes, etc.) carry a higher risk-control risk than read operations. Use them with discretion.276- `get_all_comments` pages through up to 20 pages by default. You can adjust this with `max_pages`.