{"record":{"id":"965426802e49989d","repo":"jackwener/OpenCLI","slug":"ctrip-flight","errorCode":null,"errorMessage":"ctrip flight","messagePattern":"ctrip flight","errorType":"exception","errorClass":"EmptyResultError","httpStatus":null,"severity":"info","filePath":"clis/ctrip/flight.js","lineNumber":199,"sourceCode":"            typeof page?.readNetworkCapture !== 'function' ||\n            !await page.startNetworkCapture(CAPTURE_PATTERN)) {\n            throw new CommandExecutionError('Ctrip flight requires browser response interception');\n        }\n        await page.readNetworkCapture();\n        await page.goto(searchUrl);\n        // The initial document can finish before the large batchSearch body.\n        // The first rendered card is only a readiness signal; row data still\n        // comes exclusively from the structured response below.\n        const readiness = await page.evaluate(WAIT_FOR_BATCH_CAPTURE_JS);\n        if (readiness === 'captcha') {\n            throw new AuthRequiredError('flights.ctrip.com', 'Ctrip is asking for a captcha; complete it in your browser session and retry');\n        }\n        const itineraries = parseBatchSearchCaptures(await page.readNetworkCapture());\n        if (!itineraries) {\n            throw new TimeoutError('Ctrip flight API capture', CAPTURE_TIMEOUT_SECONDS, 'No batchSearch response was observed after opening the results page.');\n        }\n        if (itineraries.length === 0) {\n            throw new EmptyResultError('ctrip flight', `No flights for ${fromCode}→${toCode} on ${date}`);\n        }\n        const rows = itineraries\n            .map((itinerary, index) => mapItinerary(itinerary, searchUrl, index))\n            // The page groups direct flights before transfers, then applies its\n            // displayed starting price and departure-time order inside each group.\n            .sort(([rowA, connectingA, departureA, idA], [rowB, connectingB, departureB, idB]) =>\n                Number(connectingA) - Number(connectingB) || rowA.price - rowB.price ||\n                departureA.localeCompare(departureB) || idA.localeCompare(idB))\n            .slice(0, limit)\n            .map(([row], index) => ({\n                rank: index + 1,\n                airline: row.airline,\n                flightNo: row.flightNo,\n                aircraft: row.aircraft,\n                departureTime: row.departureTime,\n                departureAirport: row.departureAirport,\n                arrivalTime: row.arrivalTime,\n                arrivalAirport: row.arrivalAirport,","sourceCodeStart":181,"sourceCodeEnd":217,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/ctrip/flight.js#L181-L217","documentation":"An EmptyResultError raised when the batchSearch capture succeeded but payload.data.flightItineraryList yielded zero unique itineraries for the requested route/date. The command reports 'ctrip flight' as the empty source plus a human-readable summary like 'No flights for BJS→SHA on 2026-09-01'. This is a legitimate empty result, not a failure of capture or parsing.","triggerScenarios":"Running `ctrip flight <from> <to> --date <date>` for a route/date with no bookable inventory: no airline serves the pair, the date is beyond Ctrip's booking horizon (usually ~1 year, often much shorter for some routes), all flights sold out, or the from/to codes resolve to airports with no connections.","commonSituations":"Searching dates far in the future (schedules not yet loaded); obscure regional airport pairs; past dates; mistyping an IATA code so the pair is nonsensical yet valid per parseIataCode; seasonal routes searched out of season.","solutions":["Verify the route actually has flights (search on flights.ctrip.com manually for the same pair/date).","Pick a nearer date within Ctrip's sellable window.","Double-check both IATA codes — city codes (BJS) vs airport codes (PEK) behave differently; try the other form.","Handle EmptyResultError in your caller if empty route/date combinations are expected in your workflow."],"exampleFix":"// before: crash on empty result\nconst rows = await ctripFlight({ from, to, date });\n\n// after: treat empty as a valid outcome\nlet rows;\ntry { rows = await ctripFlight({ from, to, date }); }\ncatch (e) { if (e instanceof EmptyResultError) rows = []; else throw e; }","handlingStrategy":"try-catch","validationCode":"// Skip obviously impossible searches before calling\nfunction isPlausibleQuery(from, to, date) {\n  const d = new Date(date + 'T00:00:00Z');\n  const now = new Date();\n  return from !== to && d > now && d < new Date(now.getTime() + 330 * 86400000);\n}","typeGuard":null,"tryCatchPattern":"let rows = [];\ntry {\n  rows = await ctripFlight({ from, to, date });\n} catch (e) {\n  if (e instanceof EmptyResultError) {\n    console.warn(`No flights: ${e.message}`); // treat as valid empty\n  } else throw e;\n}","preventionTips":["Search dates within Ctrip's bookable window (not in the past, not ~1yr+ ahead).","Verify route pairs have service on flights.ctrip.com before automating.","Distinguish city codes (BJS) from airport codes (PEK) when constructing routes.","Handle EmptyResultError as data, not failure, in batch workflows."],"tags":["empty-result","no-data","scraper"],"backgroundTag":"empty-result-set","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}