{"record":{"id":"be92d6153fa2e94b","repo":"jackwener/OpenCLI","slug":"ctrip-hotel","errorCode":null,"errorMessage":"ctrip hotel","messagePattern":"ctrip hotel","errorType":"exception","errorClass":"EmptyResultError","httpStatus":null,"severity":"info","filePath":"clis/ctrip/hotel.js","lineNumber":52,"sourceCode":"        '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":34,"sourceCodeEnd":57,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/ctrip/hotel.js#L34-L57","documentation":"EmptyResultError thrown when the extracted detail object exists but lacks hotelId or name — the minimum fields that constitute a usable hotel detail. The library treats a detail without identity fields as 'no detail exposed' for the given hotel id rather than returning partial data.","triggerScenarios":"buildHotelDetailExtractJs() returns an object whose hotelId/name keys are absent or empty: the page rendered but the identity block (title/hotel id) uses new markup, or the hotel is delisted and renders a shell page.","commonSituations":"Querying a closed/renamed hotel whose page still resolves; Ctrip renaming identity fields in its SSR payload; overseas/regional variants omitting these fields; stale CLI after a Ctrip update.","solutions":["Open the hotel URL in a browser to confirm the hotel page still exposes name/id","Check the hotel id is current (re-run hotel-search to get fresh ids)","Update mapHotelDetail/extract script if Ctrip renamed the fields","Update the CLI to the latest version"],"exampleFix":"// before\nif (!detail.hotelId || !detail.name) throw new EmptyResultError('ctrip hotel', `No detail exposed for hotel id ${hotelId}`);\n// after\nif (!detail.hotelId) detail.hotelId = String(hotelId); // fall back to the requested id\nif (!detail.hotelId || !detail.name) throw new EmptyResultError('ctrip hotel', `No detail exposed for hotel id ${hotelId}`);","handlingStrategy":"type-guard","validationCode":"// after extraction, before relying on the detail\nif (typeof detail?.name !== 'string' || !detail.name) console.warn(`hotel ${hotelId} exposes no name — possibly delisted`);","typeGuard":"const isUsableDetail = (d) => !!d && typeof d === 'object' && !!d.hotelId && typeof d.name === 'string' && d.name.length > 0;","tryCatchPattern":"try {\n  return await ctrip.hotel({ id });\n} catch (e) {\n  if (e instanceof EmptyResultError) {\n    return null; // hotel has no exposed detail — treat as not found\n  }\n  throw e;\n}","preventionTips":["Refresh hotel ids via hotel-search before fetching details","Verify delisted hotels manually in a browser","Fallback to requested id when only hotelId is missing","Track empty-detail rates per id source"],"tags":["empty-result","scraping","schema-drift"],"backgroundTag":"empty-result-set","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}