{"record":{"id":"53fb985042718562","repo":"jackwener/OpenCLI","slug":"guazi-contexthint-http-resp-status","errorCode":null,"errorMessage":"guazi ${contextHint} HTTP ${resp.status}","messagePattern":"guazi (.+?) HTTP (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/guazi/utils.js","lineNumber":120,"sourceCode":"    return id;\n}\n\n/** Fetch a Guazi mobile page as HTML text, throwing typed errors. */\nexport async function guaziFetch(path, contextHint) {\n    let resp;\n    try {\n        resp = await fetch(`${GUAZI_M_BASE}${path}`, {\n            headers: {\n                'User-Agent': UA,\n                Referer: `${GUAZI_M_BASE}/`,\n                'Accept-Language': 'zh-CN,zh;q=0.9',\n            },\n        });\n    } catch (err) {\n        throw new CommandExecutionError(`guazi ${contextHint} network error: ${err?.message || err}`);\n    }\n    if (!resp.ok) {\n        throw new CommandExecutionError(`guazi ${contextHint} HTTP ${resp.status}`);\n    }\n    const html = await resp.text();\n    // Guazi may eventually push the mobile pages behind their JS challenge.\n    if (/瑞数|reese84|captcha|滑动验证|verify\\.guazi|安全验证/i.test(html) && !/car-detail\\/c\\d+/.test(html)) {\n        throw new AuthRequiredError(\n            'guazi.com',\n            `guazi ${contextHint} hit an anti-bot challenge — Guazi may have started gating the mobile site.`,\n        );\n    }\n    return html;\n}\n\nexport { ArgumentError, CommandExecutionError, EmptyResultError };\n","sourceCodeStart":102,"sourceCodeEnd":134,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/guazi/utils.js#L102-L134","documentation":"guaziFetch checks resp.ok after the request succeeds at the transport level and throws CommandExecutionError for any non-OK HTTP status (404, 403, 500, rate-limit 429, etc.), embedding the status code and contextHint. This distinguishes server-side rejections from network failures.","triggerScenarios":"guaziFetch(path, contextHint) receiving a response with resp.ok === false: a deleted/invalid car listing (404), IP rate-limiting or WAF block (403/429), or Guazi server errors (5xx).","commonSituations":"Scraping a car id that no longer exists; hitting Guazi too frequently from one IP and getting rate-limited; the mobile endpoint path changed in a site redesign.","solutions":["Log resp.status from the error message and check it: 404 means bad id/path, 403/429 means blocked","Add delays/backoff between requests to avoid rate-limiting","Verify the car id or path is correct by opening it in a browser","Rotate IP/proxy if consistently 403/429"],"exampleFix":"// before\nawait guaziFetch('/car/99999999999', 'car page'); // 404\n// guazi car page HTTP 404\n\n// after\nconst listing = await findLiveCarId(query); // resolve a valid id first\nawait guaziFetch(`/car/${listing.id}`, 'car page');","handlingStrategy":"retry","validationCode":"// verify the target page exists before scraping\nconst HEAD = await fetch(`${GUAZI_M_BASE}${path}`, { method: 'HEAD' });\nif (HEAD.status === 404) throw new Error('listing not found');","typeGuard":"const isHttpError = (e) => e instanceof CommandExecutionError && /HTTP \\d+/.test(e.message);","tryCatchPattern":"try {\n  const html = await guaziFetch(path, hint);\n} catch (e) {\n  const m = e.message.match(/HTTP (\\d+)/);\n  if (m && ['429','500','502','503'].includes(m[1])) return retryWithBackoff(() => guaziFetch(path, hint), 3);\n  throw e;\n}","preventionTips":["Throttle requests with delays to avoid 403/429 rate limits","Treat 404 as a permanent failure — do not retry","Rotate proxies/IPs if you see repeated 403s","Monitor Guazi status; 5xx bursts usually indicate server-side incidents"],"tags":["http","scraping","status-code"],"backgroundTag":"http-non-2xx-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}