jackwener/OpenCLI · error · CommandExecutionError
Ctrip ferry rows rendered but parser did not find required…
Error message
Ctrip ferry rows rendered but parser did not find required sailing anchors
What it means
CommandExecutionError thrown when the ferry page rendered sailing rows (.list-item-parent cards exist — renderedCardCount > 0) but buildFerryExtractJs returned an empty array because its required sailing anchors (specific links/fields inside each row) matched nothing. The CLI deliberately distinguishes this from EmptyResultError: data is visibly on the page, so an empty parse means the extractor's selectors no longer match Ctrip's row markup. This is a parser/site-contract drift signal, not a data absence.
Solutions
- Inspect a rendered .list-item-parent row in browser DevTools and update the anchor selectors in buildFerryExtractJs (clis/ctrip/utils.js) to the current markup
- Upgrade opencli in case a newer release already tracks the new ship.ctrip.com markup
- Add a short settle delay or re-scroll before extraction so lazily-attached anchors exist when the extractor runs
- Verify the page region/variant matches what the extractor expects (mainland CN page vs other locale variants)
Example fix
// before (utils.js): extract immediately after scroll
const raw = await page.evaluate(buildFerryExtractJs());
// after: settle, then extract (also update row anchors in DevTools)
await page.evaluate(buildScrollUntilJs('.list-item-parent', limit));
await sleep(500);
const raw = await page.evaluate(buildFerryExtractJs()); Defensive patterns
Strategy: validation
Validate before calling
// Validate that rendered rows expose the anchors the parser needs, before extracting
const anchorsOk = await page.evaluate(() =>
document.querySelectorAll('.list-item-parent a').length > 0
);
if (!anchorsOk) console.error('Ferry row markup changed; update buildFerryExtractJs selectors'); Type guard
function isParserMissError(e) {
return e instanceof Error && e.name === 'CommandExecutionError' && /sailing anchors/.test(e.message);
} Try / catch
try {
return await run(['ctrip', 'ferry', '--from', f, '--to', t, '--date', d]);
} catch (e) {
if (isParserMissError(e)) {
console.error('Rows rendered but selectors missed — inspect .list-item-parent in DevTools and update buildFerryExtractJs');
throw e;
}
throw e;
} Prevention
- Add a weekly smoke test of the ferry command to catch selector drift early
- Allow a settle delay after scrolling so row anchors finish hydrating before extraction
- Keep opencli updated so extractor selectors track ship.ctrip.com redesigns
- Distinguish this error from EmptyResultError in scripts — this one is a parser bug, not missing data
When it happens
Trigger: Calling `ctrip ferry <from> <to> --date <d>` where scrolling confirmed non-zero .list-item-parent cards but the extraction script's required per-row anchor selectors matched zero elements — class renames, restructured row internals, or anchors attaching after extraction ran (late lazy hydration).
Common situations: Ctrip shipping a front-end redesign or A/B test of the sailing row; CLI version predating a markup change; regional page variants with different DOM; scroll-until resolving before inner anchors mount, then extracting too early.
Related errors
- Ctrip cruise cards rendered but parser did not find…
- Ctrip ferry DOM extraction returned malformed rows
- Ctrip round-trip flight cards rendered but parser did not…
- Ctrip cruise DOM extraction returned malformed rows
- Ctrip ferry page did not render sailing rows
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/7a8903891c1dae7c.
Report an issue: GitHub.
Appendix: source
Thrown at clis/ctrip/ferry.js:70
const limit = parseListLimit(kwargs.limit);
const searchUrl = buildFerryListUrl(fromCity, toCity, date);
await page.goto(searchUrl);
const waitResult = await page.evaluate(WAIT_FOR_FERRY_JS);
if (waitResult === 'captcha') {
throw new AuthRequiredError('ship.ctrip.com', 'Ctrip is asking for a captcha; complete it in your browser session and retry');
}
if (waitResult !== 'content') {
throw new CommandExecutionError(`Ctrip ferry page did not render sailing rows (state=${String(waitResult)})`);
}
const renderedCardCount = await page.evaluate(buildScrollUntilJs('.list-item-parent', limit));
const raw = await page.evaluate(buildFerryExtractJs());
if (!Array.isArray(raw)) {
throw new CommandExecutionError('Ctrip ferry DOM extraction returned malformed rows');
}
if (raw.length === 0) {
if (Number(renderedCardCount) > 0) {
throw new CommandExecutionError('Ctrip ferry rows rendered but parser did not find required sailing anchors');
}
throw new EmptyResultError('ctrip ferry', `No sailings for ${fromCity} to ${toCity} on ${date}`);
}
return raw.slice(0, limit).map((r, i) => ({
rank: i + 1,
shipName: r.shipName,
departureTime: r.departureTime,
fromPort: r.fromPort,
arrivalTime: r.arrivalTime,
toPort: r.toPort,
duration: r.duration,
price: r.price,
status: r.status,
url: searchUrl,
}));
},
});
View on GitHub (pinned to 49907e53dc)