{"record":{"id":"478994235b225ddd","repo":"jackwener/OpenCLI","slug":"autohome-brand-catalog-returned-an-unexpected-html","errorCode":null,"errorMessage":"autohome brand catalog returned an unexpected HTML shape; expected brand <dl> blocks.","messagePattern":"autohome brand catalog returned an unexpected HTML shape; expected brand <dl> blocks\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/autohome/brand.js","lineNumber":35,"sourceCode":"    BRAND_COLUMNS,\n    CommandExecutionError,\n    EmptyResultError,\n    ahFetch,\n    clean,\n    requireLimit,\n    requireStableId,\n    requireText,\n    resolveBrandInitial,\n} from './utils.js';\n\n/**\n * Pure parser: catalog HTML + brand name → series rows. Exported for tests.\n */\nexport function parseBrandSeries(html, brandName, limit) {\n    const source = String(html || '');\n    const blocks = source.match(/<dl[^>]*>[\\s\\S]*?<\\/dl>/g);\n    if (!blocks) {\n        throw new CommandExecutionError('autohome brand catalog returned an unexpected HTML shape; expected brand <dl> blocks.');\n    }\n    const want = String(brandName || '').replace(/[·\\s]/g, '');\n\n    // No brand name (single-letter catalog mode): scan the whole page.\n    // Otherwise isolate the <dl> block whose <dt> names the brand.\n    let block = html;\n    if (want) {\n        block = null;\n        for (const b of blocks) {\n            const nameM = b.match(/<dt>[\\s\\S]*?<div>\\s*<a[^>]*>([^<]+)<\\/a>/);\n            const name = nameM ? clean(nameM[1]).replace(/[·\\s]/g, '') : '';\n            if (name && (name === want || name.startsWith(want) || want.startsWith(name))) {\n                block = b;\n                break;\n            }\n        }\n        if (!block) return [];\n    }","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/autohome/brand.js#L17-L53","documentation":"CommandExecutionError thrown by parseBrandSeries when the Autohome brand catalog HTML contains no <dl> blocks at all. Since the parser relies on <dl> blocks holding brand/series data, their absence means the fetched page is not the expected catalog (error page, anti-bot interstitial, or redesign).","triggerScenarios":"ahFetch returned HTML for /grade/carhtml/<letter>.html without any <dl> tags — e.g. an anti-bot/captcha page, a 404/soft-404 page, or Autohome changing the catalog markup.","commonSituations":"Autohome serving a verification page to datacenter IPs or after rapid requests; site redesign removing <dl> blocks; the letter page URL no longer existing.","solutions":["Retry later — the page shape may be temporarily altered by anti-bot protection.","Slow request rate or change network/IP to avoid the anti-bot interstitial.","Check the actual HTML returned (curl the URL) to confirm whether Autohome changed markup, then update the parser regex.","Verify the catalog letter URL (e.g. /grade/carhtml/A.html) is still valid in a browser."],"exampleFix":"// before\nconst rows = parseBrandSeries(errorPageHtml, '宝马', 10); // throws\n// after: check response sanity before parsing\nconst html = await ahFetch(url, 'brand A');\nif (!/<dl[\\s\\S]*<\\/dl>/.test(html)) throw new Error('unexpected autohome response, got: ' + html.slice(0, 200));\nconst rows = parseBrandSeries(html, '宝马', 10);","handlingStrategy":"try-catch","validationCode":"// sanity-check HTML before parsing\nif (!/<dl[\\s\\S]*<\\/dl>/.test(html)) throw new Error('autohome page did not contain brand <dl> blocks');","typeGuard":"function hasBrandBlocks(html) {\n  return typeof html === 'string' && /<dl[^>]*>[\\s\\S]*?<\\/dl>/.test(html);\n}","tryCatchPattern":"try {\n  const rows = await brand(brandArg, limit);\n} catch (e) {\n  if (/unexpected HTML shape/.test(e.message)) {\n    // anti-bot page or markup change; back off and retry, or surface a clear failure\n    await sleep(5000);\n    return retryOnce();\n  }\n  throw e;\n}","preventionTips":["Throttle requests to autohome and reuse sessions to reduce anti-bot pages.","Detect captcha/verification markers in fetched HTML before parsing.","Pin tests against a saved snapshot of real catalog HTML to catch markup changes.","Fail loudly with a snippet of the unexpected HTML for diagnosis."],"tags":["scraping","html-parsing","upstream-change"],"backgroundTag":"unexpected-page-structure","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}