{"record":{"id":"8f6bc1042301ea3b","repo":"jackwener/OpenCLI","slug":"xiaohongshu-collection-dom-extraction-returned-mal","errorCode":null,"errorMessage":"xiaohongshu collection DOM extraction returned malformed rows","messagePattern":"xiaohongshu collection DOM extraction returned malformed rows","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/xiaohongshu/collection-helpers.js","lineNumber":234,"sourceCode":"    await page.goto('https://www.xiaohongshu.com/explore');\n    await page.wait(2);\n    await throwIfLoginWall(page);\n    const userId = unwrapBrowserResult(await page.evaluate(`() => {\n      const user = window.__INITIAL_STATE__?.user?.userInfo;\n      const info = user?._value ?? user ?? {};\n      return info.user_id || info.userId || info.userID || '';\n    }`));\n    const clean = toCleanString(userId);\n    if (!clean) {\n        throw new AuthRequiredError('www.xiaohongshu.com', 'Not logged into Xiaohongshu (could not resolve current user id)');\n    }\n    return clean;\n}\n\nexport async function extractNotesFromDom(page) {\n    const payload = unwrapBrowserResult(await page.evaluate(EXTRACT_COLLECTION_DOM_JS));\n    if (!Array.isArray(payload)) {\n        throw new CommandExecutionError('xiaohongshu collection DOM extraction returned malformed rows');\n    }\n    return payload.filter((item) => item?.id);\n}\n\nexport async function fetchXhsCollectionNotes(page, {\n    userId,\n    profileTab,\n    apiPattern,\n    limit,\n    emptyLabel,\n}) {\n    const capturedRequests = [];\n    await page.installInterceptor(apiPattern);\n    await page.goto(buildProfileCollectionUrl(userId, profileTab));\n    await page.wait(2);\n    await assertOnCollectionProfile(page, userId);\n    let notes = [];\n    for (let i = 0; i < 16; i++) {","sourceCodeStart":216,"sourceCodeEnd":252,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaohongshu/collection-helpers.js#L216-L252","documentation":"extractNotesFromDom runs the EXTRACT_COLLECTION_DOM_JS script in the page and expects a JSON array back. If page.evaluate resolves to anything other than an array, the DOM extraction layer is considered broken and a CommandExecutionError is thrown.","triggerScenarios":"The injected script returns undefined/null or an object because the collection DOM did not render, a selector/structure change broke extraction, or unwrapBrowserResult passed through a non-array value.","commonSituations":"XHS updated its DOM so the in-page script's try/catch returns a sentinel object, the collection page failed to load, or a CSP/anti-bot mechanism interfered with evaluate.","solutions":["Ensure the collection page fully loaded (wait for the note grid selector) before calling extractNotesFromDom.","Console-log the raw page.evaluate result to see what the script actually returned.","Update EXTRACT_COLLECTION_DOM_JS to match the current XHS DOM structure.","Add a null/undefined guard upstream or fall back to the API-based note fetch path."],"exampleFix":"// before\nif (!Array.isArray(payload)) throw new CommandExecutionError('...malformed rows');\n// after\nconst raw = unwrapBrowserResult(await page.evaluate(EXTRACT_COLLECTION_DOM_JS));\nif (!Array.isArray(payload)) {\n  console.error('DOM extraction returned:', raw);\n  throw new CommandExecutionError('...malformed rows');\n}","handlingStrategy":"type-guard","validationCode":"const raw = await page.evaluate(EXTRACT_COLLECTION_DOM_JS);\nconst payload = unwrapBrowserResult(raw);\nif (!Array.isArray(payload)) throw new Error(`DOM extraction returned ${typeof payload}`);","typeGuard":"function isNoteRows(v) {\n  return Array.isArray(v) && v.every(i => i == null || typeof i === 'object');\n}","tryCatchPattern":"try {\n  const rows = await extractNotesFromDom(page);\n} catch (e) {\n  if (e instanceof CommandExecutionError) {\n    await page.waitForSelector('.note-item', { timeout: 10000 }).catch(() => {});\n    return extractNotesFromDom(page);\n  }\n  throw e;\n}","preventionTips":["Wait for the note grid to render before extracting.","Log the raw evaluate result when extraction fails to spot DOM changes.","Keep EXTRACT_COLLECTION_DOM_JS updated against XHS markup changes.","Fall back to the API path when DOM extraction yields non-array data."],"tags":["scraping","dom","browser-automation"],"backgroundTag":"malformed-scrape-payload","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}