{"record":{"id":"e8407e6dc769cf95","repo":"jackwener/OpenCLI","slug":"ctrip-is-asking-for-a-captcha-complete-it-in-your-e8407e","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/ferry.js","lineNumber":58,"sourceCode":"        'departureTime', 'fromPort',\n        'arrivalTime', 'toPort',\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 = buildFerryListUrl(fromCity, toCity, date);\n        await page.goto(searchUrl);\n        const waitResult = await page.evaluate(WAIT_FOR_FERRY_JS);\n        if (waitResult === 'captcha') {\n            throw new AuthRequiredError('ship.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 ferry page did not render sailing rows (state=${String(waitResult)})`);\n        }\n        const renderedCardCount = await page.evaluate(buildScrollUntilJs('.list-item-parent', limit));\n        const raw = await page.evaluate(buildFerryExtractJs());\n        if (!Array.isArray(raw)) {\n            throw new CommandExecutionError('Ctrip ferry DOM extraction returned malformed rows');\n        }\n        if (raw.length === 0) {\n            if (Number(renderedCardCount) > 0) {\n                throw new CommandExecutionError('Ctrip ferry rows rendered but parser did not find required sailing anchors');\n            }\n            throw new EmptyResultError('ctrip ferry', `No sailings for ${fromCity} to ${toCity} on ${date}`);\n        }\n        return raw.slice(0, limit).map((r, i) => ({\n            rank: i + 1,\n            shipName: r.shipName,","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/ctrip/ferry.js#L40-L76","documentation":"AuthRequiredError thrown when WAIT_FOR_FERRY_JS detects the captcha state on ship.ctrip.com after navigating to the ferry results deep link. The library drives the user's real cookie session (Strategy.COOKIE) and will not bypass captchas automatically, so it raises this error instructing the user to solve the challenge in the browser session and retry. Thrown before any scrolling or extraction, so no sailing data is available.","triggerScenarios":"Calling `ctrip ferry <from> <to> --date <d>` where, right after page.goto(buildFerryListUrl(...)), the in-page probe finds Ctrip's anti-bot captcha overlay instead of sailing rows — triggered by expired/stale cookies, suspicious IP, or aggressive query frequency.","commonSituations":"Batch-querying many ferry routes/dates until Ctrip's risk engine challenges the session; running from datacenter/VPN IPs; cookie export grown stale; shared IP already flagged by other users.","solutions":["Open ship.ctrip.com in the CLI's browser session, complete the captcha, then rerun the command","Re-export fresh login cookies for ship.ctrip.com and retry","Slow down / spread out queries and add delays between ferry searches","Use a residential network or rotate to a trusted IP"],"exampleFix":"// before: hammering many dates triggers captcha\nfor (const d of dates) {\n  results.push(await run(['ctrip', 'ferry', '--from', f, '--to', t, '--date', d]));\n}\n// after: handle captcha and pace requests\nfor (const d of dates) {\n  try {\n    results.push(await run(['ctrip', 'ferry', '--from', f, '--to', t, '--date', d]));\n  } catch (e) {\n    if (e.name === 'AuthRequiredError') {\n      await promptCaptchaSolve();\n      results.push(await run(['ctrip', 'ferry', '--from', f, '--to', t, '--date', d]));\n    } else throw e;\n  }\n  await sleep(5000);\n}","handlingStrategy":"try-catch","validationCode":"// Pre-flight probe of ship.ctrip.com session health before running the actual query\nconst probe = await page.goto('https://ship.ctrip.com/').then(() => page.evaluate(WAIT_FOR_FERRY_JS));\nif (probe === 'captcha') console.error('Solve the Ctrip captcha in your browser session before querying ferries');","typeGuard":"function isAuthRequiredError(e) {\n  return e instanceof Error && e.name === 'AuthRequiredError';\n}","tryCatchPattern":"try {\n  return await run(['ctrip', 'ferry', '--from', f, '--to', t, '--date', d]);\n} catch (e) {\n  if (isAuthRequiredError(e)) {\n    await promptUserToSolveCaptcha('ship.ctrip.com');\n    return retry(() => run(['ctrip', 'ferry', '--from', f, '--to', t, '--date', d]), { retries: 1 });\n  }\n  throw e;\n}","preventionTips":["Refresh exported ship.ctrip.com cookies regularly; stale cookies raise risk scores","Pace ferry queries (seconds between requests) and avoid large batch sweeps","Run from residential IPs; avoid datacenter/VPN ranges Ctrip flags","Probe session health once before batch runs and abort early on captcha"],"tags":["captcha","auth-required","anti-bot","ctrip","ship"],"backgroundTag":"captcha-challenge-blocked","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}