xiaohongshu-hub · diff
git:20260311.51c6ba6 to git:20260801.3cb01ce
145 added, 145 removed. Audit A to A.
---
name: xiaohongshu-hub
description: >
- 使用 Python + UV 读写小红书(XHS)数据的技能,仅依赖 httpx + pycryptodome,
- 通过 browser_use get_cookies 自动获取 Cookie 完成认证,无需手动复制。
- 支持搜索笔记/用户/话题、读取笔记详情与评论、推荐 Feed、热门榜单、
- 社交操作(关注/收藏)、互动(点赞/评论/回复)、通知查询、创作者笔记管理等。
- 当用户提到"小红书"、"XHS"、"抓取小红书"、"搜索小红书笔记"、"小红书评论"、
- "xiaohongshu-hub"、"读取小红书数据"、"小红书 Cookie",
- 或任何需要以编程方式读写小红书内容的场景,必须触发本技能。
+ 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.
---
# xiaohongshu-hub
- > **改造来源**:[jackwener/xiaohongshu-cli](https://github.com/jackwener/xiaohongshu-cli)(Apache-2.0)
+ > **Modified from**: [jackwener/xiaohongshu-cli](https://github.com/jackwener/xiaohongshu-cli) (Apache-2.0)
>
- > 本技能在原仓库基础上做了以下简化与改造:
- > - 移除 `browser-cookie3` / `click` / `rich` / `PyYAML` / `qrcode` 依赖
- > - Cookie 认证改为直接传入 `dict` 或从环境变量读取,不做浏览器自动提取
- > - 移除 CLI 层(commands/)和 QR 登录模块(qr_login.py)
- > - `signing.py` 保留完整逆向签名算法,纯标准库实现(无第三方依赖)
- > - `creator_signing.py` 保留 AES-128-CBC 签名(依赖 pycryptodome)
- > - 在 Minis 环境中,Cookie 通过 `browser_use get_cookies` 自动获取
+ > 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 项目配置(仅 httpx + pycryptodome)
+ ├── pyproject.toml # UV project configuration (httpx + pycryptodome only)
└── scripts/
├── __init__.py
- ├── constants.py # 常量(Host、UA、SDK 版本等)
- ├── exceptions.py # 结构化异常(6 种错误类型)
- ├── signing.py # 主 API 签名(x-s / x-s-common / x-t)纯标准库
- ├── creator_signing.py # 创作者 API 签名(AES-128-CBC)
- └── client.py # XhsClient 核心类(全部 API 方法)
+ ├── 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
- 小红书 Web API 使用三个关键 Cookie:
+ The Xiaohongshu Web API uses three key cookies:
- | Cookie | 说明 |
- |--------|------|
- | `a1` | 用户身份标识,签名算法核心参数(必填) |
- | `web_session` | 登录 Session(必填) |
- | `webId` | 设备 ID(建议填写) |
+ | Cookie | Description |
+ |--------|-------------|
+ | `a1` | User identity identifier and core signing algorithm parameter (required) |
+ | `web_session` | Login session (required) |
+ | `webId` | Device ID (recommended) |
- ### 方法一:browser_use 自动获取(Minis 环境首选)
+ ### Method 1: Automatically Retrieve with `browser_use` (Preferred in the Minis Environment)
- 在 Minis 中可直接用 `browser_use` 工具导航到小红书,再用 `get_cookies` 自动读取,
- **Cookie 原始值不会出现在对话中**,通过 offload env 文件安全传递。
+ 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.
- 操作步骤:
- 1. `browser_use navigate` 打开 `https://www.xiaohongshu.com`,确认已登录
- 2. `browser_use get_cookies` 分别获取 `a1`、`web_session`、`webId`
- - 工具返回 offload env 文件路径(如 `/var/minis/offloads/env_cookies_xxx.sh`)
- - Cookie 原始值不出现在对话上下文中
- 3. 加载 env 文件后使用:
+ Steps:
+ 1. Use `browser_use navigate` to open `https://www.xiaohongshu.com` and confirm that you are logged in.
+ 2. 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.
+ 3. Load the env file before use:
```bash
. /var/minis/offloads/env_cookies_xxx.sh
- # 文件内已导出 COOKIE_A1 / COOKIE_WEB_SESSION / COOKIE_WEBID 等变量
+ # 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"
```
- > **注意**:`get_cookies` 仅对当前页面域名生效,需先 navigate 到 `https://www.xiaohongshu.com` 再调用。
+ > **Note**: `get_cookies` only applies to the current page's domain. Navigate to `https://www.xiaohongshu.com` before calling it.
- ### 方法二:手动从浏览器 DevTools 获取
+ ### Method 2: Manually Retrieve Cookies from Browser DevTools
- 1. 登录小红书,打开 DevTools → Application → Cookies → `https://www.xiaohongshu.com`
- 2. 找到 `a1`、`web_session`、`webId` 的值
- 3. 存入 Minis 环境变量(Settings → Environments):`XHS_A1` / `XHS_WEB_SESSION` / `XHS_WEBID`
+ 1. Log in to Xiaohongshu and open DevTools -> Application -> Cookies -> `https://www.xiaohongshu.com`.
+ 2. Find the values for `a1`, `web_session`, and `webId`.
+ 3. Save them to Minis environment variables (Settings -> Environments): `XHS_A1` / `XHS_WEB_SESSION` / `XHS_WEBID`.
- ### Cookie 传入方式(三种,优先级从高到低)
+ ### Ways to Pass Cookies (Three Methods, Highest to Lowest Priority)
- 1. **环境变量**:`XHS_A1` + `XHS_WEB_SESSION` + `XHS_WEBID`(推荐)
- 2. **代码直接传入**:`XhsClient({"a1": ..., "web_session": ..., "webId": ...})`
- 3. **脚本参数**:通过 `-a1` / `--web-session` 等参数传入
+ 1. **Environment variables**: `XHS_A1` + `XHS_WEB_SESSION` + `XHS_WEBID` (recommended)
+ 2. **Pass directly in code**: `XhsClient({"a1": ..., "web_session": ..., "webId": ...})`
+ 3. **Script arguments**: Pass through arguments such as `-a1` / `--web-session`
---
- ## 快速开始
+ ## Quick Start
- ### 环境准备
+ ### Set Up the Environment
```bash
cd /var/minis/skills/xiaohongshu-hub
uv sync
```
- ### 作为 Python 库调用(推荐)
+ ### Call as a Python Library (Recommended)
```python
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()
- # 方式二:直接传入 Cookie dict
+ # 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("用户:", me.get("nickname"))
+ print("User:", me.get("nickname"))
- # 搜索笔记
- results = client.search_notes("美食", page=1)
+ # 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', '')}")
- # 推荐 Feed
+ # Recommendation feed
feed = client.get_home_feed()
- print(f"推荐 Feed:{len(feed.get('items', []))} 条")
+ print(f"Recommendation feed: {len(feed.get('items', []))} items")
- # 热门笔记(旅行)
+ # Trending notes (travel)
hot = client.get_hot_feed("homefeed.travel_v3")
- print(f"热门旅行:{len(hot.get('items', []))} 条")
+ print(f"Trending travel: {len(hot.get('items', []))} items")
```
- ### 通过脚本调用(shell 环境)
+ ### Call Through a Script (Shell Environment)
```bash
- # 搜索笔记,输出 JSON
+ # 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('旅行', page=1)
+ 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 方法速查
+ ## API Method Quick Reference
- ### 用户
+ ### User
- | 方法 | 说明 |
- |------|------|
- | `get_self_info()` | 获取当前登录用户信息 |
- | `get_user_info(user_id)` | 获取指定用户主页信息 |
- | `get_user_notes(user_id, cursor="")` | 获取用户发布的笔记列表 |
+ | 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
- | 方法 | 说明 |
- |------|------|
- | `search_notes(keyword, page=1, sort="general", note_type=0)` | 搜索笔记 |
- | `search_users(keyword, page=1)` | 搜索用户 |
- | `search_topics(keyword)` | 搜索话题/标签 |
+ | 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` 可选:`"general"` / `"popularity_descending"` / `"time_descending"`
- `note_type` 可选:`0`=全部 / `1`=视频 / `2`=图文
+ `sort` options: `"general"` / `"popularity_descending"` / `"time_descending"`
+ `note_type` options: `0` = all / `1` = video / `2` = image and text
- ### 笔记
+ ### Notes
- | 方法 | 说明 |
- |------|------|
- | `get_note_by_id(note_id, xsec_token="")` | 获取笔记详情 |
- | `get_comments(note_id, cursor="", xsec_token="")` | 获取评论(单页) |
- | `get_all_comments(note_id, xsec_token="", max_pages=20)` | 自动翻页获取全部评论 |
- | `get_sub_comments(note_id, comment_id, cursor="", xsec_token="")` | 获取评论回复 |
+ | 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 / 发现
+ ### Feed / Discover
- | 方法 | 说明 |
- |------|------|
- | `get_home_feed(category="homefeed_recommend")` | 推荐 Feed |
- | `get_hot_feed(category="homefeed.food_v3")` | 热门笔记(按分类) |
+ | Method | Description |
+ |--------|-------------|
+ | `get_home_feed(category="homefeed_recommend")` | Recommendation feed |
+ | `get_hot_feed(category="homefeed.food_v3")` | Trending notes (by category) |
- 热门分类:`fashion_v3` / `food_v3` / `cosmetics_v3` / `movie_and_tv_v3` /
+ 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
- | 方法 | 说明 |
- |------|------|
- | `follow_user(user_id)` | 关注用户 |
- | `unfollow_user(user_id)` | 取消关注 |
- | `get_user_favorites(user_id, cursor="")` | 获取用户收藏夹 |
+ | 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
- | 方法 | 说明 |
- |------|------|
- | `like_note(note_id, xsec_token="")` | 点赞 |
- | `unlike_note(note_id, xsec_token="")` | 取消点赞 |
- | `collect_note(note_id, xsec_token="")` | 收藏 |
- | `uncollect_note(note_id, xsec_token="")` | 取消收藏 |
- | `post_comment(note_id, content, xsec_token="")` | 发表评论 |
- | `reply_comment(note_id, comment_id, content, xsec_token="")` | 回复评论 |
- | `delete_comment(note_id, comment_id)` | 删除自己的评论 |
+ | 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
- | 方法 | 说明 |
- |------|------|
- | `get_unread_count()` | 未读通知数 |
- | `get_notifications_mentions(cursor="")` | 评论 / @ 通知 |
- | `get_notifications_likes(cursor="")` | 点赞 / 收藏通知 |
- | `get_notifications_connections(cursor="")` | 新增关注通知 |
+ | 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
- | 方法 | 说明 |
- |------|------|
- | `get_my_notes(page=0)` | 获取自己发布的笔记列表 |
- | `delete_note(note_id)` | 删除笔记(实验性) |
+ | Method | Description |
+ |--------|-------------|
+ | `get_my_notes(page=0)` | Get a list of notes you posted |
+ | `delete_note(note_id)` | Delete a note (experimental) |
---
- ## 错误处理
+ ## Error Handling
```python
from scripts.exceptions import (
- NeedVerifyError, # 触发验证码(HTTP 461/471)
- SessionExpiredError, # Cookie 过期(code -100)
- IpBlockedError, # IP 被封(code 300012)
- SignatureError, # 签名失败(code 300015)
- XhsApiError, # 其他 API 错误(基类)
+ 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("美食")
+ result = client.search_notes("Food")
except NeedVerifyError:
- print("触发验证码,请在浏览器完成验证后重试")
+ print("A CAPTCHA was triggered. Complete verification in the browser and try again.")
except SessionExpiredError:
- print("Cookie 已过期,请重新获取")
+ print("Cookie has expired. Retrieve it again.")
except IpBlockedError:
- print("IP 被封,请更换网络")
+ print("IP is blocked. Switch networks.")
except XhsApiError as e:
- print(f"API 错误:{e}(code={e.code})")
+ 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:
- - **高斯抖动**:请求间隔使用截断高斯分布(非固定间隔),模拟真实浏览节奏
- - **随机长停顿**:约 5% 的请求额外等待 2~5 秒,模拟阅读行为
- - **指数退避**:HTTP 429/5xx 自动重试(最多 3 次)
- - **验证码冷却**:触发验证码后自动等待 5→10→20→30 秒,并永久加倍请求间隔
- - **浏览器指纹一致性**:macOS Chrome UA、session 级 GPU/分辨率/CPU 固定
- - **完整签名**:`x-s` / `x-s-common` / `x-t` 签名(逆向自 Web 客户端)
+ - **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
- - Cookie 有效期通常为数天至数周,过期后需重新通过 `browser_use get_cookies` 获取
- - 建议使用专用账号,避免主账号被风控
- - 写操作(评论、点赞等)风控风险高于读操作,请酌情使用
- - `get_all_comments` 默认最多翻 20 页,可通过 `max_pages` 调整
+ - 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`.