This script exports a RedNote (Xiaohongshu, 小红书) account's notes to CSV: date, title, type, likes, saves, comments and shares for each note, plus the account's follower count. We ran it on 2026-10-12 on Luckin Coffee's official account, 瑞幸咖啡, and exported its 200 most recent notes in 16 seconds.
Which API to call
RedNote's open platform at open.xiaohongshu.com is for shop tools such as ERP and order printing, and its homepage lists no API for profiles or notes (what it covers). We make ToolzerHub; the script calls our RedNote API, which returns public profiles and their notes as JSON.
What you need
- Python 3 and
requests(pip install requests). - An API key from the dashboard. Each call is 15 credits, so the free tier's 100 credits a month cover the profile call and 5 pages, 100 notes.
- The account's profile ID, or its profile URL. To find an ID by name, search profiles:
curl "https://api.toolzerhub.com/v2/rednote/search/profiles?query=瑞幸咖啡" \
-H "x-api-key: $TOOLZERHUB_API_KEY"
The first of 20 results was the verified 瑞幸咖啡 account, "id": "5bac7d93168a6000019384c5", with "Fans 1.3m". A second verified account, 瑞幸咖啡美国 ("Fans 4.2k"), came next, so check the follower count as well as red_official_verified before you copy an ID.
The script
import csv
import os
import sys
import time
from datetime import datetime, timezone
import requests
BASE = "https://api.toolzerhub.com/v2/rednote/profiles"
HEADERS = {"x-api-key": os.environ["TOOLZERHUB_API_KEY"]}
FIELDS = ["note_id", "posted", "title", "type", "pinned", "likes", "saves", "comments",
"shares"]
def cell(value):
# Spreadsheets run text that starts with = + - or @ as a formula; an apostrophe keeps it text.
if isinstance(value, str) and value[:1] in ("=", "+", "-", "@", "\t", "\r"):
return "'" + value
return value
def get(path, params, tries=5):
# 429 and 503 are retryable; the error body says how long to wait.
for attempt in range(tries):
r = requests.get(f"{BASE}/{path}", headers=HEADERS, params=params, timeout=90)
if r.status_code not in (429, 503) or attempt == tries - 1:
r.raise_for_status()
return r.json()["data"]
wait = r.json().get("error", {}).get("details", {}).get("retryAfter", 5 * (attempt + 1))
time.sleep(min(wait, 60))
def row(note):
return {
"note_id": note["id"],
"posted": datetime.fromtimestamp(note["create_time"], timezone.utc).date().isoformat(),
"title": note.get("title") or note.get("desc", "")[:80],
"type": note.get("type"),
"pinned": note.get("sticky", False),
"likes": note.get("likes"),
"saves": note.get("collected_count"),
"comments": note.get("comments_count"),
"shares": note.get("share_count"),
}
def notes(profile, max_pages):
cursor = None
for _ in range(max_pages):
params = {"profile_id_or_url": profile}
if cursor:
params["cursor"] = cursor
data = get("posts", params)
yield from data["notes"]
cursor = data.get("next_cursor")
if not data.get("has_more") or not cursor:
break
def main(profile, max_pages=10, out="profile_notes.csv"):
p = get("profile", {"profile_id_or_url": profile})
print(f"{p['nickname']}: {p['fans']} followers, {p['ndiscovery']} notes, "
f"{p['liked']} likes and {p['collected']} saves received")
seen = set()
with open(out, "w", newline="", encoding="utf-8-sig") as f:
writer = csv.DictWriter(f, fieldnames=FIELDS)
writer.writeheader()
for note in notes(profile, int(max_pages)):
if note["id"] not in seen:
seen.add(note["id"])
writer.writerow({k: cell(v) for k, v in row(note).items()})
print(f"wrote {len(seen)} notes to {out}")
if __name__ == "__main__":
main(*sys.argv[1:])
The script makes one profile call, then pages through the notes 20 at a time with next_cursor, stopping at max_pages. Pinned notes come first (pinned is True), then the rest newest first. ndiscovery is the field we read as the account's note count; our reference does not document it, so treat it as approximate.
cell() puts an apostrophe in front of any text that starts with =, +, - or @, so a spreadsheet shows it as text instead of running it as a formula.
Run the script
export TOOLZERHUB_API_KEY=your_key
python rednote_profile.py 5bac7d93168a6000019384c5 10 luckin.csv
Output on 2026-10-12:
瑞幸咖啡: 1263918 followers, 3694 notes, 8276854 likes and 1414420 saves received
wrote 200 notes to luckin.csv
That took 16.1 seconds and 11 calls: 1 profile call and 10 pages. The account's five most-liked notes in the export. Titles are in Chinese, with emoji removed; the numbers are what to compare.
| posted | type | likes | saves | comments | title |
|---|---|---|---|---|---|
| 2026-07-27 | image | 51,502 | 1,952 | 5,729 | 我们赢了! |
| 2026-08-06 | video | 51,262 | 8,023 | 1,377 | 不同年代下闯祸be like: |
| 2026-07-29 | image | 24,942 | 859 | 3,225 | 妙生笛 |
| 2026-08-10 | image | 23,286 | 1,486 | 3,100 | 恭喜最佳男主角易烊千玺 |
| 2026-08-10 | image | 19,785 | 552 | 1,637 | 再次幸会,最佳男主角! |
What 200 notes from one brand account showed
The account posts steadily, and a few notes sit far above the median.
- The 200 notes covered 2026-07-13 to 2026-10-12, so the account posted about two notes a day. At 20 notes a page, all 3,694 would be about 185 pages; we ran 10 and did not test paging to the end.
- 125 were image notes (
typeisnormal) and 75 were videos. The median video had 1,909 likes and the median image note 1,354. - The median note had 1,513 likes, 336 comments and 170 saves.
- Comments outnumbered likes on 16 notes. The most-commented was a back-to-school "surprise box" note (开学瑞一杯, 惊喜盒子限时掉落): 20,482 comments, 17,744 likes. For a brand, rows like these mark the posts that drew replies, which you can then pull with
/v2/rednote/posts/comments.
The image-versus-video gap and the posting rate describe one account over three months, not RedNote in general.
Export several accounts or more history
- A competitor set: run the script once per account and add an
accountcolumn, or loop over several IDs. - More history: raise
max_pages. About 185 pages for the whole Luckin account would be roughly 2,790 credits. - A profile URL works in place of the ID, since the parameter is
profile_id_or_url. We ran the ID only.
What it costs
Each call is 15 credits. This run was 11 calls, 165 credits. 1,000 notes take 50 pages plus the profile call, 765 credits: $0.76 to $1.45 at our rates of $0.99 to $1.90 per 1,000 credits.
We are not affiliated with RedNote or Luckin Coffee.
Sources
- RedNote e-commerce open platform, https://open.xiaohongshu.com/, read 2026-10-12
- ToolzerHub RedNote profiles reference, https://docs.toolzerhub.com/reference/rednote/profiles, checked 2026-10-12
- ToolzerHub pricing, https://toolzerhub.com/pricing, checked 2026-10-12