Back to blog
TutorialOctober 11, 20266 min read

How to Collect Xiaohongshu (RedNote) Notes and Comments with an API

Search Xiaohongshu notes, retrieve details and paginate comments with Just One API. Includes time-filter limitations and a Python example that saves JSON responses.

Build a note-and-comment sample from a keyword

To study how people discuss sunscreen on Xiaohongshu, first search for relevant notes, then retrieve their details and comments. Linking content to reader feedback helps you organize product experiences, recurring questions, and ideas for future content.

Just One API provides separate endpoints for Xiaohongshu (RedNote) note search, details, and comments. This tutorial searches for “防晒” (sunscreen), selects up to 3 notes, and retrieves their details and up to 2 comment pages each. If you already have note IDs, skip the search step.

Register and test in the interactive documentation

Create a Just One API account, obtain your API token, and try a keyword in the Note Search documentation. You can test APIs for free and use the same token across platforms, with pay-as-you-go billing afterward. Check your account for each endpoint's available trial allowance.

This tutorial uses three GET endpoints, all requiring the token query parameter:

  • /api/xiaohongshu/search-note/v2: search with keyword, starting at page=1.
  • /api/xiaohongshu/get-note-detail/v1: retrieve details with noteId.
  • /api/xiaohongshu/get-note-comment/v2: retrieve comments with noteId and use lastCursor for subsequent pages.

Check that the response body's business code is 0 before processing data. Its structure differs between endpoints, so each needs its own parsing logic.

Step 1: Choose search filters

Note Search V2 supports general, popularity, publication-time, comment-count, and collection-count sorting. The example uses sort=general; use time_descending to order by publication time.

The noteType options are _0 for all supported types, _1 for video, and _2 for image/text notes. This example leaves the type unrestricted. In the current public response example, results are in data.items, with ordinary note IDs at each item's note.id. The script skips entries without a note ID.

For more search results, increment page, keep the keyword and filters consistent, and set a page limit. Search returns the platform's available result set, not every note containing a keyword.

Time sorting is different from time filtering

time_descending controls ordering; noteTime filters publication time. V2 can return notes outside the requested time range, so this example omits noteTime. For more accurate time filtering, review Note Search V4 and validate returned timestamps. A “last day” request parameter alone does not establish your dataset's time coverage.

Step 2: Retrieve note details

Pass each note ID to Note Details V1. This endpoint also accepts note URLs containing /explore/; other short links should be resolved before being used as identifiers.

In the current public example, data is an array, and its entries contain a note_list. The script preserves the complete response so you can extract content and author fields afterward. It does not assume that every endpoint returns an object in data.

V1 does not support video downloads. If downloading video is your goal, consult the recommended Note Details V6 and validate that version's response structure separately.

Step 3: Page through comments

Note Comments V2 supports latest, normal, and like_count sorting. The documentation recommends starting with latest. If it cannot retrieve comments for a particular note, try normal, restarting from the first page.

Leave lastCursor empty for the first request. For subsequent requests, pass the previous response's data.cursor; stop when data.has_more is false. This endpoint has no request parameter named page. The script's page variable only limits the loop and names output files.

The current example places comments in data.comments, with id for the comment ID and content for its text. For larger collections, deduplicate by note ID and comment ID. Any embedded sub_comments are not necessarily the full reply thread; use the separate Comment Replies endpoint when needed.

Python example: Save notes and comments as JSON

Install the dependency and set your token in a macOS or Linux terminal. Keep real tokens out of source control and request logs.

bash
python3 -m pip install requests
export JUSTONEAPI_TOKEN='your_token'

Save this as xiaohongshu_sample.py and run python3 xiaohongshu_sample.py. It makes at most 10 API calls: 1 search, up to 3 detail requests, and up to 2 comment requests per note. These are example limits, not platform quotas.

python
import json
import os
from datetime import datetime, timezone
from pathlib import Path

import requests

BASE_URL = "https://api.justoneapi.com"
TOKEN = os.environ.get("JUSTONEAPI_TOKEN", "")


