{"record":{"id":"3d7b4455bf952e06","repo":"jackwener/OpenCLI","slug":"xiaohongshu-collection-api-returned-a-note-without","errorCode":null,"errorMessage":"xiaohongshu collection API returned a note without stable id/xsec token","messagePattern":"xiaohongshu collection API returned a note without stable id/xsec token","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/xiaohongshu/collection-helpers.js","lineNumber":103,"sourceCode":"export function extractNotesFromResponses(requests, fallbackUserId) {\n    const rows = [];\n    const seen = new Set();\n    for (const req of requests ?? []) {\n        const payload = unwrapBrowserResult(req);\n        if (!isObject(payload)) {\n            throw new CommandExecutionError('xiaohongshu collection API returned a malformed payload');\n        }\n        const data = payload.data;\n        if (!isObject(data)) {\n            throw new CommandExecutionError('xiaohongshu collection API returned malformed data');\n        }\n        const notes = data.notes ?? data.note_list;\n        if (!Array.isArray(notes))\n            throw new CommandExecutionError('xiaohongshu collection API returned malformed notes');\n        for (const entry of notes) {\n            const row = mapCollectionNote(entry, { fallbackUserId });\n            if (!row?.id || !row.url.includes('xsec_token=')) {\n                throw new CommandExecutionError('xiaohongshu collection API returned a note without stable id/xsec token');\n            }\n            if (seen.has(row.id))\n                continue;\n            seen.add(row.id);\n            rows.push(row);\n        }\n    }\n    return rows;\n}\n\nexport const EXTRACT_COLLECTION_DOM_JS = `\n  (() => {\n    const normalizeUrl = (href) => {\n      if (!href) return '';\n      let url;\n      try {\n        url = new URL(href, 'https://www.xiaohongshu.com');\n      } catch {","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaohongshu/collection-helpers.js#L85-L121","documentation":"Each mapped note row must have a stable id and a URL containing xsec_token, which the library needs to open notes reliably later. If mapCollectionNote yields a row missing id or lacking an xsec_token query parameter, CommandExecutionError is thrown. This prevents emitting rows that would fail on subsequent detail-page access.","triggerScenarios":"A note entry in the API response lacks its note_id or its url/xsec_token field — e.g. deleted/hidden notes, partially populated entries, or a mapping change in mapCollectionNote's source fields.","commonSituations":"Scraping a collection containing notes the session cannot fully see (restricted or removed notes); xiaohongshu omitting xsec_token on some list entries; version change in the entry schema (e.g. nested note objects).","solutions":["Skip/drop offending entries instead of aborting: pre-filter notes whose entry lacks id or xsec_token before calling extractNotesFromResponses","Refresh login — restricted visibility can hide fields","Inspect the raw entry JSON to find where id/xsec_token now live and update mapping","Retry later if entries were transiently incomplete"],"exampleFix":"// before\nconst rows = extractNotesFromResponses(reqs, userId); // throws on one bad entry\n// after\nconst cleaned = reqs.map(req => {\n  const p = req.json;\n  const notes = p?.data?.notes ?? p?.data?.note_list ?? [];\n  return { ...p, data: { ...p.data, notes: notes.filter(n => n.id && String(n.url ?? '').includes('xsec_token=')) } };\n});\nconst rows = extractNotesFromResponses(cleaned, userId);","handlingStrategy":"validation","validationCode":"const usable = (entry) => entry && (entry.id ?? entry.note_id) && String(entry.url ?? entry.note_url ?? '').includes('xsec_token=');\n// pre-filter entries before extraction so one bad note doesn't abort the run","typeGuard":"const hasStableIdAndToken = (row) => typeof row?.id === 'string' && row.id.length > 0 && typeof row.url === 'string' && row.url.includes('xsec_token=');","tryCatchPattern":"try { rows = extractNotesFromResponses(reqs, userId); } catch (e) { if (String(e.message).includes('xsec token')) { console.warn('skipping notes missing xsec_token'); rows = partialExtract(reqs, userId); } else throw e; }","preventionTips":["Pre-filter note entries lacking id or xsec_token before extraction","Refresh login — restricted sessions omit token fields","Inspect raw entry JSON when the schema drifts and update mapping","Deduplicate by id to avoid relying on unstable entries"],"tags":["scraping","data-integrity","xsec-token","command-execution-error"],"backgroundTag":"missing-required-field","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}