{"record":{"id":"4534fbf13578844f","repo":"jackwener/OpenCLI","slug":"dianping-search-parser-found-no-result-shaped-shop","errorCode":null,"errorMessage":"dianping search parser found no result-shaped shop cards","messagePattern":"dianping search parser found no result-shaped shop cards","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/dianping/search.js","lineNumber":140,"sourceCode":"            await page.wait(2);\n        });\n\n        const result = await wrapDianpingStep(\n            `search \"${keyword}\" extraction`,\n            () => page.evaluate(`(${extractSearchRows.toString()})()`),\n        );\n\n        if (!result || !result.ok) {\n            detectAuthOrPageFailure(\n                { text: String(result?.sample || ''), url: String(result?.url || url) },\n                `search \"${keyword}\"`,\n                { emptyPatterns: [/没有找到|暂无结果|暂无商户|换个关键词|未找到相关/i] },\n            );\n        }\n\n        const rows = (result.rows || []).slice(0, limit);\n        if (rows.length === 0) {\n            throw new CommandExecutionError('dianping search parser found no result-shaped shop cards');\n        }\n        if (rows.some((row) => !row?.shop_id)) {\n            throw new CommandExecutionError('dianping search parser found result cards without shop_id values');\n        }\n        return rows.map((r) => ({\n            rank: r.rank,\n            shop_id: r.shop_id,\n            name: r.name,\n            rating: r.starClass ? Number((Number(r.starClass) / 10).toFixed(1)) : null,\n            reviews: parseReviewCount(r.reviewsRaw),\n            price: parsePrice(r.priceRaw),\n            cuisine: r.cuisine,\n            district: r.district,\n            url: r.url,\n        }));\n    },\n});\n","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/dianping/search.js#L122-L158","documentation":"The dianping search adapter fetched a results page but its extraction produced zero rows shaped like shop cards, even though the page did not match any of the known 'no results' empty-state patterns (没有找到/暂无结果 etc.). This means dianping changed its markup, the page was an anti-bot/captcha wall, or the HTML was truncated, so the parser found nothing it recognized. The library throws instead of returning an empty list so callers do not silently mistake a parsing failure for a legitimate empty result.","triggerScenarios":"Calling the dianping search command where the fetched HTML contains no extractable shop cards: dianping redesigned the search results DOM, the request hit a verification/anti-bot page, network middleware stripped or truncated the HTML, or the keyword matches nothing but renders an empty state not covered by emptyPatterns.","commonSituations":"Dianping front-end redesign breaking CSS selectors; scraping from a datacenter IP triggering a captcha page; a query that yields zero shops while the empty-state text changed; running an outdated version of this CLI after the site changed.","solutions":["Re-run the same query with a different, common keyword (e.g. 火锅) to check whether parsing fails for all queries or only this one.","Print/save the raw HTML for the failing request and inspect whether it is a captcha/verification page or a markup change.","Update the CLI/adapters package to the latest version in case selectors were fixed for a dianping redesign.","Retry from a residential IP or after solving any verification page; add cookies from a logged-in browser session.","If it is a genuine empty result set, treat it as 'no results found' — refine the keyword or city rather than retrying the same query."],"exampleFix":"// before\ncli.search({ keyword: 'myquery', city: 'shanghai' }); // throws CommandExecutionError\n// after\ntry {\n  const rows = cli.search({ keyword: 'myquery', city: 'shanghai' });\n} catch (e) {\n  if (String(e.message).includes('no result-shaped shop cards')) {\n    console.error('dianping markup changed or no results; inspect raw HTML before retrying');\n  } else { throw e; }\n}","handlingStrategy":"try-catch","validationCode":"// pre-check: use a common keyword and a known-good city\nif (!keyword || !keyword.trim()) throw new Error('keyword required');\n// note: cannot pre-validate server markup; detect failure via error message","typeGuard":"function isParserEmptyError(e) {\n  return e instanceof Error && /no result-shaped shop cards/.test(e.message);\n}","tryCatchPattern":"try {\n  const rows = await search({ keyword, city });\n} catch (e) {\n  if (isParserEmptyError(e)) {\n    // treat as possible markup change or true empty set; log keyword+city, try alternate keyword\n  } else throw e;\n}","preventionTips":["Pin and regularly update the adapters package — dianping markup changes break parsers.","Probe with a high-frequency keyword (火锅/咖啡) before concluding a query truly has no results.","Log raw HTML on failure to distinguish captcha walls from DOM changes.","Scrape from residential IPs to avoid anti-bot pages that render without shop cards."],"tags":["scraping","parser","dom-change","empty-result"],"backgroundTag":"parser-found-no-results","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}