jackwener/OpenCLI · error · CommandExecutionError
Xiaohongshu media extraction returned malformed payload.
Error message
Xiaohongshu media extraction returned malformed payload.
What it means
CommandExecutionError thrown when the page.evaluate extraction (buildDownloadExtractJs) returns something that is not an object with a `media` array. This means the extraction script did not run in the expected page context or the page DOM changed so the script returned null/undefined/a primitive. It guards downstream code that assumes `data.media` is iterable.
Source
Thrown at clis/xiaohongshu/download.js:231
args: [
{ name: 'note-id', positional: true, required: true, help: 'Full Xiaohongshu note URL with xsec_token, or xhslink short link' },
{ name: 'output', default: './xiaohongshu-downloads', help: 'Output directory' },
],
columns: ['index', 'type', 'status', 'size'],
func: async (page, kwargs) => {
const rawInput = String(kwargs['note-id']);
const output = kwargs.output;
const noteId = parseNoteId(rawInput);
await page.goto(buildNoteUrl(rawInput, { allowShortLink: true, commandName: 'xiaohongshu download' }));
await page.wait({ time: 1 + Math.random() * 2 });
const data = await page.evaluate(buildDownloadExtractJs(noteId));
if (data?.securityBlock) {
throw new CliError('SECURITY_BLOCK', 'Xiaohongshu security block: the note detail page was blocked by risk control.', /^https?:\/\//.test(rawInput)
? 'The page may be temporarily restricted. Try again later or from a different session.'
: 'Try using a full URL from search results (with xsec_token) instead of a bare note ID.');
}
if (!data || typeof data !== 'object' || !Array.isArray(data.media)) {
throw new CommandExecutionError('Xiaohongshu media extraction returned malformed payload.');
}
if (data.media.length === 0) {
throw new EmptyResultError('xiaohongshu download', 'No downloadable media found on this note.');
}
// Extract cookies for authenticated downloads
const cookies = formatCookieHeader(await page.getCookies({ domain: 'xiaohongshu.com' }));
const resolvedNoteId = typeof data.noteId === 'string' && data.noteId.trim()
? data.noteId.trim()
: noteId;
return downloadMedia(data.media, {
output,
subdir: resolvedNoteId,
cookies,
filenamePrefix: resolvedNoteId,
timeout: 60000,
});
},
});View on GitHub (pinned to 49907e53dc)
Solutions
- Re-run once — it may be a transient render race; increase the pre-evaluate wait.
- Confirm you can see the note normally in a browser session with the same cookies.
- Inspect the raw evaluate return to see what the page actually returned and update buildDownloadExtractJs selectors for the new DOM.
- Ensure the input URL actually resolves to a note detail page (not a profile or redirect).
Example fix
// before
await page.goto(url);
const data = await page.evaluate(buildDownloadExtractJs(noteId));
// after
await page.goto(url);
await page.wait({ time: 2 + Math.random() * 2 }); // let note content mount
const data = await page.evaluate(buildDownloadExtractJs(noteId));
if (!data || !Array.isArray(data?.media)) throw new Error('unexpected page: ' + await page.title()); Defensive patterns
Strategy: type-guard
Validate before calling
null
Type guard
function isExtractPayload(data) {
return data !== null && typeof data === 'object' && Array.isArray(data.media);
} Try / catch
try {
await cli.run('xiaohongshu download', { input: url });
} catch (err) {
if (/malformed payload/.test(err.message)) {
// page didn't render as expected: increase wait, retry once, then surface
await sleep(3000);
return downloadNote(url);
}
throw err;
} Prevention
- Keep the extraction script's selectors in sync with the current Xiaohongshu DOM.
- Add a settle wait before page.evaluate so dynamic note content has mounted.
- Check for login/captcha redirects before running the extractor.
- Validate the payload shape immediately after evaluate to fail fast with context.
When it happens
Trigger: page.evaluate returns null or a non-object because the note page did not render the expected structure; the extraction script threw silently and returned undefined; a DOM redesign changed the fields buildDownloadExtractJs reads; the script ran on a login/redirect page instead of the note page.
Common situations: Xiaohongshu shipped a front-end update that broke the extractor; the page redirected to login or a captcha (without setting securityBlock); a race where evaluate ran before the note content mounted.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- No downloadable media found on this note.
- archive search returned malformed payload: result row is mis
- archive search returned malformed payload for "${id}": downl
- archive snapshots returned malformed CDX payload: missing "$
- Booking.com hotel row is missing stable name/url identity
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/73a30e86ae6d906e.
Report an issue: GitHub.