Python 小红书 API:`XhsClient` 接口文档

概览

XhsClient 是小红书 Web/创作平台的底层 API 客户端,通过多个 Mixin 组合了阅读、互动、创作、社交、通知、认证六大领域的端点方法。客户端内部负责:

  • 自动签名:主站 API 使用 sign_main_api,创作者 API 使用 sign_creator
  • 限速:带高斯抖动的随机延迟,模拟人类浏览行为
  • 重试:对网络错误和 429/5xx 进行指数退避重试
  • 验证码处理:遇到 461/471 时冷却并抛出 NeedVerifyError
  • Cookie 回写:响应中的 Set-Cookie 会合并回内存会话
class XhsClient(
    ReadingEndpointsMixin,
    InteractionEndpointsMixin,
    CreatorEndpointsMixin,
    SocialEndpointsMixin,
    NotificationEndpointsMixin,
    AuthEndpointsMixin,
):

安装及导入

pip install git+https://github.com/jackwener/xiaohongshu-cli
from xhs_cli.client import XhsClient

1. 构造函数

XhsClient(
    cookies: dict[str, str],
    timeout: float = 30.0,
    request_delay: float = 1.0,
    max_retries: int = 3,
)
参数 类型 默认值 说明
cookies dict[str, str] — 登录态 Cookie 字典,至少需包含 a1(创作者 API 签名依赖)
timeout float 30.0 HTTP 请求超时(秒)
request_delay float 1.0 请求间最小间隔(秒),设为 0 关闭限速
max_retries int 3 网络错误/5xx 的最大重试次数

实例属性

属性 类型 说明
cookies dict[str, str] 当前会话 Cookie,会随响应动态更新
_http httpx.Client 底层 HTTP 会话
_request_delay float 当前生效的请求间隔(验证码后会自动翻倍)
_base_request_delay float 初始请求间隔
_max_retries int 最大重试次数
_last_request_time float 上次请求时间戳
_verify_count int 连续触发验证码的次数
_request_count int 累计请求数

2. 生命周期方法

close() -> None

关闭底层 httpx.Client。建议在程序结束时调用。

__enter__() / __exit__(*args)

支持 with 语句,退出时自动调用 close()。

with XhsClient(cookies) as client:
    info = client.get_self_info()

3. 读取类接口(ReadingEndpointsMixin)

resolve_xsec_context(note_id, preferred_token="", preferred_source="") -> tuple[str, str]

解析笔记的 xsec_token / xsec_source。优先级:显式传入 → 本地缓存 → 抓取笔记 HTML 页面解析。成功解析后会写入缓存。

resolve_xsec_token(note_id, preferred_token="") -> str

同上,仅返回 xsec_token。

get_self_info() -> dict[str, Any]

获取当前登录用户信息(/api/sns/web/v2/user/me)。

get_user_info(user_id: str) -> dict[str, Any]

获取指定用户资料(/api/sns/web/v1/user/otherinfo)。

get_user_notes(user_id: str, cursor: str = "") -> dict[str, Any]

获取用户发布的笔记列表(/api/sns/web/v1/user_posted),每页 30 条。

search_notes(keyword, page=1, page_size=20, sort="general", note_type=0) -> Any

搜索笔记(/api/sns/web/v1/search/notes)。

  • 内部维护搜索会话缓存(按 keyword + sort + note_type 维度,TTL 600 秒,最多 128 条,持久化到配置目录下的 search_sessions.json),同一会话复用 search_id
  • 新会话会先调用 search/onebox 和 search/filter 预热,搜索后再预取 search/recommend(预热失败仅记录 debug 日志,不中断)

get_note_by_id(note_id, xsec_token="", xsec_source="pc_feed") -> Any

通过 feed API 获取笔记详情(/api/sns/web/v1/feed)。若提供 xsec_token 会写入缓存。

get_note_from_html(note_id, xsec_token="", xsec_source="pc_feed") -> dict[str, Any]

抓取笔记 HTML 页面并用 extract_note_from_html 解析,无需 xsec_token。

get_note_detail(note_id, xsec_token="", xsec_source="") -> dict[str, Any]

读取笔记的"最佳通道"封装:

  • 有 xsec_token(显式或缓存)→ 先尝试 feed API,失败回退 HTML
  • 无 xsec_token → 直接走 HTML
  • 若使用的是缓存 token 且 feed API 失败 → 使缓存失效后重试

