{"record":{"id":"c83c3c44e98679bb","repo":"jackwener/OpenCLI","slug":"ctrip-hotel-detail-ssr-extraction-returned-malform","errorCode":null,"errorMessage":"Ctrip hotel detail SSR extraction returned malformed data","messagePattern":"Ctrip hotel detail SSR extraction returned malformed data","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/ctrip/hotel.js","lineNumber":49,"sourceCode":"        'star', 'score', 'scoreLabel', 'reviewCount', 'ratingBreakdown',\n        'facilities', 'checkInOut',\n        'cityName', 'address', 'lat', 'lon',\n        'url',\n    ],\n    func: async (page, kwargs) => {\n        const hotelId = parseHotelId(kwargs.id);\n        const url = buildHotelDetailUrl(hotelId);\n        await page.goto(url);\n        const waitResult = await page.evaluate(WAIT_FOR_HOTEL_DETAIL_JS);\n        if (waitResult === 'captcha') {\n            throw new AuthRequiredError('hotels.ctrip.com', 'Ctrip is asking for a captcha; complete it in your browser session and retry');\n        }\n        if (waitResult !== 'content') {\n            throw new CommandExecutionError(`Ctrip hotel detail page did not expose SSR hotel data (state=${String(waitResult)})`);\n        }\n        const detail = await page.evaluate(buildHotelDetailExtractJs());\n        if (!detail || typeof detail !== 'object') {\n            throw new CommandExecutionError('Ctrip hotel detail SSR extraction returned malformed data');\n        }\n        if (!detail.hotelId || !detail.name) {\n            throw new EmptyResultError('ctrip hotel', `No detail exposed for hotel id ${hotelId}`);\n        }\n        return [{ ...detail, url }];\n    },\n});\n","sourceCodeStart":31,"sourceCodeEnd":57,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/ctrip/hotel.js#L31-L57","documentation":"CommandExecutionError thrown when the SSR extraction step returns null or a non-object even though the page reached the 'content' state. The wait script said content was ready, but buildHotelDetailExtractJs() failed to assemble a detail object, indicating the DOM nodes it reads exist but its data assembly broke.","triggerScenarios":"buildHotelDetailExtractJs() evaluates to null/undefined/a primitive: the detail container renders but inner data blocks it reads are absent (partial render, hydration race), or the script throws internally and page.evaluate surfaces null.","commonSituations":"Ctrip hydrating detail data client-side after the extract runs; regional page variants missing fields the extract script reads; Ctrip markup update after the wait selector still matched; corrupted/partial page load on flaky networks.","solutions":["Add a short delay or wait for the detail data node before running buildHotelDetailExtractJs()","Log the raw extraction output and update buildHotelDetailExtractJs to current markup","Update the CLI to the latest version","Wrap extraction in try/catch inside the page and return diagnostics"],"exampleFix":"// before\nconst detail = await page.evaluate(buildHotelDetailExtractJs());\nif (!detail || typeof detail !== 'object') throw new CommandExecutionError('...malformed data');\n// after\nawait page.waitForSelector(DETAIL_DATA_SELECTOR, { timeout: 10000 });\nconst detail = await page.evaluate(buildHotelDetailExtractJs());\nif (!detail || typeof detail !== 'object') throw new CommandExecutionError('...malformed data');","handlingStrategy":"try-catch","validationCode":"await page.waitForSelector(DETAIL_DATA_SELECTOR, { timeout: 10000 }); // ensure data node exists before extracting\nconst detail = await page.evaluate(buildHotelDetailExtractJs());\nif (!detail || typeof detail !== 'object') throw new Error('detail extraction malformed');","typeGuard":"const isDetailObject = (v) => !!v && typeof v === 'object' && !Array.isArray(v);","tryCatchPattern":"try {\n  return await ctrip.hotel({ id });\n} catch (e) {\n  if (e instanceof CommandExecutionError && /malformed data/.test(e.message)) {\n    // hydration race: wait longer and retry once, else escalate as markup drift\n    await sleep(3000);\n    return await ctrip.hotel({ id });\n  }\n  throw e;\n}","preventionTips":["Wait for the detail data node, not just the content shell, before extracting","Log the raw extraction result on failure","Keep the extract script updated against current markup","Detect repeated malformed extractions as a page-structure change signal"],"tags":["scraping","ssr","extraction","hydration"],"backgroundTag":"ssr-extract-returned-non-array","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}