jackwener/OpenCLI · error · CommandExecutionError

Ctrip hotel detail SSR extraction returned malformed data

Error message

Ctrip hotel detail SSR extraction returned malformed data

What it means

CommandExecutionError thrown when the SSR extraction step returns null or a non-object even though the page reached the 'content' state. The wait script said content was ready, but buildHotelDetailExtractJs() failed to assemble a detail object, indicating the DOM nodes it reads exist but its data assembly broke.

Source

Thrown at clis/ctrip/hotel.js:49

        'star', 'score', 'scoreLabel', 'reviewCount', 'ratingBreakdown',
        'facilities', 'checkInOut',
        'cityName', 'address', 'lat', 'lon',
        'url',
    ],
    func: async (page, kwargs) => {
        const hotelId = parseHotelId(kwargs.id);
        const url = buildHotelDetailUrl(hotelId);
        await page.goto(url);
        const waitResult = await page.evaluate(WAIT_FOR_HOTEL_DETAIL_JS);
        if (waitResult === 'captcha') {
            throw new AuthRequiredError('hotels.ctrip.com', 'Ctrip is asking for a captcha; complete it in your browser session and retry');
        }
        if (waitResult !== 'content') {
            throw new CommandExecutionError(`Ctrip hotel detail page did not expose SSR hotel data (state=${String(waitResult)})`);
        }
        const detail = await page.evaluate(buildHotelDetailExtractJs());
        if (!detail || typeof detail !== 'object') {
            throw new CommandExecutionError('Ctrip hotel detail SSR extraction returned malformed data');
        }
        if (!detail.hotelId || !detail.name) {
            throw new EmptyResultError('ctrip hotel', `No detail exposed for hotel id ${hotelId}`);
        }
        return [{ ...detail, url }];
    },
});

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Add a short delay or wait for the detail data node before running buildHotelDetailExtractJs()
  2. Log the raw extraction output and update buildHotelDetailExtractJs to current markup
  3. Update the CLI to the latest version
  4. Wrap extraction in try/catch inside the page and return diagnostics

Example fix

// before
const detail = await page.evaluate(buildHotelDetailExtractJs());
if (!detail || typeof detail !== 'object') throw new CommandExecutionError('...malformed data');
// after
await page.waitForSelector(DETAIL_DATA_SELECTOR, { timeout: 10000 });
const detail = await page.evaluate(buildHotelDetailExtractJs());
if (!detail || typeof detail !== 'object') throw new CommandExecutionError('...malformed data');
Defensive patterns

Strategy: try-catch

Validate before calling

await page.waitForSelector(DETAIL_DATA_SELECTOR, { timeout: 10000 }); // ensure data node exists before extracting
const detail = await page.evaluate(buildHotelDetailExtractJs());
if (!detail || typeof detail !== 'object') throw new Error('detail extraction malformed');

Type guard

const isDetailObject = (v) => !!v && typeof v === 'object' && !Array.isArray(v);

Try / catch

try {
  return await ctrip.hotel({ id });
} catch (e) {
  if (e instanceof CommandExecutionError && /malformed data/.test(e.message)) {
    // hydration race: wait longer and retry once, else escalate as markup drift
    await sleep(3000);
    return await ctrip.hotel({ id });
  }
  throw e;
}

Prevention

When it happens

Trigger: buildHotelDetailExtractJs() evaluates to null/undefined/a primitive: the detail container renders but inner data blocks it reads are absent (partial render, hydration race), or the script throws internally and page.evaluate surfaces null.

Common situations: Ctrip hydrating detail data client-side after the extract runs; regional page variants missing fields the extract script reads; Ctrip markup update after the wait selector still matched; corrupted/partial page load on flaky networks.

Understand the failure class

Related errors


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