get_home_feed(category="homefeed_recommend") -> dict[str, Any]

获取首页推荐流(/api/sns/web/v1/homefeed),每页 40 条。

get_hot_feed(category="homefeed.fashion_v3") -> dict[str, Any]

热点流,实际调用 get_home_feed(category=...)。

get_comments(note_id, cursor="", xsec_token="", top_comment_id="", xsec_source="") -> Any

获取笔记一级评论(/api/sns/web/v2/comment/page)。

  • 必须能解析出 xsec_token,否则抛出 XhsApiError
  • 使用缓存 token 失败时会自动刷新 token 重试一次

get_all_comments(note_id, xsec_token="", xsec_source="", max_pages=20) -> dict[str, Any]

自动翻页聚合所有评论,返回:

{
    "comments": list[dict],
    "has_more": False,
    "cursor": "",
    "total_fetched": int,
    "pages_fetched": int,
}

get_sub_comments(note_id, root_comment_id, num=30, cursor="") -> Any

获取子评论(/api/sns/web/v2/comment/sub/page)。


4. 互动类接口(InteractionEndpointsMixin)

post_comment(note_id, content) -> dict[str, Any]

发表评论(/api/sns/web/v1/comment/post)。

reply_comment(note_id, target_comment_id, content) -> Any

回复指定评论,底层同为 comment/post,附带 target_comment_id。

like_note(note_id) -> dict[str, Any]

点赞(/api/sns/web/v1/note/like,参数名 note_oid)。

unlike_note(note_id) -> dict[str, Any]

取消点赞(/api/sns/web/v1/note/dislike)。

favorite_note(note_id) -> dict[str, Any]

收藏(/api/sns/web/v1/note/collect)。

unfavorite_note(note_id) -> dict[str, Any]

取消收藏(/api/sns/web/v1/note/uncollect,参数名 note_ids)。

delete_comment(note_id, comment_id) -> dict[str, Any]

删除自己的评论(/api/sns/web/v1/comment/delete)。


5. 创作类接口(CreatorEndpointsMixin)

这些方法走创作者 API 签名(sign_creator),部分端点位于 CREATOR_HOST(/api/galaxy/ 开头),其余位于 EDITH_HOST。

search_topics(keyword) -> dict[str, Any]

搜索话题(/web_api/sns/v1/search/topic,每页 20 条)。

search_users(keyword) -> dict[str, Any]

搜索用户(/web_api/sns/v1/search/user_info,每页 20 条)。

get_upload_permit(file_type="image", count=1) -> dict[str, str]

申请上传许可(/api/media/v1/upload/web/permit),返回:

{"fileId": str, "token": str}

upload_file(file_id, token, file_path, content_type=None) -> None

以 PUT 方式上传文件到 UPLOAD_HOST/{file_id}。content_type 未指定时按文件扩展名推断。HTTP 状态码 ≥ 400 时抛出 XhsApiError。

create_image_note(title, desc, image_file_ids, topics=None, is_private=False) -> Any

发布图文笔记(/web_api/sns/v2/note)。topics 为 [{"name": ..., "id": ...}, ...] 形式的 hashtag 列表;is_private=True 时设置隐私类型。

delete_note(note_id) -> dict[str, Any]

删除笔记(/api/galaxy/creator/note/delete)。注意:若返回 404,会抛出 UnsupportedOperationError,提示该端点当前不可用(实验性功能)。

get_creator_note_list(tab=0, page=0) -> dict[str, Any]

创作者平台"我的笔记"列表(/api/galaxy/v2/creator/note/user/posted)。


6. 社交类接口(SocialEndpointsMixin)

follow_user(user_id) -> dict[str, Any]

关注用户(/api/sns/web/v1/user/follow)。

unfollow_user(user_id) -> dict[str, Any]

取消关注(/api/sns/web/v1/user/unfollow)。

get_user_favorites(user_id, cursor="") -> dict[str, Any]

获取用户的收藏列表(/api/sns/web/v2/note/collect/page,每页 30 条)。

get_user_likes(user_id, cursor="") -> dict[str, Any]

获取用户点赞过的笔记(/api/sns/web/v1/note/like/page,每页 30 条)。


7. 通知类接口(NotificationEndpointsMixin)

get_unread_count() -> dict[str, Any]

未读消息计数(/api/sns/web/unread_count)。

