jackwener/OpenCLI · warning · EmptyResultError

No trains for ${fromName} to ${toName} on ${date}

Error message

No trains for ${fromName} to ${toName} on ${date}

What it means

Thrown as EmptyResultError when the Ctrip train page rendered fine but zero train rows were extracted and no cards were rendered — i.e. Ctrip genuinely has no trains for the requested route/date. This is a normal empty result, not a failure of the scraper.

Source

Thrown at clis/ctrip/train.js:71

        const searchUrl = buildTrainListUrl(fromName, toName, date);
        await page.goto(searchUrl);
        const waitResult = await page.evaluate(WAIT_FOR_TRAINS_JS);
        if (waitResult === 'captcha') {
            throw new AuthRequiredError('trains.ctrip.com', 'Ctrip is asking for a captcha; complete it in your browser session and retry');
        }
        if (waitResult !== 'content') {
            throw new CommandExecutionError(`Ctrip train page did not render train cards (state=${String(waitResult)})`);
        }
        const renderedCardCount = await page.evaluate(buildScrollUntilJs('.card-white.list-item', limit));
        const raw = await page.evaluate(buildTrainExtractJs());
        if (!Array.isArray(raw)) {
            throw new CommandExecutionError('Ctrip train DOM extraction returned malformed rows');
        }
        if (raw.length === 0) {
            if (Number(renderedCardCount) > 0) {
                throw new CommandExecutionError('Ctrip train cards rendered but parser did not find required train anchors');
            }
            throw new EmptyResultError('ctrip train', `No trains for ${fromName} to ${toName} on ${date}`);
        }
        return raw.slice(0, limit).map((r, i) => ({
            rank: i + 1,
            trainNo: r.trainNo,
            departureTime: r.departureTime,
            departureStation: r.departureStation,
            arrivalTime: r.arrivalTime,
            arrivalStation: r.arrivalStation,
            duration: r.duration,
            fromPrice: r.fromPrice,
            seats: Array.isArray(r.seats) && r.seats.length ? r.seats.join(' / ') : null,
            url: searchUrl,
        }));
    },
});

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Confirm the route actually has train service and try a nearby major station pair.
  2. Query a date within the booking window (usually the next ~15 days).
  3. Handle EmptyResultError as an expected outcome in your script and continue.
  4. Verify the resolved fromName/toName are the intended stations.

Example fix

// before
const trains = await runTrainList({ from, to, date });
console.log(trains[0]);
// after
try { const trains = await runTrainList({ from, to, date }); if (!trains.length) console.log('no trains'); }
catch (e) { if (e instanceof EmptyResultError) console.log('no trains for route/date'); else throw e; }
Defensive patterns

Strategy: fallback

Validate before calling

if (new Date(opts.date) > salesWindowEnd()) console.warn('date likely beyond booking window');

Type guard

const isBookableDate = (d) => { const t = new Date(d).getTime(); return t >= Date.now() && t <= Date.now() + 15*864e5; };

Try / catch

try { return await runTrainList(opts); } catch (e) { if (e instanceof EmptyResultError) return []; throw e; }

Prevention

When it happens

Trigger: raw.length === 0 with Number(renderedCardCount) === 0 for the given fromName/toName/date, e.g. an unbookable or future-date-not-on-sale route, or a route with no direct trains.

Common situations: Querying a date beyond Ctrip's sales window (typically ~15 days); minor station pairs with no service; holidays where schedules changed; misspelled station resolving to a real but unserved place.

Understand the failure class

Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/7e88ac0534b772cc. Report an issue: GitHub.