{"record":{"id":"9901cc2239d662aa","repo":"jackwener/OpenCLI","slug":"ctrip-train-cards-rendered-but-parser-did-not-find","errorCode":null,"errorMessage":"Ctrip train cards rendered but parser did not find required train anchors","messagePattern":"Ctrip train cards rendered but parser did not find required train anchors","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/ctrip/train.js","lineNumber":69,"sourceCode":"        const limit = parseListLimit(kwargs.limit);\n\n        const searchUrl = buildTrainListUrl(fromName, toName, date);\n        await page.goto(searchUrl);\n        const waitResult = await page.evaluate(WAIT_FOR_TRAINS_JS);\n        if (waitResult === 'captcha') {\n            throw new AuthRequiredError('trains.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 train page did not render train cards (state=${String(waitResult)})`);\n        }\n        const renderedCardCount = await page.evaluate(buildScrollUntilJs('.card-white.list-item', limit));\n        const raw = await page.evaluate(buildTrainExtractJs());\n        if (!Array.isArray(raw)) {\n            throw new CommandExecutionError('Ctrip train DOM extraction returned malformed rows');\n        }\n        if (raw.length === 0) {\n            if (Number(renderedCardCount) > 0) {\n                throw new CommandExecutionError('Ctrip train cards rendered but parser did not find required train anchors');\n            }\n            throw new EmptyResultError('ctrip train', `No trains for ${fromName} to ${toName} on ${date}`);\n        }\n        return raw.slice(0, limit).map((r, i) => ({\n            rank: i + 1,\n            trainNo: r.trainNo,\n            departureTime: r.departureTime,\n            departureStation: r.departureStation,\n            arrivalTime: r.arrivalTime,\n            arrivalStation: r.arrivalStation,\n            duration: r.duration,\n            fromPrice: r.fromPrice,\n            seats: Array.isArray(r.seats) && r.seats.length ? r.seats.join(' / ') : null,\n            url: searchUrl,\n        }));\n    },\n});\n","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/ctrip/train.js#L51-L87","documentation":"Thrown as CommandExecutionError when the page visibly rendered train cards (scroll-until counted renderedCardCount > 0) but the extractor produced zero parseable rows. This means the DOM anchors the extractor requires (per-train identifiers) were not found, i.e. the page layout changed relative to the parser's expectations.","triggerScenarios":"buildScrollUntilJs('.card-white.list-item', limit) counted cards, but buildTrainExtractJs() returned an empty array because required train anchors (train number elements etc.) are missing or renamed in the DOM.","commonSituations":"Ctrip partial redesign: card wrapper class unchanged but inner anchor attributes/classes changed; lazy-rendered fields not present when extraction runs; A/B test variant lacking the expected anchors.","solutions":["Inspect a rendered card in DevTools and update the anchors required by buildTrainExtractJs.","Re-run after a full page load / extra settle delay in case anchors render late.","Check for Ctrip A/B variants by re-running in a fresh session."],"exampleFix":"// before (extractor selector)\ndocument.querySelector('.train-num')\n// after (updated to current markup)\ndocument.querySelector('[data-train-no], .train-number')","handlingStrategy":"retry","validationCode":"null","typeGuard":"null","tryCatchPattern":"try { return await runTrainList(opts); } catch (e) { if (e instanceof CommandExecutionError && /train anchors/.test(e.message)) { await sleep(3000); return retryOnce(opts); } throw e; }","preventionTips":["Allow extra settle time before extraction so late-rendered anchors exist","Update required-anchor selectors after Ctrip redesigns","Compare rendered card count vs extracted rows to detect drift early"],"tags":["scraping","dom-extraction","page-structure","selector-drift"],"backgroundTag":"selector-drift","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}