{"record":{"id":"a0c84bb7fe375aef","repo":"jackwener/OpenCLI","slug":"ctrip-round-trip-flight-dom-extraction-returned-ma","errorCode":null,"errorMessage":"Ctrip round-trip flight DOM extraction returned malformed rows","messagePattern":"Ctrip round-trip flight DOM extraction returned malformed rows","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/ctrip/flight-round.js","lineNumber":92,"sourceCode":"            throw new ArgumentError(`--return (${ret}) must be on or after --depart (${depart})`);\n        }\n        const limit = parseListLimit(kwargs.limit);\n\n        const searchUrl =\n            `https://flights.ctrip.com/online/list/round-${fromCode.toLowerCase()}-${toCode.toLowerCase()}` +\n            `?depdate=${depart}_${ret}&cabin=Y_S_C_F&adult=1&child=0&infant=0`;\n        await page.goto(searchUrl);\n        const waitResult = await page.evaluate(WAIT_FOR_FLIGHTS_ROUND_JS);\n        if (waitResult === 'captcha') {\n            throw new AuthRequiredError('flights.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 round-trip flight page did not render flight cards (state=${String(waitResult)})`);\n        }\n        const renderedCardCount = await page.evaluate(buildScrollUntilJs(ROUND_CARD_SELECTOR, limit));\n        const raw = await page.evaluate(buildFlightExtractJs(ROUND_CARD_SELECTOR, false));\n        if (!Array.isArray(raw)) {\n            throw new CommandExecutionError('Ctrip round-trip flight DOM extraction returned malformed rows');\n        }\n        if (raw.length === 0) {\n            if (Number(renderedCardCount) > 0) {\n                throw new CommandExecutionError('Ctrip round-trip flight cards rendered but parser did not find required flight anchors');\n            }\n            throw new EmptyResultError('ctrip flight-round', `No round-trip flights for ${fromCode}→${toCode} on ${depart} / ${ret}`);\n        }\n        const completeRows = raw\n            .filter((r) => r.departureTime && r.departureAirport && r.arrivalTime && r.arrivalAirport && r.airline)\n            .slice(0, limit)\n            .map((r, i) => ({\n                rank: i + 1,\n                airline: r.airline,\n                flightNo: r.flightNo,\n                aircraft: r.aircraft,\n                departureTime: r.departureTime,\n                departureAirport: r.departureAirport,\n                arrivalTime: r.arrivalTime,","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/ctrip/flight-round.js#L74-L110","documentation":"A CommandExecutionError thrown when the in-page extraction script returns a non-array value, meaning the DOM extraction phase itself failed structurally rather than finding zero flights. This is treated as a scraper/contract violation (malformed rows), distinct from an empty but valid result (EmptyResultError).","triggerScenarios":"buildFlightExtractJs evaluated in the page returns null/undefined or a non-array (e.g. an exception object or wrong shape) instead of an array of extracted flight rows.","commonSituations":"Ctrip DOM changed so the extraction script's return path changed; the evaluate() call was intercepted by a page error; internal scraper bug producing a non-array; page replaced content mid-extraction.","solutions":["Retry — if it's a transient page mutation it may pass on rerun.","Update to the latest library version in case a DOM-drift fix has shipped.","Reproduce manually and file an issue with the wait state and page URL; this indicates parser breakage rather than bad input.","If you control timing, ensure no other automation is navigating the same page concurrently."],"exampleFix":"// before\nconst rows = await runFlightRound(args); // crashes on non-array\n// after\nlet rows;\ntry { rows = await runFlightRound(args); } catch (e) {\n  if (String(e.message).includes('malformed rows')) rows = await runFlightRound(args); // retry once\n  else throw e;\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":"function isMalformedRowsError(e) { return e && String(e.message || '').includes('malformed rows'); }","tryCatchPattern":"try { return await runFlightRound(args); }\ncatch (e) { if (isMalformedRowsError(e)) { await sleep(5000); return await runFlightRound(args); } throw e; }","preventionTips":["Retry once on 'malformed rows' — often a transient page mutation.","Avoid concurrent automation on the same browser session/page.","Report persistent occurrences as parser breakage with URL and version.","Pin the library version and test against Ctrip after announced UI changes."],"tags":["scraping","dom-extraction","ctrip","parser"],"backgroundTag":"dom-parser-drift","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}