XHS API Response Parsing#
Overview#
XHS-Downloader processes XiaoHongShu posts by extracting structured data from server-rendered HTML responses. The parsing pipeline has three sequential stages: HTML โ JSON extraction, JSON โ Namespace wrapping, and Namespace โ typed field extraction for metadata and download links.
Pipeline Architecture#
Stage 1: HTML โ JSON (Converter)#
Source: source/expansion/converter.py
Converter.run() chains three steps:
_extract_object(html)โ parses the HTML vialxmlXPath (//script/text()), then finds the script starting withwindow.__INITIAL_STATE__._convert_object(text)โ strips thewindow.__INITIAL_STATE__=prefix, removes illegal YAML control characters, then callsyaml.safe_load()to produce a Python dict._filter_object(data)โ navigates the dict with two fallback key paths:- Phone:
noteData โ data โ noteData - PC:
note โ noteDetailMap โ [-1] โ note
- Phone:
Returns the note-level dict, or {} on failure.
Stage 2: Dict โ Namespace Object#
Source: source/expansion/namespace.py
Namespace.__init__ calls generate_data_object(), which recursively converts every nested dict into a SimpleNamespace and every list into a list of SimpleNamespaces (or scalars).
The primary accessor is safe_extract(attribute_chain, default):
- Splits
attribute_chainon.and traverses attributes viagetattr. - Supports bracket-index notation:
"imageList[0]"indexes into a list element. - Returns
default(empty string by default) at any missing step โ never raises.
The class method Namespace.object_extract(data_object, attribute_chain) applies the same logic to an already-unwrapped SimpleNamespace (used when iterating list items like individual images).
In app.py, the Namespace is created at __generate_data_object() or via json_to_namespace() for script-server mode.
Stage 3: Field Extraction#
Metadata โ Explore#
Source: source/application/explore.py
Explore.run(data) returns a flat dict with all post metadata:
| Container Key | safe_extract path |
|---|---|
ๆถ่ๆฐ้ | interactInfo.collectedCount |
่ฏ่ฎบๆฐ้ | interactInfo.commentCount |
ๅไบซๆฐ้ | interactInfo.shareCount |
็น่ตๆฐ้ | interactInfo.likedCount |
ไฝๅๆ ็ญพ | tagList[*].name (joined) |
ไฝๅID | noteId |
ไฝๅๆ ้ข | title |
ไฝๅๆ่ฟฐ | desc |
ๅๅธๆถ้ด | time (ms โ formatted datetime) |
ๆๅๆดๆฐๆถ้ด | lastUpdateTime |
ไฝ่
ๆต็งฐ | user.nickname / user.nickName |
ไฝ่
ID | user.userId |
Content type is classified by __classify_works(): "video" type with a single imageList entry โ ่ง้ข๏ผ"video" with multiple โ ๅพ้๏ผ"normal" โ ๅพๆ.
Image Download Links โ Image#
Source: source/application/image.py
Image.get_image_link(data, format_):
- Extracts
imageListviadata.safe_extract("imageList", []). - Tries
urlDefaultper item first, falls back tourl. __extract_image_token(url)strips the CDN prefix and variant suffix:"/".join(url.split("/")[5:]).split("!")[0].- Builds final URLs:
autoformat:https://sns-img-bd.xhscdn.com/{token}- Fixed format (png/webp/jpeg/heic/avif):
https://ci.xiaohongshu.com/{token}?imageView2/format/{format_}
- Also returns live-image links per item from
stream.h264[0].masterUrl.
Returns (download_urls: list, live_urls: list) โ stored as container["ไธ่ฝฝๅฐๅ"] and container["ๅจๅพๅฐๅ"].
Video Download Links โ Video#
Source: source/application/video.py
Video.deal_video_link(data, preference) first checks video.consumer.originVideoKey for a direct CDN key; if absent, collects H.264/H.265 stream objects from video.media.stream.h264 / video.media.stream.h265, sorts by preference (resolution / bitrate / size), and returns the best backupUrls[0] or masterUrl. (See Video Quality Selection)
Container Assembly (app.py)#
Source: source/application/app.py
__deal_extract() orchestrates the full pipeline:
- Fetches HTML โ wraps in
Namespace. - Calls
_extract_data()โExplore.run()โ metadata dict . - Merges
ไฝๅ้พๆฅinto the dict and calls_deal_download_tasks(). _deal_download_tasks()routes byไฝๅ็ฑปๅ:__extract_video()or__extract_image()populateไธ่ฝฝๅฐๅandๅจๅพๅฐๅ.save_data()serializes lists to space-separated strings before writing to the data recorder.
The script-server path (deal_script_tasks) accepts pre-parsed JSON directly and wraps it via json_to_namespace() , skipping the HTML fetching stage.
Key Source Files#
| File | Role |
|---|---|
source/expansion/converter.py | HTML โ note dict extraction |
source/expansion/namespace.py | Dict โ traversable Namespace |
source/application/explore.py | Metadata field extraction |
source/application/image.py | Image URL construction |
source/application/video.py | Video URL selection |
source/application/app.py | Pipeline orchestration |