| name | xiaohongshu-note-image-ocr |
| description | Extract image text from a Xiaohongshu note when the browser is blocked or the page requires app/login. Fetch the public share page HTML directly, parse __INITIAL_STATE__, extract image URLs, then OCR each image with vision. |
| version | 1.0.0 |
| author | Hermes Agent |
| license | MIT |
| metadata | {"hermes":{"tags":["xiaohongshu","xhs","ocr","image-text","social-media"],"related_skills":["ocr-and-documents"]}} |
Xiaohongshu note image OCR
Use when the user gives a Xiaohongshu share link and wants text from note images.
Why this exists
Browser access may hit Xiaohongshu risk control (300012 IP存在风险) even when the raw public note HTML is still fetchable via requests/https. In that case, do not stop at the browser block.
Workflow
- Try the shared URL in browser first if useful for confirmation.
- If browser shows risk control / login wall, fetch the note URL directly with terminal using a mobile Safari UA.
- Follow redirects; Xiaohongshu often lands on
/discovery/item/<note_id>....
- Parse the page HTML for
__INITIAL_STATE__=.
- Extract the JS object payload by brace matching, because simple regex-to-
</script> can break.
- Replace JS
undefined values with JSON null before parsing.
- Read note metadata from
obj.noteData.data.noteData:
title
desc
user.nickName
imageList
- Normalize image URLs to
https://.
- OCR each image with
vision_analyze, asking for strict verbatim transcription in reading order and [不清] for unclear text.
- If the user wants a pure Markdown note, merge:
- note title
- author
- cleaned post body
- cleaned image OCR text
- Remove platform noise in the cleaned Markdown pass:
小红书
剪映
- page counters / slide numbers when obvious
- share slogans / link口令
- For multiple links, process each note separately, then join them into one Markdown file with
--- between notes.
- Write the final Markdown to a file with
write_file when the user asks for a reusable artifact.
Proven commands
Fetch page HTML
python - <<'PY'
import requests
url='SHARE_URL_HERE'
headers={
'User-Agent':'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
'Accept-Language':'zh-CN,zh;q=0.9,en;q=0.8',
}
r=requests.get(url, headers=headers, timeout=20, allow_redirects=True)
print(r.status_code)
print(r.url)
print(r.text[:2000])
PY
Parse __INITIAL_STATE__ and list image URLs
node - <<'NODE'
const https=require('https');
const page='FULL_DISCOVERY_ITEM_URL_HERE';
function extractObj(s,start){
let i=start; while(s[i] !== '{') i++;
let depth=0,inStr=false,esc=false;
for(let j=i;j<s.length;j++){
const ch=s[j];
if(inStr){
if(esc) esc=false;
else if(ch==='\\') esc=true;
else if(ch==='"') inStr=false;
} else {
if(ch==='"') inStr=true;
else if(ch==='{') depth++;
else if(ch==='}') { depth--; if(depth===0) return s.slice(i,j+1); }
}
}
return null;
}
https.get(page,{headers:{'User-Agent':'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1'}},res=>{
let data='';
res.on('data',c=>data+=c);
res.on('end',()=>{
let js=extractObj(data, data.indexOf('__INITIAL_STATE__='));
js=js.replace(/:undefined/g,':null').replace(/,undefined([,}\]])/g,',null$1');
const obj=JSON.parse(js);
const imgs=obj.noteData.data.noteData.imageList.map(x=>x.url.replace(,));
console.log(imgs.join());
});
});
NODE
Expected data shape
Useful fields inside obj.noteData.data.noteData:
title
desc
user.nickName
imageList[]
Each imageList[] item often contains:
url
width
height
infoList[]
OCR prompt that worked well
请尽量完整逐字提取这张图片里的所有可见文字。按阅读顺序输出。不要总结,只做OCR转写。看不清处用[不清]。
Pitfalls
- Browser block does not mean the note is inaccessible via raw HTML fetch.
- The HTML payload is JS object syntax, not strict JSON.
undefined must be normalized.
- Regex extraction of the whole state blob is brittle; use brace matching.
- OCR quality may drop on long dense slides. Return
[不清] instead of hallucinating.
- If there are many images, batch OCR in groups with parallel tool calls.
Verification
- Confirm
noteData.data.noteData.title matches the shared note.
- Count
imageList.length before OCR so you know expected image count.
- Spot-check first OCR result against the title slide before processing all images.
Output guidance
- OCR mode: return
图1, 图2, ... with raw extracted text.
- Clean mode: remove watermarks/page markers and normalize broken line wraps.
- Markdown mode: produce a single
.md file containing:
# <note title>
**作者**:<nickName>
## 帖子正文
- cleaned body text
## 配图文字整理
- cleaned merged OCR text, preserving headings/lists where visible
- Multi-note Markdown mode: one H1 top-level collection title, then one H2 per note.
- Always say when OCR is uncertain instead of filling gaps.
Reusable prompt pattern
When the user says things like:
- “把这篇/这几篇小红书整理成纯 Markdown”
- “提取图片文字并清洗”
- “给我一个可复制的 md 文件”
Do the full pipeline automatically without asking follow-up questions unless a link is invalid.