{"record":{"id":"bcc257e03995e45a","repo":"jackwener/OpenCLI","slug":"ctrip-tour-dom-extraction-returned-malformed-rows","errorCode":null,"errorMessage":"Ctrip tour DOM extraction returned malformed rows","messagePattern":"Ctrip tour DOM extraction returned malformed rows","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/ctrip/tour.js","lineNumber":57,"sourceCode":"    func: async (page, kwargs) => {\n        const destination = parsePlaceName('destination', kwargs.destination);\n        const limit = parseListLimit(kwargs.limit);\n\n        const searchUrl = buildTourListUrl(destination);\n        await page.goto(searchUrl);\n        const waitResult = await page.evaluate(WAIT_FOR_VACATIONS_JS);\n        if (waitResult === 'captcha') {\n            throw new AuthRequiredError('vacations.ctrip.com', 'Ctrip is asking for a captcha; complete it in your browser session and retry');\n        }\n        if (waitResult === 'empty') {\n            throw new EmptyResultError('ctrip tour', `No tour packages for \"${destination}\"`);\n        }\n        if (waitResult !== 'content') {\n            throw new CommandExecutionError(`Ctrip tour page did not render package cards (state=${String(waitResult)})`);\n        }\n        const raw = await page.evaluate(buildVacationsExtractJs());\n        if (!Array.isArray(raw)) {\n            throw new CommandExecutionError('Ctrip tour DOM extraction returned malformed rows');\n        }\n        if (raw.length === 0) {\n            throw new CommandExecutionError('Ctrip tour cards rendered but parser did not find required package anchors');\n        }\n        return raw.slice(0, limit).map((r, i) => ({\n            rank: i + 1,\n            title: r.title,\n            subtitle: r.subtitle,\n            tags: r.tags,\n            score: r.score,\n            sold: r.sold,\n            reviews: r.reviews,\n            price: r.price,\n            url: searchUrl,\n        }));\n    },\n});\n","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/ctrip/tour.js#L39-L75","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Re-run once — in-page script errors can be transient","Update the CLI package so buildVacationsExtractJs matches the current page","Run the extract script manually in browser devtools on the tour search URL to inspect its return value","Check the page console for script errors during extraction","If maintaining the extractor, guarantee an array return (Array.from / defensive [])"],"exampleFix":"// before\nconst raw = await page.evaluate(buildVacationsExtractJs());\nif (!Array.isArray(raw)) throw new CommandExecutionError('...malformed rows');\n// after\nconst raw = await page.evaluate(buildVacationsExtractJs());\nconst rows = Array.isArray(raw) ? raw : [];\nif (!rows.length) throw new EmptyResultError('ctrip tour', 'No tour rows extracted');","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isRowArray(v) {\n  return Array.isArray(v) && v.every((r) => r && typeof r === 'object');\n}","tryCatchPattern":"try {\n  const tours = await runCli('ctrip', 'tour', dest);\n} catch (e) {\n  if (e.name === 'CommandExecutionError' && e.message.includes('malformed rows')) {\n    // re-run once or flag extractor/page mismatch\n  } else throw e;\n}","preventionTips":["Keep the extractor updated with Ctrip DOM changes","Re-run once before escalating — in-page errors can be transient","Watch for repeated failures indicating structural page changes","Test the extract script in devtools when errors cluster"],"tags":["browser-automation","dom-extraction","scraping","ctrip"],"backgroundTag":"dom-extraction-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}