# Xiaohongshu Scraper (`s-r/xiaohongshu-scraper`) Actor

Paste Xiaohongshu note links, or leave them empty to read the public feed, and get rows with title, body text, author, publish date, hashtags, image count and all four engagement counters: likes, comments, collects and shares. No account, no keyword search.

- **URL**: https://apify.com/s-r/xiaohongshu-scraper.md
- **Developed by:** [SR](https://apify.com/s-r) (community)
- **Categories:** Social media, Marketing
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $6.00 / 1,000 notes

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Xiaohongshu Scraper (RedNote)

Turn Xiaohongshu note links into a clean table. Every row carries the note's
title and body text, who published it, when, which topics it is filed under,
how many images it holds, and all four engagement counters that Xiaohongshu
publishes: likes, comments, collects and shares.

Paste links and you get those notes. Paste nothing and the Actor reads the
public recommendation feed instead and returns what is being pushed to a
logged-out visitor right now, across twelve categories from fashion to fitness.

### Read this before you run it

**Xiaohongshu note links only work complete.** Every link you can copy out of
the app or out of the browser address bar carries an `xsec_token` parameter,
and that parameter is what makes the note readable. It belongs to that one
note. Strip it, shorten the link by hand, or type a bare note id, and the
server answers with a page that has no note in it.

So: copy the link, paste the link. Do not tidy it up.

That behaviour is also why this Actor is careful about what it reports. The
page you get back for an incomplete link is a normal-looking one, roughly the
size of a real note page, and it even carries a success flag. It simply has
nothing inside it. An Actor that trusted the flag would hand you a row of
empty columns and call it a note. This one checks that the note you asked for
is actually present, and if it is not, the note is listed in `errors` with the
reason instead of being shipped as a blank row.

### What you cannot do here, and why we say so

**Keyword search is not available.** Xiaohongshu requires an account before it
will return search results, and this Actor does not use accounts. Typing a
keyword into a logged-out session returns a page with zero results in it, not a
short list. There is no partial version of this we could sell you.

**A creator's full back catalogue is not available either**, for the same
reason. A profile page renders the nickname and the avatar and no notes.

**Comments are not returned.** A note page carries its comment *count*, which
you do get, but the comment bodies load separately behind the same account
requirement.

Competing Actors in this category sell keyword search, profile crawls and
comment extraction as their headline features. If those are what you need, this
is not the tool, and we would rather tell you that on the listing page than
after you have paid for a run. What this Actor does, it does without an account
and without asking you for one.

### The two ways to run it

**Note links.** Put one link per line in **Note links**. This is the mode with
no ceiling: notes are read quickly and in parallel, and a few hundred in a run
is comfortable. This is the right mode for monitoring a brief, a campaign, a
creator's posts you already have links to, or a list handed to you by someone
else.

**The recommendation feed.** Leave **Note links** empty and pick a **Feed
category**. The Actor reads the public feed and returns what it finds. Each
load returns roughly thirty notes and they do not repeat between loads, so the
run walks the feed until it has what you asked for.

The feed is metered by Xiaohongshu. After a run of loads it starts answering
with a page that has no cards on it, and it stays that way for a good while:
long enough that a feed run scheduled hourly is realistic and one scheduled
every five minutes is not. The
Actor stops the moment that happens, writes `feed_exhausted` into `errors`,
sets `feedExhausted` in the run summary, and returns what it already had rather
than looping. In practice a feed run brings back somewhere around a hundred and
fifty notes before it has to wait. Ask for two thousand and you will get the
first batch and an honest note saying why the rest did not come.

The twelve categories are Recommended, Fashion, Food, Beauty, Film and TV,
Career, Relationships, Home, Gaming, Travel, Fitness and Video.

### What a row looks like

- `note_id`, `url`, `type` — `normal` for a photo note, `video` for a video
- `title`, `description` — the headline and the body text as published
- `author_name`, `author_id`, `author_url`, `author_avatar`
- `published_at`, `published_timestamp`, `updated_at`
- `likes`, `comments`, `collects`, `shares`, `engagement_total`
- `likes_text` — the like count exactly as the site displays it
- `hashtags`, `topic_ids` — the topics the note is filed under
- `mentioned_users` — accounts tagged in the body
- `image_count`, `has_live_photo`, and `images` when you ask for them
- `share_disabled` — whether the author turned sharing off
- `source`, `feed_category`, `position` — where in the run the row came from

#### Engagement counts come in two forms

Xiaohongshu abbreviates above ten thousand: a note with 18,000 likes displays
as `1.8万`. Flattening that to `1.8` would be a disaster and dropping it would
lose your biggest notes, so every row carries both. `likes_text` is the string
the site shows. `likes` is the number, expanded, ready to sort on. The other
three counters are returned as numbers only, because the site reports them that
way.

`engagement_total` is the four counters added together. It is there because
ranking by likes alone consistently misreads Xiaohongshu, where a note that
gets saved and forwarded is worth more than one that gets tapped.

#### Fields that are legitimately empty

Plenty of notes have no title and lead with the body text instead, so `title`
comes back as an empty string and `description` carries the content. A video
note reports `image_count` of one, its cover. `updated_at` equals
`published_at` on a note that was never edited. None of those are filled in
with a guess.

### Narrowing the output

**Note type** keeps only photo notes or only video notes. **Minimum likes**
drops everything under a threshold. Both are applied after the notes are read,
so they trim the output rather than the work, and the run summary reports how
many rows they removed under `filteredOut`.

**Fetch full note detail** is on by default and is what produces everything
above. Switch it off in a feed run and you get the lighter card instead: id,
title, author, cover image, like count and, on videos, the duration. That is
one request instead of two per note and is the cheap way to survey a category
before deciding which notes are worth opening. Pasted links need it on, because
a link on its own carries no data.

**Include image URLs** adds the full image list to every row. The image count is
always there; this adds the addresses, which makes the dataset noticeably wider
on a photo note carrying seventeen pictures.

### Run sizes and timing

A hundred pasted links finish in well under a minute. A feed run is slower by
design, because the feed is walked deliberately rather than hammered, and it
ends when the feed stops serving.

512 MB of memory is plenty. Nothing here runs a browser.

### When something cannot be read

The `errors` record in the key-value store lists every note that did not come
back and why:

- `not_readable` — the page carried no note. Almost always a link pasted
  without its token, or a note that has since been removed.
- `bad_input` — no note id could be found in that line at all.
- `feed_exhausted` — the feed stopped serving cards. Wait, or supply links.
- `fetch_failed` — the page could not be loaded after three tries.

The `summary` record counts the same things from the other direction:
`notesRequested`, `notesReturned`, `notesWithDetail`, `notesUnreadable`,
`withEngagement`, `withHashtags`, `feedExhausted` and `filteredOut`. Between
the two, an unexpectedly short run explains itself without you having to guess.

### A note on stability

Xiaohongshu is an actively defended platform and the reachable surface here is
narrower than the site as a whole. The token requirement described above is a
deliberate design of theirs, not a loophole, and the feed's metering is not
going to get looser. We test this Actor against the live site rather than
against a saved copy, so when something changes it shows up as a failing run on
our side before it shows up as a bad row on yours.

If a run comes back thinner than you expected, read `summary` first. It was
built to answer that question.

# Actor input Schema

## `noteUrls` (type: `array`):

Xiaohongshu note links, one per line. Paste the link exactly as you copied it from the app or the browser address bar: the xsec\_token in the link is what makes the note readable, and a link stripped back to just the note id returns nothing. Leave this empty to read the public recommendation feed instead.

## `xsecToken` (type: `string`):

Only needed if you are supplying note ids without their links. A token belongs to one note, so this single value is applied to every id in the list and will only work for the note it came from. Paste full links instead wherever you can.

## `feedCategory` (type: `string`):

Which slice of the public recommendation feed to read when no note links are supplied. Ignored when you paste links.

## `maxItems` (type: `integer`):

How many notes to return in total. Pasted links are read first. Feed runs return roughly thirty notes per load and the feed is metered, so a feed-only run realistically returns around a hundred and fifty before it has to wait; the run summary reports it when that happens.

## `includeNoteDetail` (type: `boolean`):

On, the Actor opens every note for its description, hashtags, image list, publish date and all four engagement counters. Off, it returns only what the feed card carries: id, title, author, cover image and like count, at one request instead of two. Pasted links need this on, because a link on its own carries no data.

## `includeImages` (type: `boolean`):

Add the full list of image links to every row. The image count is always returned; this adds the addresses themselves, which makes the dataset considerably wider on photo notes.

## `noteType` (type: `string`):

Keep only photo notes or only video notes. Applied after the notes are read, so it narrows the output rather than the work.

## `minLikes` (type: `integer`):

Drop notes below this like count. Zero keeps everything. Applied after the notes are read.

## Actor input object example

```json
{
  "noteUrls": [
    "https://www.xiaohongshu.com/explore/6a755753000000002c002751?xsec_token=ABCDEF&xsec_source=pc_feed"
  ],
  "feedCategory": "recommended",
  "maxItems": 50,
  "includeNoteDetail": true,
  "includeImages": false,
  "noteType": "any",
  "minLikes": 0
}
```

# Actor output Schema

## `notes` (type: `string`):

One row per note.

## `summary` (type: `string`):

Notes requested, notes returned, how many carried engagement counts, and whether the feed stopped serving.

## `errors` (type: `string`):

Notes that could not be read, with the reason for each.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "feedCategory": "recommended",
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/xiaohongshu-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "feedCategory": "recommended",
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("s-r/xiaohongshu-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "feedCategory": "recommended",
  "maxItems": 50
}' |
apify call s-r/xiaohongshu-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,s-r/xiaohongshu-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/l6b7AxKYAo8Ej1M8l/builds/YNKS24cLxK9ebcf56/openapi.json
