💬 Xiaohongshu note comments scraper | 小红书笔记评论爬虫 Fetch the public comment list for a Xiaohongshu note by `note_id` or `share_text`. Supports cursor-based pagination (page 1-10) with cursor + index + pageArea tokens, and three sort strategies (hot / newest / earliest).
The run can no longer be killed by the key-value store. Under
LIMITED_PERMISSIONS — how Apify's automated QA and every API-triggered run
starts an Actor — opening the store is refused with HTTP 403
(KeyValueStoreCollectionClient.getOrCreate, path /v2/key-value-stores).
That call sat in a || fallback chain and was never inside a try/catch of
ours, so the throw ended the run with exit code 91 and a reported FAILED run
with zero items. Every store open now goes through a wrapper that returns a
no-op store instead of throwing, so the free-run counter and the run SUMMARY
degrade and the actual work proceeds.
The gate store's name-to-id lookup no longer depends on the store still being
on the first page. There is no GET-by-name endpoint, so the lookup pages
through /v2/key-value-stores; it used a fixed limit=50, which silently
stopped containing gate stores once the account passed 50 of them — and the
account creates a new default store on every run, so that list grows on its
own. The lookup now pages (100 per page, up to 20 pages).
[0.6] - 2026-08-13
Added
note_id schema default: 697c0eee000000000a03c308 (a public Xiaohongshu note ID). Both the input schema default and the runtime applyDefaults() now fall back to this value when the Actor is invoked with empty input. This makes the Actor pass Apify automated tests that submit empty payloads.
autoFilled field in SUMMARY: an array of field names that were filled by the runtime default instead of by the user. Empty array means the user supplied the field themselves. Support / maintainers can use this to spot runs that were not user-configured.
DEFAULT_NOTE_ID constant exported from src/main.js for unit-test and tooling use.
Changed
applyDefaults() behavior: note_id falls back to DEFAULT_NOTE_ID only when both note_id and share_text are empty. A share_text-only payload still routes through the share_text path (the default does not pollute it).
Integration test scenario 5 rewritten: the previous negative test asserted that empty input was rejected; the new positive test asserts the run succeeds with autoFilled: ['note_id'] and sourceKind: 'note_id'.
Verified
Unit + integration tests still pass (3 Actors × 2 suites = 6).
Empty-input local smoke test succeeds with autoFilled: ['note_id'] and Fetched image note via note_id in the logs.
Cloud empty-input smoke test on Apify passes (see deployment record).
[0.5] - 2026-08-13
Removed
Upstream pricing stripped from README (per user instruction, 2026-08-13): README no longer mentions the upstream per-request cost. Pricing is the Actor maintainer's decision and is configured via the Apify Console pricing model, not hardcoded in user-facing docs.
estimatedUpstreamCostUsd SUMMARY field removed: the field was a 1:1 mirror of the upstream's known cost. Removing it prevents the README or API output from leaking upstream pricing. The Actor maintainer can re-add a maintainer-defined pricing field later if needed.
UPSTREAM_COST_USD_PER_REQUEST constant removed from main.js: same reason.
Upstream priority chain removed from README (per user instruction): the App V2 > App > Web V2 > Web priority note is internal knowledge. The README still uses the App V2 endpoint but does not publish the comparison order.
Integration test assertion for estimatedUpstreamCostUsd removed: tied to the deleted field.
Verified
Unit + integration tests still pass (8 suites across 4 Actors).
Code no longer contains any hardcoded upstream price string or upstream priority chain.
[0.4] - 2026-08-12
Changed
Schema relaxation: note_id and share_text are no longer marked required in the input schema. The previous setup forced both fields to be filled (with a minLength: 16 minimum), which contradicted the README's "either-or" design and required Cloud smoke tests to feed a 16-character placeholder just to pass validation. The Actor now accepts either field on its own and uses runtime validation (Either note_id or share_text is required.) to surface the real constraint.
note_id length: schema minLength: 16 removed. Runtime regex still enforces 16-64 alphanumeric characters when note_id is provided (see main.js::validateInput).
share_text length: schema minLength: 16 removed. Runtime validation still enforces 1-2000 characters and a valid http(s) URL when share_text is provided.
Verified
Unit tests still pass (runtime validation unchanged).
Integration tests still pass.
Cloud smoke test planned for the next push.
[0.3] - 2026-08-12
Changed
Switched to inline environmentVariables (skill §4.1 owner-self-use pattern): UPSTREAM_API_KEY and UPSTREAM_BASE_URL are now baked into the Actor build. Users no longer need to add any Environment variables in the Apify Console before their first run.
README updated: replaced the "First Run Setup" section with a "No Setup Required" callout. The FAQ entry on upstream API keys is rewritten to reflect the preconfigured integration.
UPSTREAM_BASE_URL default: restored from empty string back to the real upstream base URL. Empty value was a leftover from the secret-reference workflow; with inline envs the default must be the live upstream so the Actor works out of the box.
Verified
Local smoke test against the live upstream with the inline key — request returned 200 with expected payload shape.
All existing unit and integration tests still pass.
All notable changes to this Actor are documented here. The format follows Keep a Changelog .
[0.2] - 2026-08-12
Changed
Env name: TIKHUB_API_KEY → UPSTREAM_API_KEY. Add the new secret in the Actor's Environment variables before your next run; the old name no longer works.
share_text accepts free-form share blobs: The Actor now extracts the first http(s) URL from the text instead of requiring the whole input to be a URL. Paste the full app share text (e.g. "美食 https://www.xiaohongshu.com/explore/abc?xsec_token=... 复制打开") and the Actor picks the URL for you.
share_text length: maxLength 500 → 2000 to accommodate full app share strings with xsec_source / xsec_token parameters.
Public-source audit: removed the upstream provider name from the README and schema. Internal code still uses it for the base URL, but no Console-visible string mentions it.
Verified
Unit tests for the new extractShareUrl helper (pure URL / URL in blob / multi-URL / trailing-punctuation stripping).
Integration tests for share-text blob inputs (full app share string → upstream receives the extracted URL).
[0.1] - 2026-08-12
Added
Initial release.
Inputs: note_id (preferred) or share_text (Xiaohongshu share link). At least one is required.
User-driven pagination: page (1–10) plus cursor / index / pageArea tokens. Page 1 leaves tokens empty; pages 2..10 forward the previous run's tokens.
Three sort strategies: latest_v2 (recommended, time desc), like_count, default.
One normalized dataset row per unique comment with content, author, engagement, target reply info, and the full upstream payload in rawData.
SUMMARY record with page count, request count, dedup count, next-page tokens, and upstream cost.
Free-tier quota: 5 runs/day. Paid tier is unlimited.
Hard caps: 10 pages per run, 1 upstream request per page.