| name | rednote-skills |
| description | Use this skill for Xiaohongshu workflows via a shared real Chrome profile: login, draft save, note search/export, profile note listing, and note interactions. |
rednote-skill
Directory structure
rednote-skills/
โโโ scripts/
โ โโโ _bootstrap_env.py # Venv bootstrap helper
โ โโโ action_delay.py # Delay/cooldown management
โ โโโ batch_context_comments.py # Batch comment sending with context
โ โโโ batch_dump_notes.py # Batch export note data (JSON+XLSX) from URL list
โ โโโ batch_generate_comment_materials.py # Generate comment materials from search
โ โโโ batch_search_keywords.py # Batch keyword search
โ โโโ browser_profile.py # Chrome profile management
โ โโโ collect_note.py # Collect/bookmark notes
โ โโโ comment_note.py # Send comments to notes
โ โโโ convert_notes_to_xlsx.py # Convert JSON notes to styled Excel (no browser)
โ โโโ dedupe_utils.py # Shared deduplication utilities
โ โโโ dedup_urls.py # Deduplicate URLs against index (no browser)
โ โโโ dump_note.py # Export single note (markdown or JSON)
โ โโโ export_profile_note_comments.py # Export profile note comments to Excel/JSON
โ โโโ follow_user.py # Follow users
โ โโโ interact_helpers.py # Helper functions for interactions
โ โโโ like_note.py # Like notes
โ โโโ list_profile_notes.py # List notes from a user profile
โ โโโ manual_login.py # Manual login helper
โ โโโ note_content.py # Note content extraction utilities
โ โโโ official_risk_guard.py # Risk detection and guard
โ โโโ publish_note.py # Save notes as drafts
โ โโโ search_note_by_key_word.py # Search notes by keyword
โ โโโ validate_cookies.py # Cookie/login validation
โโโ deliveries/ # Final deliverables
โโโ logs/rednote/ # Runtime logs and dedupe indexes
โโโ tmp/rednote/ # Temporary or debug files
โโโ SKILL.md # This file
โโโ README.md # Project documentation
โโโ requirements.txt # Python dependencies
Script naming conventions
batch_<action>_<target>.py โ Batch operations that process multiple items (e.g., batch_dump_notes.py, batch_search_keywords.py)
<action>_<target>.py โ Single-item operations (e.g., dump_note.py, like_note.py, search_note_by_key_word.py)
<descriptor>_utils.py โ Shared utility modules (e.g., dedupe_utils.py)
<action>_<target>_to_<format>.py โ Format conversion scripts (e.g., convert_notes_to_xlsx.py)
Browser scripts launch Chrome; pure data scripts (dedup_urls, convert_notes_to_xlsx) do not.
Core rules
- Use the shared real Chrome profile through Patchright. Do not switch to a temporary cookie browser.
- Start with the target business script. Do not force
validate_cookies.py first.
- If a script says
ๆช็ปๅฝๅฐ็บขไนฆ or asks for python3 scripts/manual_login.py, complete manual login and rerun the same script.
- Search, dump, publish, like, collect, follow, and comment must all reuse the same Chrome profile.
- For comment or reply workflows, read the note first, then let AI write the final article-specific text, then send it.
- Never ask Python to rewrite or generate comments.
comment_note.py only sends the final text you pass in.
Browser config
Default values:
- Chrome executable: auto-detected
- Chrome source user data dir:
~/Library/Application Support/Google/Chrome
- Chrome runtime user data dir:
workspace tmp/rednote/chrome-user-data
- Chrome profile directory:
Profile 2
Optional overrides:
REDNOTE_CHROME_PATH
REDNOTE_CHROME_USER_DATA_DIR
REDNOTE_CHROME_RUNTIME_USER_DATA_DIR
REDNOTE_CHROME_PROFILE_DIRECTORY
File creation rules
Directory rules:
- Put final deliverables under
deliveries/
- Put runtime logs under
logs/rednote/
- Put temporary or debug files under
tmp/rednote/
- Do not create business output files under
skills/ or scripts/
File type rules:
- Use
.json for structured machine-readable input or output
- Use
.jsonl for append-only process logs
- Use
.csv or .xlsx for human review and spreadsheet delivery
- Use
.md for summaries, handoff notes, or run instructions
Naming rules:
- Prefer lowercase snake_case for stable filenames
- Prefer
YYYYMMDDTHHMMSS for run-level timestamps, for example 20260403T153000.json
- Prefer
batch_01_<keyword>.json, combined_50.json, summary.json for task outputs when a batch structure exists
- Keep names ASCII when practical; if a Chinese keyword is business-critical, keep it short and stable
Creation rules:
- Default to creating a new timestamped file instead of overwriting an old result
- Only append to log files such as
logs/rednote/*.jsonl
- Only overwrite an existing file when the command explicitly targets that path and replacement is intentional
- When a task creates a delivery folder, add a short
README.md if the folder contains multiple result files
Path rules:
- Prefer absolute paths for
--input, --output, and --output-dir
- Reuse script default output paths unless the task needs a dedicated delivery folder
- Preserve note URLs exactly when saving records, including
xsec_token and xsec_source
Recommended patterns:
- Batch materials:
logs/rednote/generated_comment_materials/<run_id>.json|csv|xlsx
- Batch logs:
logs/rednote/batch_search_keywords.jsonl and logs/rednote/batch_context_comments.jsonl
- Profile exports:
deliveries/rednote_profile_comment_sheets/<run_id>.json|xlsx
- Ad hoc note lists or debug data:
tmp/rednote/<topic>_<date>.json
Note dedupe rules
Goal:
- Avoid repeated note capture across runs
- Avoid collecting multiple notes from the same author in the same dedupe scope
- Keep dedupe state persistent and machine-readable
Default storage:
- Use
logs/rednote/note_dedupe_index.json as the default persistent dedupe index
- If a task needs an isolated dedupe scope, pass a dedicated path with
--dedupe-path
- Recommended isolated naming:
logs/rednote/note_dedupe_index_<topic>.json
How to save:
- Save dedupe state as JSON, not JSONL
- Keep one persistent index file per dedupe scope
- Write the dedupe index after a batch run finishes
- Do not mix unrelated campaigns into the same dedupe file unless cross-campaign dedupe is intended
Index shape:
- Top-level fields:
version, items, authors
items is keyed by note_id
authors is keyed by normalized author_key
Recommended item fields:
note_id
author_name
author_key
first_seen_at
last_seen_at
source_keyword
last_run_id
status
dedupe_reason
Recommended author fields:
author_key
author_name
first_seen_at
last_seen_at
first_note_id
last_note_id
source_keyword
last_run_id
How to validate:
- Parse
note_id from the candidate note URL before opening the detail page
- If
note_id already exists in the persistent index or current-run memory set, skip early as a note duplicate
- After reading note content, extract the authoritative
note_id from note data and check again
- Normalize author name into
author_key and check author-level duplication
- Only keep the note when both
note_id and author_key pass validation
Author normalization:
- Build
author_key from cleaned author nickname
- Normalize with NFKC
- Remove whitespace differences
- Compare case-insensitively
- Treat the normalized
author_key as the stable author dedupe key
How to dedupe:
- First priority:
note_id
- Second priority:
author_key
- If
note_id is duplicated, skip directly
- If
note_id is new but author_key already exists, skip as author_duplicate
- Default policy is strict: one author contributes at most one captured note within one dedupe scope
How to record results:
- Captured notes: write to batch output and update dedupe index with
status: captured
- Author duplicates found after content read: update dedupe index with
status: skipped and dedupe_reason: author_duplicate
- Note duplicates detected before capture: count as skipped in run summary even if no new item is written
- Run summary should expose at least:
deduped_skipped
note_id_skipped
author_skipped
captured
Operational rules:
- Never treat note title as a dedupe key
- Never treat keyword as a dedupe key
- Do not save records without a valid
note_id into the persistent dedupe index
- Preserve the original note URL in exported records, including
xsec_token and xsec_source
- If the business goal requires multiple notes from the same author, use a separate
--dedupe-path for that task instead of reusing the default global index
Login flow
Run the business script first. Only login when needed.
cd rednote-skills
python3 scripts/manual_login.py
Optional check:
cd rednote-skills
python3 scripts/validate_cookies.py
True: already logged in
False: run python3 scripts/manual_login.py
Common commands
cd rednote-skills
python3 scripts/publish_note.py
python3 scripts/publish_note.py "ๆต่ฏๆ ้ข" "ๆญฃๆๅ
ๅฎน" --delay-seconds 5
python3 scripts/publish_note.py "ๆต่ฏ๏ฝๅพๆๆททๆ" $'็ฌฌไธๆฎต\n\n[[image:/absolute/path/to/a.jpg]]\n\n็ฌฌไบๆฎต' --delay-seconds 5
python3 scripts/search_note_by_key_word.py "ๅ
ณ้ฎ่ฏ" --top_n 5 --delay-seconds 3
python3 scripts/search_note_by_key_word.py "ๅ
ณ้ฎ่ฏ" --start-index 21 --end-index 40 --filter '{"sort_by":"ๆๆฐ","note_type":"ๅพๆ"}' --delay-seconds 3
python3 scripts/batch_search_keywords.py --input /absolute/path/to/keywords.json --top-n 5 --delay-seconds 3
python3 scripts/batch_generate_comment_materials.py --input /absolute/path/to/keywords.json --top-n 5 --delay-seconds 3 --dedupe-path logs/rednote/note_dedupe_index.json
python3 scripts/list_profile_notes.py --profile-url "https://www.xiaohongshu.com/user/profile/<user_id>" --output /absolute/path/to/profile_notes.json
python3 scripts/list_profile_notes.py --user-id "<user_id>" --limit 0 --delay-seconds 3
python3 scripts/dump_note.py "<note_url>"
python3 scripts/dump_note.py "<note_url>" --format json
python3 scripts/dump_note.py "<note_url>" --format json --output /absolute/path/to/note.json
python3 scripts/dedup_urls.py --input /absolute/path/to/urls.json --output /absolute/path/to/unique_urls.json
python3 scripts/dedup_urls.py --input /absolute/path/to/urls.json --dedupe-path logs/rednote/note_dedupe_index_<topic>.json --update-index
python3 scripts/dedup_urls.py --input /absolute/path/to/urls.json --dry-run
python3 scripts/batch_dump_notes.py --input /absolute/path/to/urls.json --output-dir deliveries/bid_notes --dedupe-path logs/rednote/note_dedupe_index.json --limit 30 --delay-seconds 3
python3 scripts/batch_dump_notes.py --input /absolute/path/to/urls.json --dry-run
python3 scripts/convert_notes_to_xlsx.py --input /absolute/path/to/notes.json --output /absolute/path/to/summary.xlsx
python3 scripts/convert_notes_to_xlsx.py --input /absolute/path/to/notes.json --output /absolute/path/to/summary.xlsx --columns "title,nickname,note_url,tags,desc,publish_time,liked_count"
python3 scripts/export_profile_note_comments.py --profile-url "https://www.xiaohongshu.com/user/profile/<user_id>" --output-dir deliveries/rednote_profile_comment_sheets
python3 scripts/export_profile_note_comments.py --input /absolute/path/to/profile_notes.json --limit 10
python3 scripts/like_note.py "<note_url>" --delay-seconds 5
python3 scripts/collect_note.py "<note_url>" --delay-seconds 5
python3 scripts/follow_user.py "<note_url>" --delay-seconds 5
python3 scripts/comment_note.py "<note_url>" "<final_comment>" --delay-seconds 5 --like-probability 0.35
python3 scripts/batch_context_comments.py --input /absolute/path/to/comments.json --delay-seconds 5 --like-probability 0.35
Publish rules
publish_note.py saves to drafts only. It does not publish.
- Body supports inline image placeholders:
[[image:/absolute/path/to/file]]
- Keep image paths absolute for reliability.
- The browser stays open after draft save for manual review.
Search rules
search_note_by_key_word.py returns a Python list of note URLs.
- Use
--top_n for the first N results.
- Use
--start-index and --end-index for waterfall ranges.
- Use
--max-scroll-rounds and --max-idle-scroll-rounds when you need a deeper range.
--filter '<json>' supports:
sort_by: ็ปผๅ / ๆๆฐ / ๆๅค็น่ต / ๆๅค่ฏ่ฎบ / ๆๅคๆถ่
note_type: ไธ้ / ่ง้ข / ๅพๆ
publish_time: ไธ้ / ไธๅคฉๅ
/ ไธๅจๅ
/ ๅๅนดๅ
search_scope: ไธ้ / ๅทฒ็่ฟ / ๆช็่ฟ / ๅทฒๅ
ณๆณจ
position_distance: ไธ้ / ๅๅ / ้่ฟ
- Filter aliases also work:
sort, location_distance, location, ๆๅบๆนๅผ, ็ฌ่ฎฐ็ฑปๅ, ๅๅธๆถ้ด, ๆ็ดข่ๅด, ไฝ็ฝฎ่ท็ฆป
- Preserve
xsec_token and xsec_source in note URLs for follow-up scripts.
Profile note listing
list_profile_notes.py accepts either --profile-url or --user-id
--limit 0 means collect as many notes as the page exposes
- Output JSON includes
profile_url, count, and notes
- Each note includes
id, xsec_token, xsec_source, note_url, title, cover
Batch search input
batch_search_keywords.py supports:
- JSON array of keywords:
["ๅ
ณ้ฎ่ฏA", "ๅ
ณ้ฎ่ฏB"]
- JSON array of objects:
[{"keyword": "ๅ
ณ้ฎ่ฏA", "top_n": 5}]
- JSON object with
items: {"items": [{"keyword": "ๅ
ณ้ฎ่ฏA", "top_n": 5}]}
- JSON mapping:
{"ๅ
ณ้ฎ่ฏA": 5, "ๅ
ณ้ฎ่ฏB": {"top_n": 8}}
Per item fields:
keyword
top_n
start_index
end_index
max_scroll_rounds
max_idle_scroll_rounds
delay_seconds
filter
meta
Rules:
- CLI
--filter is supported
- Root-level
filter is supported for {"items": [...]} input
- Merge order: CLI
--filter < root filter < item filter
- Use
--dry-run to validate input without launching searches
- Logs default to
logs/rednote/batch_search_keywords.jsonl
Comment workflow
Required order:
- Run
python3 scripts/dump_note.py "<note_url>"
- Let AI write the final comment based on the actual note content
- Run
python3 scripts/comment_note.py "<note_url>" "<final_comment>" --delay-seconds 5
Hard rules:
- Never send generic template comments.
- Comments must be grounded in the current note's title,ๆญฃๆ็ป่ใๆ ็ญพใ่กจ่พพๆนๅผใๆ
็ปชๆ่ง็น.
- Reply comments must also be article-specific and consistent with the original comment context.
comment_note.py only sends the final comment text it receives.
--like-probability <0-1> or REDNOTE_COMMENT_AUTO_LIKE_PROBABILITY controls optional pre-like behavior. Default: 0.35
Batch comment workflow
Required order:
- Run
python3 scripts/batch_generate_comment_materials.py ...
- Let AI read the exported materials and prepare
/absolute/path/to/comments.json
- Run
python3 scripts/batch_context_comments.py --input /absolute/path/to/comments.json --delay-seconds 5 --like-probability 0.35
Rules:
batch_generate_comment_materials.py only exports materials. It does not send comments.
- Exported files default to
logs/rednote/generated_comment_materials/<run_id>.json|csv|xlsx
comment_info and reply_comment_info are placeholders for later AI-written drafts
batch_context_comments.py does not search notes or generate copy
- Each record must map one
note_url to one final comment_text
- Do not batch-send generic praise or filler copy
- Use
--dry-run to validate input without posting
- Logs default to
logs/rednote/batch_context_comments.jsonl
Supported comments.json forms:
- JSON array:
[{"note_url": "...", "comment_text": "..."}]
- JSON object with
items: {"items": [{"note_url": "...", "comment_text": "..."}]}
- JSON mapping:
{"https://.../explore/...": "่ฏ่ฎบๅ
ๅฎน"}
Optional per-row fields:
delay_seconds
like_probability
meta
Delay rules
- Sensitive scripts accept
--delay-seconds <seconds>
- Shared default delay can be set with
REDNOTE_ACTION_DELAY_SECONDS
- Random delay range can be adjusted with:
REDNOTE_RANDOM_DELAY_MIN_SECONDS
REDNOTE_RANDOM_DELAY_MAX_SECONDS
Notes
- The search script can bootstrap the skill-local virtualenv automatically.
- If a note needs candidate images, use the sibling skill
skills/rednote-image-search first.