{"record":{"id":"26f3855572dedf4d","repo":"jackwener/OpenCLI","slug":"ctrip-place-page-did-not-render-attraction-links-f","errorCode":null,"errorMessage":"Ctrip place page did not render attraction links for city id ${cityId} (state=${String(waitResult)}); check the city id","messagePattern":"Ctrip place page did not render attraction links for city id (.+?) \\(state=(.+?)\\); check the city id","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/ctrip/attraction.js","lineNumber":56,"sourceCode":"    ],\n    columns: [\n        'rank',\n        'name',\n        'rating', 'reviews',\n        'url',\n    ],\n    func: async (page, kwargs) => {\n        const cityId = parseCityId(kwargs.city);\n        const limit = parseListLimit(kwargs.limit);\n\n        const placeUrl = buildAttractionPlaceUrl(cityId);\n        await page.goto(placeUrl);\n        const waitResult = await page.evaluate(buildWaitForAttractionsJs(cityId));\n        if (waitResult === 'captcha') {\n            throw new AuthRequiredError('you.ctrip.com', 'Ctrip is asking for a captcha; complete it in your browser session and retry');\n        }\n        if (waitResult !== 'content') {\n            throw new CommandExecutionError(`Ctrip place page did not render attraction links for city id ${cityId} (state=${String(waitResult)}); check the city id`);\n        }\n        const raw = await page.evaluate(buildAttractionExtractJs(cityId));\n        if (!Array.isArray(raw)) {\n            throw new CommandExecutionError('Ctrip attraction DOM extraction returned malformed rows');\n        }\n        if (raw.length === 0) {\n            throw new CommandExecutionError('Ctrip attraction links rendered but parser did not find required sight anchors');\n        }\n        return raw.slice(0, limit).map((r, i) => ({\n            rank: i + 1,\n            name: r.name,\n            rating: r.rating,\n            reviews: r.reviews,\n            url: r.url,\n        }));\n    },\n});\n","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/ctrip/attraction.js#L38-L74","documentation":"After the attraction page loads, the wait script must return 'content' (attraction links rendered). Any other state ('captcha' handled separately) means the place page never rendered the `/sight/<city><cityId>/...` links, so the command throws this CommandExecutionError and points at the city id, since by design a valid city always lists attractions.","triggerScenarios":"`opencli ctrip attraction <cityId>` with a stale, invalid, or unrenderable city id — the page loads but the DOM never contains city-scoped attraction links within the wait window.","commonSituations":"Hand-typed city id instead of one from `ctrip search`; a city whose you.ctrip page layout changed; slow page load exceeding the wait timeout; page redirected to an error/landing page.","solutions":["Re-derive the correct numeric city id with `opencli ctrip search <city>` (e.g. 1 for 北京) and retry with that id.","Verify the id corresponds to a real you.ctrip place page by opening buildAttractionPlaceUrl's URL in a browser.","Retry later or on a better network if the page simply timed out before rendering.","If many valid ids fail, the site layout likely changed — update the CLI adapter."],"exampleFix":"// before\nawait opencli.ctrip.attraction('beijing'); // non-numeric/invalid id\n// after\nconst city = await opencli.ctrip.search('北京');\nawait opencli.ctrip.attraction(city[0].id);","handlingStrategy":"validation","validationCode":"// only pass numeric city ids obtained from `ctrip search`\nconst results = await opencli.ctrip.search(cityName);\nif (!results.length) throw new Error(`no ctrip city found for ${cityName}`);\nconst cityId = results[0].id;\nif (!/^\\d+$/.test(String(cityId))) throw new Error(`unexpected city id: ${cityId}`);","typeGuard":"const isValidCityId = (id) => typeof id === 'number' ? Number.isInteger(id) && id > 0 : /^\\d+$/.test(String(id));","tryCatchPattern":"try {\n  const rows = await opencli.ctrip.attraction(cityId);\n} catch (err) {\n  if (err.message.includes('check the city id')) {\n    // re-resolve the id via ctrip search before retrying\n  }\n  throw err;\n}","preventionTips":["Always source city ids from `ctrip search`, never from memory or free text.","Sanity-check the id opens a real you.ctrip place page in a browser.","Retry once on slow networks — the failure can be a render timeout.","Keep the adapter updated if Ctrip changes its place-page layout."],"tags":["scraping","dom-render","invalid-input","ctrip","timeout"],"backgroundTag":"invalid-city-id","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}