{"record":{"id":"166b5d03a50f7c55","repo":"jackwener/OpenCLI","slug":"dianping-search-parser-found-result-cards-without","errorCode":null,"errorMessage":"dianping search parser found result cards without shop_id values","messagePattern":"dianping search parser found result cards without shop_id values","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/dianping/search.js","lineNumber":143,"sourceCode":"        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":125,"sourceCodeEnd":158,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/dianping/search.js#L125-L158","documentation":"The dianping search parser extracted rows, but at least one extracted card is missing a shop_id, which is the required key for every result row. This indicates a partial or malformed extraction — selectors matched extra junk elements (ads, recommendation blocks, non-shop cards) or the shop_id attribute moved in dianping's markup. The library validates every row before returning to guarantee a consistent schema.","triggerScenarios":"Any search call where result.rows contains an entry without a truthy shop_id — e.g. dianping inserted ad/promo cards without ids into results, a selector now captures header/footer items, or the data attribute holding the shop id was renamed.","commonSituations":"Site A/B tests adding ad cards to result lists; scraping pages where sponsored listings appear first; stale adapter code after a dianping HTML update.","solutions":["Update the CLI/adapters package to the latest version — this usually means dianping changed markup and selectors were fixed upstream.","Save the raw HTML and check whether the offending card is an ad/promo block; adjust or filter selectors to exclude non-shop cards.","Re-run with a narrower keyword that returns plain organic results to confirm it is markup-related rather than query-related.","As a workaround, filter out rows lacking shop_id before the adapter's validation by using a lower-level parse function if exposed."],"exampleFix":"// before\nconst rows = await search({ keyword: 'coffee', city: 1 }); // throws on ad card without shop_id\n// after\ntry {\n  const rows = await search({ keyword: 'coffee', city: 1 });\n} catch (e) {\n  if (String(e.message).includes('without shop_id values')) {\n    console.error('dianping markup changed: ad/promo cards missing shop_id in results');\n  } else { throw e; }\n}","handlingStrategy":"validation","validationCode":"function looksLikeShopRow(r) {\n  return r != null && typeof r.shop_id === 'string' && r.shop_id.length > 0;\n}","typeGuard":"function hasShopId(row) {\n  return typeof row === 'object' && row !== null &&\n    'shop_id' in row && typeof row.shop_id === 'string' && row.shop_id !== '';\n}","tryCatchPattern":"try {\n  const rows = await search({ keyword, city });\n} catch (e) {\n  if (/without shop_id values/.test(String(e.message))) {\n    console.error('dianping returned non-shop cards (ads?) — update adapters or filter selector output');\n  } else throw e;\n}","preventionTips":["Keep the parser/selectors up to date; ad-card insertion is a frequent dianping change.","Sanity-check one known shop query in CI to catch markup drift early.","When consuming lower-level parse output, filter rows with hasShopId before use.","Narrow keywords reduce ad/promo card injection into results."],"tags":["scraping","parser","schema-mismatch","dom-change"],"backgroundTag":"parser-schema-mismatch","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}