CLI tool
jackwener/xhs-cli avatar
jackwener/xhs-cli

xhs-cli: driving Xiaohongshu from the terminal through a real browser

小红书命令行工具 — 搜索、阅读、点赞、收藏、评论,全在终端完成 | Xiaohongshu CLI

672 stars70 forksPythonApache-2.0

At a glance

What is it?
xhs-cli is a Python command line client for Xiaohongshu that never calls the platform's private API. It navigates a camoufox browser to real pages and reads structured data out of the page state, which makes it slower than an API client but harder for the platform's risk control to flag.
Who is it for?
xhs-cli fits developers who need Xiaohongshu data in scripts and agent workflows, and it fits people who would rather not hand their session cookies to an unofficial API endpoint. It does not fit production batch scraping, because every command drives a real browser, and for accounts under risk restriction it is deliberately the safer route, since the README states plainly that the browser approach holds up better than the reverse engineered API route.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 14 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A terminal client that refuses to call the private API

xhs-cli is a Python command line tool for Xiaohongshu, and its defining choice is that it does not use the platform's undocumented API. Instead it drives a real browser and reads the same page state a human visitor would load.

The project description lists the scope in plain terms: search notes, read them, like, favorite, comment, all from the terminal. The command table in the README groups the surface into Auth commands (login, logout, status, whoami), Read commands (search, read, feed, topics), user lookups (user, user-posts, followers, following), engagement (like, unlike, comment, delete), favorites (favorite, unfavorite, favorites) and posting (post).

The README also points to a sibling project, xiaohongshu-cli, and describes it as the version built on reverse engineered APIs. That sibling is faster and more stable, the README says, but weaker against the platform's risk control than the browser approach used here. That comparison is the whole design argument in one paragraph: this project trades throughput for the fact that its traffic looks like ordinary browsing.

Who this is for is narrow. The tool targets developers and automation authors who want Xiaohongshu content inside scripts, terminals and AI agent workflows rather than a phone app. The licence is Apache-2.0, and the package version is 0.1.4 with the classifier Development Status :: 3 - Alpha, so treat it as a young tool rather than a settled library.

camoufox, page state and the absence of a network protocol

The mechanism is worth understanding because it explains both the reliability and the cost. The client launches camoufox, an anti-fingerprint Firefox build, navigates to the real Xiaohongshu pages, and pulls structured data out of the page's window.__INITIAL_STATE__ object. The README states that the extracted data is identical to what a normal browsing session sees.

The dependency list in pyproject.toml matches that story: camoufox[geoip]>=0.4, playwright>=1.40, browser-cookie3>=0.19, qrcode>=7.4, click>=8.0 and rich>=13.0. Playwright is the automation layer, click is the command parser, rich renders the tables, browser-cookie3 reads Chrome's cookie store and qrcode renders the login code in the terminal.

Everything the client knows comes from the DOM or the page state, never from a documented endpoint. That is a deliberate constraint, and it is why interactions like liking or commenting are implemented by clicking real DOM buttons rather than by issuing a request. The README says so directly: engagement actions are performed by clicking the actual buttons on the page.

The cost of that design is latency. Every search, feed load and like costs a browser navigation, and the tool needs a working browser binary on the machine. The README notes that it runs headless Firefox so no window appears, and that the first run downloads the camoufox browser with python -m camoufox fetch. Headless operation also removes the visual confirmation that a manual click would give you, so any action the tool performs on your account happens without you seeing the page it acted on.

Login has three doors and two of them read your Chrome profile

Authentication is the part of xhs-cli that touches your machine most directly, so the README spends real space on it. There are three ways in.

The default path extracts cookies from a local Chrome profile, and the README marks this as the recommended route. The forced alternative renders a QR code in the terminal for a browser-assisted scan, which the documentation offers as the way to troubleshoot login problems. The third path takes a cookie string directly:

bash
xhs login
xhs login --qrcode
xhs login --cookie "a1=xxx; web_session=yyy"

