{"record":{"id":"84d2ea39ff3f2409","repo":"jackwener/OpenCLI","slug":"ctrip-hotel-search","errorCode":null,"errorMessage":"ctrip hotel-search","messagePattern":"ctrip hotel-search","errorType":"exception","errorClass":"EmptyResultError","httpStatus":null,"severity":"error","filePath":"clis/ctrip/hotel-search.js","lineNumber":111,"sourceCode":"        const checkout = parseIsoDate('checkout', kwargs.checkout);\n        assertCheckinBeforeCheckout(checkin, checkout);\n        const limit = parseHotelLimit(kwargs.limit);\n\n        const url = `https://hotels.ctrip.com/hotels/list?city=${cityId}&checkin=${checkin}&checkout=${checkout}`;\n        await page.goto(url);\n        const waitResult = await page.evaluate(WAIT_FOR_SSR_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-search page did not expose SSR hotel list (state=${String(waitResult)})`);\n        }\n        const raw = await page.evaluate(EXTRACT_HOTELS_JS);\n        if (!Array.isArray(raw)) {\n            throw new CommandExecutionError('Ctrip hotel-search returned malformed SSR hotel list');\n        }\n        if (raw.length === 0) {\n            throw new EmptyResultError('ctrip hotel-search', `No hotels for city=${cityId} on ${checkin} → ${checkout}`);\n        }\n        const rows = raw\n            .map((entry, i) => mapHotelRow(entry, i))\n            .filter((row) => row.hotelId && row.name)\n            .slice(0, limit);\n        if (rows.length === 0) {\n            throw new CommandExecutionError('Ctrip hotel-search SSR rows were missing required hotelId/name anchors');\n        }\n        return rows;\n    },\n});\n\nexport const __test__ = { parseHotelLimit, assertCheckinBeforeCheckout, WAIT_FOR_SSR_JS, EXTRACT_HOTELS_JS };\n","sourceCodeStart":93,"sourceCodeEnd":125,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/ctrip/hotel-search.js#L93-L125","documentation":"CommandExecutionError thrown when the SSR hotel list extracted from the Ctrip search page via EXTRACT_HOTELS_JS is not an array. The library expects page.evaluate to return an array of raw hotel entries; anything else (null, object, string) means the page structure changed or the page did not render the expected SSR data. It is thrown to fail fast instead of crashing later in mapHotelRow.","triggerScenarios":"page.evaluate(EXTRACT_HOTELS_JS) returns a non-array value after running the Ctrip hotel-search page: Ctrip redesigned its SSR data payload, a captcha/interstitial replaced the hotel list, or the extract script matched no window.__initialState-like object and returned null/undefined.","commonSituations":"Ctrip front-end update changing the SSR state key; scraping from a region that gets bot-challenged; running an outdated CLI against a new Ctrip page version; transient network issues producing a partial HTML shell.","solutions":["Re-run the search; transient interstitials often resolve on retry","Log the raw page.evaluate output and update EXTRACT_HOTELS_JS to the new SSR data shape","Verify the page is not showing a captcha and refresh your browser session/cookies","Update the CLI to the latest version with fixed selectors"],"exampleFix":"// before\nconst raw = await page.evaluate(EXTRACT_HOTELS_JS);\nif (!Array.isArray(raw)) {\n  throw new CommandExecutionError('Ctrip hotel-search returned malformed SSR hotel list');\n}\n// after\nlet raw = await page.evaluate(EXTRACT_HOTELS_JS);\nif (!Array.isArray(raw)) {\n  raw = await page.evaluate(EXTRACT_HOTELS_JS_FALLBACK_V2); // alternate selector\n  if (!Array.isArray(raw)) throw new CommandExecutionError('...');\n}","handlingStrategy":"try-catch","validationCode":"const raw = await page.evaluate(EXTRACT_HOTELS_JS);\nif (!Array.isArray(raw)) throw new Error('SSR hotel list is not an array — page shape changed or captcha shown');","typeGuard":"const isHotelRowArray = (v) => Array.isArray(v) && v.every((e) => e && typeof e === 'object');","tryCatchPattern":"try {\n  const rows = await ctrip.hotelSearch({ cityId, checkin, checkout });\n} catch (e) {\n  if (e instanceof CommandExecutionError && /malformed SSR hotel list/.test(e.message)) {\n    // retry once, then surface as upstream-structure failure\n  } else throw e;\n}","preventionTips":["Pin and update the CLI version when Ctrip changes markup","Pre-check for captcha pages before parsing SSR data","Log raw evaluate output on failure for fast diagnosis","Add a retry with backoff for transient interstitials"],"tags":["scraping","ssr","dom-changed","ctrip"],"backgroundTag":"ssr-extract-returned-non-array","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}