{"record":{"id":"a2530d3b6b4e9ada","repo":"jackwener/OpenCLI","slug":"ctrip-is-asking-for-a-captcha-complete-it-in-your-a2530d","errorCode":null,"errorMessage":"Ctrip is asking for a captcha; complete it in your browser session and retry","messagePattern":"Ctrip is asking for a captcha; complete it in your browser session and retry","errorType":"exception","errorClass":"AuthRequiredError","httpStatus":null,"severity":"error","filePath":"clis/ctrip/bus.js","lineNumber":58,"sourceCode":"        'departureTime',\n        'fromStation', 'toStation',\n        'duration', 'price', 'status',\n        'url',\n    ],\n    func: async (page, kwargs) => {\n        const fromCity = parsePlaceName('from', kwargs.from);\n        const toCity = parsePlaceName('to', kwargs.to);\n        if (fromCity === toCity) {\n            throw new ArgumentError(`--from and --to must differ (got ${fromCity})`);\n        }\n        const date = parseIsoDate('date', kwargs.date);\n        const limit = parseListLimit(kwargs.limit);\n\n        const searchUrl = buildBusListUrl(fromCity, toCity, date);\n        await page.goto(searchUrl);\n        const waitResult = await page.evaluate(WAIT_FOR_BUS_JS);\n        if (waitResult === 'captcha') {\n            throw new AuthRequiredError('bus.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 bus page did not render schedule rows (state=${String(waitResult)})`);\n        }\n        const renderedCardCount = await page.evaluate(buildScrollUntilJs('.list-item-parent', limit));\n        const raw = await page.evaluate(buildBusExtractJs());\n        if (!Array.isArray(raw)) {\n            throw new CommandExecutionError('Ctrip bus DOM extraction returned malformed rows');\n        }\n        if (raw.length === 0) {\n            if (Number(renderedCardCount) > 0) {\n                throw new CommandExecutionError('Ctrip bus rows rendered but parser did not find required schedule anchors');\n            }\n            throw new EmptyResultError('ctrip bus', `No coaches for ${fromCity} to ${toCity} on ${date}`);\n        }\n        return raw.slice(0, limit).map((r, i) => ({\n            rank: i + 1,\n            departureTime: r.departureTime,","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/ctrip/bus.js#L40-L76","documentation":"After navigating to the bus list URL, the CLI runs WAIT_FOR_BUS_JS in the page and inspects the returned state. If Ctrip's anti-bot layer serves a captcha instead of content, the CLI throws AuthRequiredError for bus.ctrip.com so the user can solve the captcha in their own authenticated browser session and retry.","triggerScenarios":"`clis ctrip bus` navigating to buildBusListUrl(...) and page.evaluate(WAIT_FOR_BUS_JS) returning the literal string 'captcha' — Ctrip flagged the session as automated or the shared browser profile is rate-limited.","commonSituations":"Running many bus queries in a short window (rate limiting); headless or fresh browser profile with no Ctrip cookies; datacenter IP flagged by Ctrip's WAF; Ctrip tightening anti-bot rules.","solutions":["Open the same browser session manually, solve the captcha on bus.ctrip.com, then rerun the command","Reuse a persistent browser profile with valid Ctrip cookies instead of a fresh/headless one","Slow down request rate and add delays between Ctrip queries","Switch network/IP if your address is being flagged"],"exampleFix":"null","handlingStrategy":"try-catch","validationCode":"null","typeGuard":"null","tryCatchPattern":"try {\n  rows = await ctripBusList({ from, to, date });\n} catch (e) {\n  if (e instanceof AuthRequiredError && e.message.includes('captcha')) {\n    // Surface to the operator: manual captcha completion required, then retry once\n    await promptManualCaptchaResolution('bus.ctrip.com');\n    rows = await ctripBusList({ from, to, date });\n  } else {\n    throw e;\n  }\n}","preventionTips":["Use a persistent, warmed-up browser profile with valid Ctrip cookies","Rate-limit ctrip queries; add randomized delays between runs","Avoid headless mode and datacenter IPs when scraping ctrip","Catch AuthRequiredError distinctly and alert a human rather than blind-retrying"],"tags":["captcha","ctrip","anti-bot","auth"],"backgroundTag":"captcha-challenge-blocked","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}