get_notification_mentions(cursor="", num=20) -> dict[str, Any]

@ 我的通知(/api/sns/web/v1/you/mentions)。

get_notification_likes(cursor="", num=20) -> dict[str, Any]

赞和收藏通知(/api/sns/web/v1/you/likes)。

get_notification_connections(cursor="", num=20) -> dict[str, Any]

新关注通知(/api/sns/web/v1/you/connections)。


8. 认证类接口(AuthEndpointsMixin)

login_activate() -> dict[str, Any]

激活登录会话(/api/sns/web/v1/login/activate)。

create_qr_login() -> dict[str, Any]

创建扫码登录二维码(/api/sns/web/v1/login/qrcode/create,qr_type=1)。

check_qr_status(qr_id, code) -> dict[str, Any]

轮询扫码状态(POST /api/qrcode/userinfo),附加请求头 service-tag: webcn。

complete_qr_login(qr_id, code) -> dict[str, Any]

完成扫码登录(GET /api/sns/web/v1/login/qrcode/status)。


9. 异常体系

所有异常定义在 xhs_cli/exceptions.py:

异常 触发条件
XhsApiError 通用 API 错误(非 JSON 响应、未知错误码、重试耗尽、上传失败等),携带 code 与 response 属性
IpBlockedError 响应 code == 300012(IP 被封禁)
SignatureError 响应 code == 300015(签名错误)
SessionExpiredError 响应 code == -100(登录态失效)
NeedVerifyError HTTP 461/471(触发验证码),携带 verify_type 与 verify_uuid
UnsupportedOperationError 功能端点不可用(如 delete_note 返回 404)

验证码冷却机制:连续触发 461/471 时,_verify_count 递增,冷却时间按 min(30, 5 * 2^(count-1)) 指数增长,同时 _request_delay 翻倍;成功响应后计数归零。

重试机制:对 httpx.TimeoutException / httpx.NetworkError 以及 HTTP 429/500/502/503/504 进行重试,等待时间为 2^attempt + uniform(0, 1) 秒。


10. 模块级辅助函数

来自 client_mixins.py,虽非类方法但常配合使用:

get_search_session_stats() -> dict[str, Any]

返回搜索会话缓存的调试统计:

{
    "active_count": int,
    "last_keyword": str,
    "last_sort": str,
    "last_note_type": int | None,
}

11. 使用示例

from xhs_cli.client import XhsClient

cookies = {"a1": "...", "web_session": "...", ...}

with XhsClient(cookies, request_delay=1.0, max_retries=3) as client:
    # 读取
    me = client.get_self_info()
    results = client.search_notes("咖啡", page=1, page_size=20)
    note = client.get_note_detail("笔记ID", xsec_source="pc_feed")
    comments = client.get_all_comments("笔记ID", max_pages=10)

    # 互动
    client.like_note("笔记ID")
    client.post_comment("笔记ID", "写得真好!")

    # 创作
    permit = client.get_upload_permit("image", 1)
    client.upload_file(permit["fileId"], permit["token"], "/path/to/img.jpg")
    client.create_image_note(
        title="标题",
        desc="正文",
        image_file_ids=[permit["fileId"]],
        topics=[{"name": "咖啡", "id": "..."}],
    )

12. 内部机制摘要(非公开 API)

以下为 XhsClient 的私有方法,供实现参考,不建议外部直接调用:

方法 作用
_rate_limit_delay() 高斯抖动限速(5% 概率额外 2–5 秒)
_mark_request() 记录请求时间与计数
_base_headers() 构造浏览器伪装请求头
_handle_response(resp) 统一解析响应、映射错误码、处理验证码
_merge_response_cookies(resp) 将响应 Cookie 合并回 self.cookies
_request_with_retry(method, url, **kwargs) 带限速与重试的 HTTP 请求
_main_api_get(uri, params=None) 主站 GET(自动签名)
_main_api_post(uri, data, header_overrides=None) 主站 POST(自动签名,支持头部覆盖)
_creator_host(uri) 根据 URI 前缀选择 CREATOR_HOST 或 EDITH_HOST
_creator_get(uri, params=None) 创作者 GET(x-s/x-t 签名)
_creator_post(uri, data) 创作者 POST(x-s/x-t 签名)
posted @ 2026-10-20 11:14  绝不原创的飞龙  阅读(15)  评论(0)    收藏  举报