jackwener/OpenCLI · error · CommandExecutionError

Ctrip tour DOM extraction returned malformed rows

Error message

Ctrip tour DOM extraction returned malformed rows

What it means

A CommandExecutionError raised by `ctrip tour` when buildVacationsExtractJs() executed in the page returned a non-array value. The extractor is expected to always return an array of raw tour rows; a non-array indicates the in-page script errored (evaluate resolves to undefined) or its return was corrupted, rather than the page simply having no results.

Source

Thrown at clis/ctrip/tour.js:57

    func: async (page, kwargs) => {
        const destination = parsePlaceName('destination', kwargs.destination);
        const limit = parseListLimit(kwargs.limit);

        const searchUrl = buildTourListUrl(destination);
        await page.goto(searchUrl);
        const waitResult = await page.evaluate(WAIT_FOR_VACATIONS_JS);
        if (waitResult === 'captcha') {
            throw new AuthRequiredError('vacations.ctrip.com', 'Ctrip is asking for a captcha; complete it in your browser session and retry');
        }
        if (waitResult === 'empty') {
            throw new EmptyResultError('ctrip tour', `No tour packages for "${destination}"`);
        }
        if (waitResult !== 'content') {
            throw new CommandExecutionError(`Ctrip tour page did not render package cards (state=${String(waitResult)})`);
        }
        const raw = await page.evaluate(buildVacationsExtractJs());
        if (!Array.isArray(raw)) {
            throw new CommandExecutionError('Ctrip tour DOM extraction returned malformed rows');
        }
        if (raw.length === 0) {
            throw new CommandExecutionError('Ctrip tour cards rendered but parser did not find required package anchors');
        }
        return raw.slice(0, limit).map((r, i) => ({
            rank: i + 1,
            title: r.title,
            subtitle: r.subtitle,
            tags: r.tags,
            score: r.score,
            sold: r.sold,
            reviews: r.reviews,
            price: r.price,
            url: searchUrl,
        }));
    },
});

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Re-run once — in-page script errors can be transient
  2. Update the CLI package so buildVacationsExtractJs matches the current page
  3. Run the extract script manually in browser devtools on the tour search URL to inspect its return value
  4. Check the page console for script errors during extraction
  5. If maintaining the extractor, guarantee an array return (Array.from / defensive [])

Example fix

// before
const raw = await page.evaluate(buildVacationsExtractJs());
if (!Array.isArray(raw)) throw new CommandExecutionError('...malformed rows');
// after
const raw = await page.evaluate(buildVacationsExtractJs());
const rows = Array.isArray(raw) ? raw : [];
if (!rows.length) throw new EmptyResultError('ctrip tour', 'No tour rows extracted');
Defensive patterns

Strategy: try-catch

Type guard

function isRowArray(v) {
  return Array.isArray(v) && v.every((r) => r && typeof r === 'object');
}

Try / catch

try {
  const tours = await runCli('ctrip', 'tour', dest);
} catch (e) {
  if (e.name === 'CommandExecutionError' && e.message.includes('malformed rows')) {
    // re-run once or flag extractor/page mismatch
  } else throw e;
}

Prevention

When it happens

Trigger: Running `opencli ctrip tour <destination>` where the page rendered but the extraction script threw inside the browser context, or returned null/undefined/an object instead of an array of row objects.

Common situations: Ctrip page JS conflicting with extractor globals; DOM changes making the collector return null; stale CLI version with an outdated extractor; serialization failures on non-serializable values.

Understand the failure class

Related errors


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