The manual string must contain at least a1 and web_session. Cookies end up in ~/.xhs-cli/cookies.json with permissions 0600, which is the right default for a file holding a live session.

Two checks are worth internalizing. xhs status inspects only the locally saved cookie and does not touch the browser cookie store, so it is a safe thing to run on a shared machine or in a script. And after logging in, the client validates that the session is a valid non-guest session and probes whether feed and search are usable, warning you to log in again if the probe fails. That probe is the difference between a CLI that quietly returns nothing and one that tells you your account is under risk control.

bash
xhs status
xhs whoami
xhs whoami --json
xhs logout

Logging out clears the saved session. Once you are in, check whoami before anything else.

xsec_token caching is what makes the commands composable

Xiaohongshu note links carry an xsec_token, and without it a note id alone is not enough to fetch a note. xhs-cli solves the annoyance by caching tokens: after a search or a feed load, the xsec_token values are written to ~/.xhs-cli/token_cache.json and later commands resolve them automatically. The README states this twice, once in the feature list and once as a note under the command table.

The practical effect is that read and engagement commands take only a note id:

bash
xhs read <note_id>
xhs read <note_id> --comments
xhs read <note_id> --xsec-token <token>

The third form is the escape hatch for a note the cache has never seen, for example one whose link came from outside the tool.

User lookups need an internal identifier rather than the public Xiaohongshu handle, and the README is explicit that this is a hexadecimal internal user_id, not the Xiaohongshu account number shown on the profile page:

bash
xhs user <user_id>
xhs user-posts <user_id>
xhs followers <user_id>
xhs following <user_id>

Getting a user_id requires reading a profile first, so plan the order of your calls. Every data command also accepts a --json flag, which matters more than it sounds: the human-readable output is rendered through rich, while the JSON path is what you want to pipe into jq or a script.

bash
xhs feed
xhs favorites --max 10

The cached token file lives under the same ~/.xhs-cli directory as the cookies, which means it travels with the session rather than with the project.

Engagement runs through DOM clicks, and posting needs a second login

The engagement commands are the ones with consequences, because they change state on a real account:

bash
xhs like <note_id>
xhs like <note_id> --undo
xhs favorite <note_id>
xhs favorite <note_id> --undo
xhs delete <note_id>

Like and favorite are reversible with --undo, which the README shows as the counterpart form. Delete is not, and the command is documented as deleting your own note.

Posting an image and text note takes a title, repeated --image flags and a --content argument. It also carries a catch the README calls out under its notes: xhs post may require an additional login to the creator platform at https://creator.xiaohongshu.com. In other words, publishing can need two separate authenticated sessions, and a working user session is not sufficient on its own.

Here is the honest limitation. Since actions are real clicks on real pages, they are subject to the same risk control that applies to your account, and a session that has been flagged may fail only at the mutation step. The README says as much in the notes, warning that a session still in guest state or under risk restriction prompts a re-login request.

Two more constraints deserve a mention. User profile queries need the internal user_id, and the tool needs a real browser binary present before any of this works.

The smoke script guards its mutations behind an environment variable

The repository ships scripts/smoke_local.sh, a one-command smoke test for people who already have a login. It expects ~/.xhs-cli/cookies.json to exist and runs pytest underneath:

bash
./scripts/smoke_local.sh
./scripts/smoke_local.sh -k whoami

The second form passes pytest arguments through, so you can narrow the run to a single case.

The interesting part is the default selection. The script runs only side-effect-free commands, described as integration and not live_mutation, and the mutating checks stay off until you opt in explicitly:

bash
XHS_SMOKE_MUTATION=1 ./scripts/smoke_local.sh

The pytest configuration in pyproject.toml explains why. Two markers are declared, integration for tests that require a valid login session and live_mutation for tests that may change account state, with like, favorite, comment and post named explicitly. The default addopts setting deselects integration tests entirely, so a plain pytest run stays offline.

Optional overrides exist for the mutation checks:

