{"record":{"id":"41890367e0c0e17d","repo":"jackwener/OpenCLI","slug":"trip-com-is-asking-for-a-verification-complete-it-418903","errorCode":null,"errorMessage":"Trip.com is asking for a verification; complete it in your browser session and retry","messagePattern":"Trip\\.com is asking for a verification; complete it in your browser session and retry","errorType":"exception","errorClass":"AuthRequiredError","httpStatus":null,"severity":"warning","filePath":"clis/trip/train.js","lineNumber":53,"sourceCode":"    ],\n    columns: [\n        'rank',\n        'departureTime', 'fromStation',\n        'arrivalTime', 'toStation',\n        'duration', 'changes',\n        'url',\n    ],\n    func: async (page, kwargs) => {\n        const from = parseKeyword('from', kwargs.from);\n        const to = parseKeyword('to', kwargs.to);\n        const country = parseKeyword('country', kwargs.country);\n        const limit = parseListLimit(kwargs.limit);\n\n        const searchUrl = buildTrainRouteUrl(country, from, to);\n        await page.goto(searchUrl);\n        const waitResult = await page.evaluate(WAIT_FOR_TRAINS_JS);\n        if (waitResult === 'captcha') {\n            throw new AuthRequiredError('trip.com', 'Trip.com is asking for a verification; complete it in your browser session and retry');\n        }\n        if (waitResult !== 'content') {\n            throw new CommandExecutionError(`Trip.com train timetable did not render (state=${String(waitResult)}); check the city names and --country`);\n        }\n        const raw = await page.evaluate(buildTrainExtractJs());\n        if (!Array.isArray(raw)) {\n            throw new CommandExecutionError('Trip.com train DOM extraction returned malformed rows');\n        }\n        if (raw.length === 0) {\n            throw new EmptyResultError('trip train', `No timetable for ${from} to ${to} (${country})`);\n        }\n        return raw.slice(0, limit).map((r, i) => ({\n            rank: i + 1,\n            departureTime: r.departureTime,\n            fromStation: r.fromStation,\n            arrivalTime: r.arrivalTime,\n            toStation: r.toStation,\n            duration: r.duration,","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/trip/train.js#L35-L71","documentation":"This AuthRequiredError is thrown when the train timetable page's wait script reports 'captcha', meaning Trip.com inserted a human-verification challenge instead of rendering the train list. Like the tours flow, the library requires you to solve the CAPTCHA manually in the shared browser session before scraping can continue.","triggerScenarios":"page.evaluate(WAIT_FOR_TRAINS_JS) returning 'captcha' for a train route search — typically triggered by automation patterns, VPN/datacenter IPs, or high query frequency on train timetables.","commonSituations":"Batch-queriing many train routes back-to-back; running in CI from cloud IPs; Trip.com enforcing stricter anti-bot checks on its trains vertical.","solutions":["Complete the CAPTCHA in the interactive browser session, then rerun the command.","Throttle searches (delays between requests) and reduce batch size.","Switch to a residential IP or disable VPN.","Use a persistent, warmed browser profile with existing Trip.com cookies."],"exampleFix":"// before\nconst r = await page.evaluate(WAIT_FOR_TRAINS_JS); // throws on captcha, aborts batch\n// after\nlet r = await page.evaluate(WAIT_FOR_TRAINS_JS);\nif (r === 'captcha') { await promptUserToSolveCaptcha(page); r = await page.evaluate(WAIT_FOR_TRAINS_JS); }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function waitSaysCaptcha(r) { return r === 'captcha'; }","tryCatchPattern":"try {\n  const trains = await trainSearch(country, from, to);\n} catch (e) {\n  if (e instanceof AuthRequiredError) {\n    await pauseForHumanCaptcha(e.message);\n    return trainSearch(country, from, to);\n  }\n  throw e;\n}","preventionTips":["Rate-limit train timetable queries and add jitter between them.","Use a persistent, warmed browser profile with trip.com cookies.","Prefer residential IPs; avoid shared datacenter egress.","Intercept AuthRequiredError at the batch level: pause once for verification, then resume the batch."],"tags":["captcha","bot-detection","trains"],"backgroundTag":"captcha-challenge","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}