{"record":{"id":"0a5c6189bdb42927","repo":"jackwener/OpenCLI","slug":"hotels-ctrip-com","errorCode":null,"errorMessage":"hotels.ctrip.com","messagePattern":"hotels\\.ctrip\\.com","errorType":"exception","errorClass":"AuthRequiredError","httpStatus":null,"severity":"warning","filePath":"clis/ctrip/hotel-search.js","lineNumber":101,"sourceCode":"    columns: [\n        'rank', 'hotelId', 'name', 'enName',\n        'star', 'score', 'scoreLabel', 'reviewCount',\n        'cityName', 'district', 'address',\n        'lat', 'lon',\n        'price', 'currency', 'url',\n    ],\n    func: async (page, kwargs) => {\n        const cityId = parseCityId(kwargs.city);\n        const checkin = parseIsoDate('checkin', kwargs.checkin);\n        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        }","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/ctrip/hotel-search.js#L83-L119","documentation":"An AuthRequiredError raised when the hotels.ctrip.com list page signals a captcha gate: the WAIT_FOR_SSR_JS probe detected the page path containing 'captcha' or anti-bot text (验证码, verify the human) instead of SSR hotel data in window.__NEXT_DATA__. The domain string 'hotels.ctrip.com' is the error's first argument and becomes the message. It means risk control intercepted the visit and a human session must clear the challenge.","triggerScenarios":"Navigating to https://hotels.ctrip.com/hotels/list?city=...&checkin=...&checkout=... when Ctrip redirects to /captcha or overlays a verification prompt — typically from flagged IPs, rapid repeated queries, or anonymous browser profiles without trusted cookies.","commonSituations":"Bulk city queries in a loop from a datacenter IP; CI environments with no persistent cookies; shared proxies previously abused; new/unwarmed browser profiles; Ctrip tightening risk control during peak travel periods.","solutions":["Complete the captcha in your linked human browser session, then rerun the command.","Slow down: add delays between city searches and avoid large sequential batches.","Reuse a logged-in Ctrip browser profile so traffic carries trusted cookies.","Switch to a residential IP if datacenter IPs are consistently flagged."],"exampleFix":"// before: 50 cities back-to-back from an anonymous profile\nfor (const city of cities) await hotelSearch({ city, checkin, checkout });\n\n// after: warm session + throttle\nawait browserLogin('ctrip');\nfor (const city of cities) {\n  await hotelSearch({ city, checkin, checkout });\n  await sleep(3000 + Math.random() * 4000);\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const rows = await ctripHotelSearch({ city, checkin, checkout });\n} catch (e) {\n  if (e instanceof AuthRequiredError && e.message.includes('hotels.ctrip.com')) {\n    await notifyHuman('Ctrip captcha on hotels.ctrip.com — complete it in the browser session');\n    return retryWithBackoff(() => ctripHotelSearch({ city, checkin, checkout }), { max: 2 });\n  }\n  throw e;\n}","preventionTips":["Reuse a logged-in, cookie-persistent browser profile for automated hotel queries.","Throttle bulk city searches with randomized delays.","Avoid datacenter IPs; use residential connections where possible.","Pause the whole workflow at the first captcha instead of continuing to other cities."],"tags":["captcha","anti-bot","authentication","browser"],"backgroundTag":"captcha-required","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}