bash
XHS_SMOKE_COMMENT_TEXT="smoke test comment"
XHS_SMOKE_POST_IMAGES="/abs/a.jpg,/abs/b.jpg"
XHS_SMOKE_POST_TITLE="smoke title"
XHS_SMOKE_POST_CONTENT="smoke content"

This is a good pattern to copy. The tests that can like, comment and delete are separated from the tests that only read, and turning them on requires naming the intent in the environment. Anyone integrating xhs-cli into CI should follow the same split rather than running everything against a real account.

Installing with uv, and handing the tool to an AI agent

The README asks for Python 3.8 or newer and gives two ways to install. The preferred route uses uv as a tool install, and pipx is the alternative:

bash
uv tool install xhs-cli
bash
pipx install xhs-cli

For development from source the same section offers a clone and a sync, which creates the environment from uv.lock:

bash
git clone [email protected]:jackwener/xhs-cli.git
cd xhs-cli
uv sync

After installation the browser binary has to be fetched once:

bash
python -m camoufox fetch

The project also ships a SKILL.md at the repository root so that AI agents can learn to use the command on their own. The README recommends installing it through the Skills CLI:

bash
npx skills add jackwener/xhs-cli

That command takes flags for global installation with -g, a target agent with -a claude-code, and non-interactive mode with -y. A manual route is documented as well, cloning the repository into .agents/skills/xhs-cli so that any agent reading .agents/skills discovers the tool.

The README also marks the older OpenClaw and ClawHub installation path as obsolete and no longer supported, so ignore any older walkthrough you find for it. The command surface itself is discoverable at runtime with xhs --help and xhs --version.

Editorial conclusion

xhs-cli fits developers who need Xiaohongshu data in scripts and agent workflows, and it fits people who would rather not hand their session cookies to an unofficial API endpoint. It does not fit production batch scraping, because every command drives a real browser, and for accounts under risk restriction it is deliberately the safer route, since the README states plainly that the browser approach holds up better than the reverse engineered API route. The version to plan against is 0.1.4, marked Development Status:: 3 - Alpha in pyproject.toml, and the repository has no GitHub releases, so pin a commit rather than a tag. Before you build on it, check that xhs login works in your environment, since the first run downloads camoufox through python -m camoufox fetch and every later command depends on a valid session in ~/.xhs-cli/cookies.json.

Frequently asked questions

How do I install xhs-cli and log in for the first time?

Install it with uv tool install xhs-cli or pipx install xhs-cli on Python 3.8 or newer, then run xhs login to extract cookies from your local Chrome profile. The README also describes xhs login --qrcode for a terminal QR code scan and xhs login --cookie for supplying a cookie string containing at least a1 and web_session.

Does xhs-cli call the Xiaohongshu API directly?

No. It uses camoufox, an anti-fingerprint Firefox build, to navigate to real pages and extracts data from window.__INITIAL_STATE__. The README notes that the data matches a normal browsing session.

Why does xhs read need an xsec_token and how do I avoid passing it?

Tokens are cached automatically in ~/.xhs-cli/token_cache.json after a search or feed load, so later commands resolve them by note id alone. You can still pass one explicitly with xhs read <note_id> --xsec-token <token>.

How does xhs-cli compare with xiaohongshu-cli?

The README describes xiaohongshu-cli as the version built on reverse engineered APIs, which is faster and more stable, but weaker against risk control than the browser-based approach used by xhs-cli.

Can I run the xhs-cli tests without liking or commenting on my account?

Yes. The default smoke run selects only side-effect-free tests, described as integration and not live_mutation, and mutating checks such as like, favorite, comment, post and delete only run when XHS_SMOKE_MUTATION=1 is set.

Official sources

  1. Issues
  2. jackwener/xhs-cli on GitHub
  3. License: Apache-2.0
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/jackwener-xhs-cli.svg)](https://hysenlabs.com/projects/jackwener-xhs-cli)

Was this useful?