def call_api(path, method="GET", accepted_codes=(0,), **params):
    if not TOKEN or TOKEN == "your_token":
        raise RuntimeError("Set JUSTONEAPI_TOKEN before running")
    values = {"token": TOKEN, **params}
    arguments = {"data": values} if method == "POST" else {"params": values}
    try:
        response = requests.request(
            method, BASE_URL + path, timeout=120, **arguments,
        )
    except requests.RequestException:
        raise RuntimeError("Network error or timeout") from None
    if response.status_code != 200:
        raise RuntimeError(f"HTTP {response.status_code}")
    try:
        result = response.json()
    except ValueError:
        raise RuntimeError("Response is not valid JSON") from None
    if not isinstance(result, dict) or result.get("code") not in accepted_codes:
        code = result.get("code") if isinstance(result, dict) else None
        raise RuntimeError(f"API failed: code={code}; check the docs")
    return result


def output_dir(prefix):
    stamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%S%fZ")
    folder = Path(prefix) / stamp
    folder.mkdir(parents=True, exist_ok=False)
    return folder


def save_json(folder, filename, value):
    (folder / filename).write_text(
        json.dumps(value, ensure_ascii=False, indent=2), encoding="utf-8",
    )


KEYWORD = "防晒"
MAX_NOTES = 3
MAX_COMMENT_PAGES = 2


def main():
    folder = output_dir("xiaohongshu-output")
    search = call_api(
        "/api/xiaohongshu/search-note/v2",
        keyword=KEYWORD, page=1, sort="general", noteType="_0",
    )
    save_json(folder, "search-1.json", search)
    note_ids = []
    seen = set()
    for item in search["data"].get("items") or []:
        note_id = (item.get("note") or {}).get("id")
        if note_id and str(note_id) not in seen:
            seen.add(str(note_id))
            note_ids.append(str(note_id))
    selected = note_ids[:MAX_NOTES]
    save_json(folder, "selected-note-ids.json", selected)

    for index, note_id in enumerate(selected, start=1):
        detail = call_api("/api/xiaohongshu/get-note-detail/v1", noteId=note_id)
        save_json(folder, f"note-{index}-detail.json", detail)
        cursor = ""
        seen_cursors = set()
        for page in range(1, MAX_COMMENT_PAGES + 1):
            comments = call_api(
                "/api/xiaohongshu/get-note-comment/v2",
                noteId=note_id, sort="latest", lastCursor=cursor,
            )
            save_json(folder, f"note-{index}-comments-{page}.json", comments)
            data = comments["data"]
            if data.get("has_more") not in (True, 1, "1"):
                break
            next_cursor = data.get("cursor")
            if page < MAX_COMMENT_PAGES:
                if not next_cursor or next_cursor in seen_cursors:
                    raise RuntimeError("Missing or repeated comment cursor")
                seen_cursors.add(next_cursor)
                cursor = next_cursor
    print(f"Saved {len(selected)} note samples to {folder}")


if __name__ == "__main__":
    try:
        main()
    except RuntimeError as error:
        raise SystemExit(str(error)) from None

Each run creates its own directory under xiaohongshu-output, retaining the search response, selected IDs, details, and comments. Field paths follow the current public documentation examples; actual records depend on the runtime responses. Once the sample meets your needs, export selected fields to CSV or a database.

The script stops on errors and does not retry automatically. Saved results remain available, but a new run makes new requests. Before scaling up, follow the retry guide to add checkpoints and bounded retries, distinguishing token, permission, or balance problems from temporary collection failures.

When do you need Pugongying data instead?

Start with these endpoints for note content, comments, and discussion themes. For creator marketing profiles, sponsored-note performance, or campaign metrics, read Xiaohongshu vs Pugongying. Ordinary note engagement counters should not be interpreted as campaign impressions or read counts.

Common questions

No. Supported /explore/ URLs can be passed as noteId to the details or comments endpoint. For other share links, consult Share Link Resolution first.

Does an absent search result mean the note does not exist?

No. Search coverage and content visibility affect results. If you know the note ID, you can try a direct lookup, but this does not guarantee access to unavailable content.

Why does the collected comment count differ from the displayed total?

This example limits collection to 2 comment pages. Sorting, reply levels, and the platform's available coverage also affect counts. Record collection time and page limits in your analysis instead of treating the sample size as the note's total comment count.

Try your own keyword

After registering and obtaining a token, test a real business keyword for free in the Note Search documentation. Confirm that the notes and comments answer your research question before expanding the collection or changing its filters.

Continue with Just One API

Log in to the Dashboard for your token, read the full API docs, or open the MCP GitHub project for the latest configuration.