{"record":{"id":"3e77530b6de6ce57","repo":"jackwener/OpenCLI","slug":"guazi-browse-code","errorCode":null,"errorMessage":"guazi browse ${code}","messagePattern":"guazi browse (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/guazi/browse.js","lineNumber":88,"sourceCode":"    site: 'guazi',\n    name: 'browse',\n    access: 'read',\n    aliases: ['list'],\n    description: '瓜子二手车在售车源列表（按城市，含售价/首付/里程/年份）',\n    strategy: Strategy.PUBLIC,\n    browser: false,\n    args: [\n        { name: 'city', positional: true, help: '城市名（北京/上海/...）或瓜子城市码（bj/sh/...）。默认 bj 北京' },\n        { name: 'limit', type: 'int', default: 20, help: '返回的车源数量（最多 40，单页 SSR 上限）' },\n    ],\n    columns: BROWSE_COLUMNS,\n    func: async (args) => {\n        const code = resolveCityCode(args.city);\n        const limit = requireLimit(args.limit, 20, 40);\n        const html = await guaziFetch(`/${code}/buy/`, `browse ${code}`);\n        const rows = parseListings(html, limit);\n        if (rows.length === 0) {\n            throw new CommandExecutionError(\n                `guazi browse ${code}`,\n                'No SSR listing anchors found on a successful Guazi mobile page; the mobile layout may have changed.',\n            );\n        }\n        return rows;\n    },\n});\n","sourceCodeStart":70,"sourceCodeEnd":96,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/guazi/browse.js#L70-L96","documentation":"The 'guazi browse' command fetched the Guazi mobile SSR listing page successfully but parseListings found zero listing anchor elements. The CLI deliberately throws CommandExecutionError instead of returning an empty result, because an empty page usually means the site's HTML layout changed (or the city page has no cars), not that there is genuinely nothing to show.","triggerScenarios":"Running 'guazi browse --city <code>' when Guazi's mobile page (/<code>/buy/) renders no <a> listing anchors — e.g. the site switched to client-side rendering, changed anchor markup/classes, or the city code resolves to a page with no inventory and an unexpectedly empty SSR body.","commonSituations":"Guazi front-end redesign or A/B test removing server-rendered anchors; scraping from a region where the mobile layout differs; city code pointing to a city with no used-car listings; bot-detection serving a JS-only shell page.","solutions":["Re-run with a different city (e.g. bj) to see if it is city-specific or site-wide.","Fetch https://m.guazi.com/<code>/buy/ in a browser and confirm whether listing anchors still appear in the server-rendered HTML.","Update the parseListings selectors in clis/guazi to match the new markup.","If the site is now client-rendered, switch to a headless browser or the underlying data API."],"exampleFix":"// before (assuming old selector)\nconst anchors = doc.querySelectorAll('a[href*=\"/car\"]');\n// after (match the new markup discovered by inspecting the page)\nconst anchors = doc.querySelectorAll('a.carlist-item, a[href*=\"/buy/detail\"]');","handlingStrategy":"fallback","validationCode":"const html = await fetch(`https://m.guazi.com/${code}/buy/`).then(r => r.text());\nif (!/<a[^>]+href=[\"']?[^\"']*car/i.test(html)) {\n  console.warn('No SSR anchors present — layout may have changed');\n}","typeGuard":"function hasListings(html) {\n  return typeof html === 'string' && /<a\\s[^>]*href=\"[^\"]*\\/(buy\\/)?detail/i.test(html);\n}","tryCatchPattern":"try {\n  const rows = await guaziBrowse({ city });\n} catch (e) {\n  if (/No SSR listing anchors/.test(e.message)) {\n    console.warn('Guazi layout may have changed; check m.guazi.com in a browser');\n    return [];\n  }\n  throw e;\n}","preventionTips":["Pin/monitor the HTML structure of the listing page with a periodic smoke test","Handle empty results gracefully instead of crashing pipelines","Try a second city to distinguish layout change from empty inventory","Keep selectors broad and add fallback selectors"],"tags":["scraping","empty-results","html-parsing","layout-change"],"backgroundTag":"scraper-selector-mismatch","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}