Files
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

74 lines
2.4 KiB
Python

"""Parse note data from Xiaohongshu HTML pages (SSR __INITIAL_STATE__).
This module provides an alternative to the feed API for reading notes.
The HTML endpoint embeds note data in a server-rendered `window.__INITIAL_STATE__`
object, which does not require a valid xsec_token to access.
"""
from __future__ import annotations
import json
import logging
import re
from typing import Any
from .exceptions import XhsApiError
logger = logging.getLogger(__name__)
# Regex to extract the __INITIAL_STATE__ JSON blob from the HTML.
_STATE_PATTERN = re.compile(r"window\.__INITIAL_STATE__=({.*?})\s*</script>", re.DOTALL)
def parse_initial_state(html: str) -> dict[str, Any]:
"""Extract and parse `window.__INITIAL_STATE__` from an XHS note page.
The server-rendered HTML contains a global state object with note data.
XHS uses bare `undefined` values in the JS object which are not valid JSON,
so we replace them before parsing.
"""
match = _STATE_PATTERN.search(html)
if not match:
raise XhsApiError("Could not parse __INITIAL_STATE__ from HTML")
raw = match.group(1)
# Replace bare `undefined` with empty string (not valid JSON)
cleaned = re.sub(r":\s*undefined", ':""', raw)
cleaned = re.sub(r",\s*undefined", ',""', cleaned)
try:
return json.loads(cleaned)
except json.JSONDecodeError as exc:
raise XhsApiError(f"Failed to parse __INITIAL_STATE__ JSON: {exc}") from None
def extract_note_from_state(
state: dict[str, Any],
note_id: str,
) -> dict[str, Any]:
"""Extract a single note dict from the parsed __INITIAL_STATE__.
The state structure is:
state.note.noteDetailMap[note_id].note -> full note object
"""
detail_map = state.get("note", {}).get("noteDetailMap", {})
if not detail_map:
raise XhsApiError("Note not found in HTML state: empty noteDetailMap")
# Try exact noteId first, then fall back to first entry
entry = detail_map.get(note_id)
if entry is None:
entry = next(iter(detail_map.values()), None)
if entry and isinstance(entry, dict) and "note" in entry:
return entry["note"]
raise XhsApiError("Note not found in HTML state")
def extract_note_from_html(html: str, note_id: str) -> dict[str, Any]:
"""High-level: parse HTML → extract note in one step."""
state = parse_initial_state(html)
return extract_note_from_state(state, note_id)