A command line for Xiaohongshu.
xhs is a single pure-Go binary. It reads public data from xiaohongshu.com over
plain HTTPS, shapes the responses into clean records, and pipes into the rest of
your tools. No paid API key, nothing to run alongside it. It signs its own
requests and bootstraps an anonymous web session the way a browser does.
go install github.com/tamnd/xiaohongshu-cli/cmd/xhs@latestOr grab a prebuilt binary from the releases, or run the container image:
docker run --rm ghcr.io/tamnd/xhs:latest --helpWhen stdout is a pipe, xhs prints JSONL, one record per line, so a run feeds
jq, awk, or another xhs command with no flags. When stdout is a terminal it
prints a compact table. Pick a format yourself with -o.
# open a note (the xsec_token comes from a listing or a share URL)
xhs note 6849c2f0000000001e034c8e --token <xsec_token>
xhs note 'https://www.xiaohongshu.com/explore/<id>?xsec_token=<t>&xsec_source=pc_feed'
# a creator profile, or the creator's notes
xhs user 5ff0e6500000000001008400
xhs user <id> --notes -n 50
# search notes or users
xhs search 'latte art' -n 40
xhs search 'travel japan' --users
# a note's comments, optionally with replies
xhs comments <note-id> --token <t> --deep -n 100
# the recommendation homefeed
xhs feed --category food -n 40
xhs feed --list
# topics, related notes, autocomplete
xhs tag coffee
xhs related <note-id> --token <t>
xhs suggest cof
# parse ids, urls, and tokens out of any link
xhs id 'https://www.xiaohongshu.com/explore/<id>?xsec_token=<t>'Pipe one command into the next. Every command that prints notes can emit just the
URL with -o url, and the next command reads ids from stdin with -:
xhs search coffee -o url | xhs note -
xhs search coffee -n 100 | xhs crawl - --out ./data --comments-o table|json|jsonl|csv|tsv|yaml|url|raw picks the format. --fields a,b,c
keeps and orders columns. --template '{{.note_id}} {{.title}}' renders each
record with Go text/template. -n caps the record count. --raw prints each
record as pretty JSON.
Xiaohongshu serves each page in two ways. The server renders the page once with
the data already embedded in a window.__INITIAL_STATE__ script, and the browser
then keeps the page fresh over a signed JSON API. The signed API refuses
anonymous callers with a login error, so xhs reads the server-rendered state
first and only falls back to the signed API when you give it a logged-in cookie.
What that means per surface, with no cookie:
- note and feed read the server-rendered page and work anonymously from any IP, including servers and CI. These are the reliable surfaces.
- user, user --notes, and related also read the server-rendered profile page. Xiaohongshu rate-limits that page hard per IP: a cold IP serves it, then it redirects to login for a cooldown window. They work on a fresh IP and at a slow pace, and need a cookie for sustained crawling.
- comments, search, suggest, tag, and me are only ever loaded over the signed JSON API, so they need a logged-in cookie.
So:
- Opening a note needs an
xsec_token. You get one from the feed, a listing, a search result, or a share URL; it travels with the note andxhs idpulls it out. Thefeedcommand is the easiest anonymous source of notes and tokens. - Run it at a polite pace. The default
--rateis 600ms; raise it with--rate 2swhen you walk many profiles. - For the signed surfaces, or to crawl profiles without hitting the wall, pass a real cookie:
xhs me --cookie 'web_session=...; a1=...'
export XHS_COOKIE='web_session=...; a1=...'The anonymous session (the a1 cookie) is bootstrapped on first use and cached
under your config dir. Inspect or reset it with xhs session show and
xhs session forget.
xhs crawl is the scraping engine. It seeds a frontier from the explore feed and
from any note ids you pass, then walks outward breadth-first: each note reaches
its author, the author's other notes, and its related notes. Every record kind
streams to its own JSONL file (notes.jsonl, users.jsonl, comments.jsonl) as
it is found, so a long crawl leaves usable output even if it stops early. Notes
and users are de-duplicated, and --depth and --max bound the walk.
# seed from the explore feed and walk two hops, capped at 500 notes
xhs crawl --explore --depth 2 --max 500 --out ./data
# crawl a category, following each author's other notes
xhs crawl --category food --author-notes --out ./food
# crawl specific notes with their comments and related notes
xhs crawl <note-id> --token <t> --related --comments --out ./data
# pipe ids in from another command
xhs search coffee -o url | xhs crawl - --out ./dataFlags win over environment variables, which win over defaults.
| Variable | Meaning |
|---|---|
XHS_COOKIE |
cookie header for gated surfaces |
XHS_COOKIE_FILE |
path to a cookie file (header or Netscape format) |
XHS_PROXY |
HTTP or SOCKS proxy URL |
XHS_USER_AGENT |
override the default desktop UA |
XHS_OUTPUT |
default output format |
XHS_CACHE_DIR |
cache location |
XHS_CONFIG_DIR |
config and session location |
xhs config show prints the resolved settings with the cookie redacted.
xhs cache stat|clear|path manages the on-disk response cache.
Exit codes: 0 success, 3 needs a login, 4 not found, 5 rate-limited or
walled by anti-bot, 6 network error, 1 anything else.
cmd/xhs/ thin main, wires cli.Root into fang and maps exit codes
cli/ the cobra command tree and the output formatter
xiaohongshu/ the library: signed HTTP client, session, and data models
pkg/xhssign/ the request signer (x-s/x-t/x-s-common)
pkg/xhsurl/ the id, url, and xsec_token parser
pkg/xhshtml/ the __INITIAL_STATE__ extractor for server-rendered pages
docs/ tago documentation site
make build # ./bin/xhs
make test # go test ./...
make vet # go vet ./...Push a version tag and GitHub Actions runs GoReleaser, which builds the archives, Linux packages, the multi-arch GHCR image, checksums, SBOMs, and a cosign signature:
git tag v0.1.0
git push --tagsThe Homebrew and Scoop steps self-disable until their tokens exist, so the first release works with no extra secrets.
Apache-2.0. See LICENSE. The request signer is a clean-room reimplementation built from observing the public web client; no third-party code is vendored.