{"record":{"id":"8e9aaa8a84e6a2b9","repo":"jackwener/OpenCLI","slug":"http-error-8e9aaa","errorCode":"HTTP_ERROR","errorMessage":"`eastmoney quote failed: HTTP ${resp.status}`","messagePattern":"`eastmoney quote failed: HTTP (.+?)`","errorType":"error_code","errorClass":"CliError","httpStatus":null,"severity":"error","filePath":"clis/eastmoney/quote.js","lineNumber":82,"sourceCode":"      throw new CliError('INVALID_ARGUMENT', 'At least one symbol is required');\n    }\n\n    /** @type {string[]} */\n    const secids = [];\n    for (const s of raw) {\n      try { secids.push(resolveSecid(s)); }\n      catch (err) { throw new CliError('INVALID_ARGUMENT', `Unrecognized symbol \"${s}\"`); }\n    }\n\n    // Multi-stock in one call via ulist.np\n    const url = new URL('https://push2.eastmoney.com/api/qt/ulist.np/get');\n    url.searchParams.set('secids', secids.join(','));\n    url.searchParams.set('fltt', '2');\n    url.searchParams.set('fields', FIELDS);\n    url.searchParams.set('ut', 'bd1d9ddb04089700cf9c27f6f7426281');\n\n    const resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0', Accept: 'application/json' } });\n    if (!resp.ok) throw new CliError('HTTP_ERROR', `eastmoney quote failed: HTTP ${resp.status}`);\n    const data = await resp.json();\n    const diff = Array.isArray(data?.data?.diff) ? data.data.diff : [];\n    if (diff.length === 0) throw new CliError('NO_DATA', 'eastmoney returned no quotes', `Check symbols: ${raw.join(', ')}`);\n\n    return diff.map((it) => ({\n      code: it.f12,\n      name: it.f14,\n      market: marketLabel(it.f13),\n      price: it.f2,\n      changePercent: it.f3,\n      change: it.f4,\n      open: it.f17,\n      high: it.f15,\n      low: it.f16,\n      prevClose: it.f18,\n      volume: it.f5,\n      turnover: it.f6,\n      turnoverRate: it.f8,","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/eastmoney/quote.js#L64-L100","documentation":"After building the ulist.np URL with the resolved secids, quote.js checks resp.ok and throws CliError HTTP_ERROR `eastmoney quote failed: HTTP ${status}` on any non-2xx response from push2.eastmoney.com. It surfaces the upstream status so the caller can distinguish auth/bot-blocking (403), bad parameters (4xx), and server faults (5xx).","triggerScenarios":"Any non-2xx from the quote endpoint: 403 from eastmoney anti-scraping/rate limiting (very common when polling frequently or from datacenter IPs), 400 if the secids parameter is malformed, 5xx during eastmoney outages or heavy load.","commonSituations":"High-frequency quote polling triggering rate limits; corporate proxies or firewalls intercepting the request; eastmoney rotating or invalidating the hardcoded ut token; temporary eastmoney server errors during volatile market sessions.","solutions":["Wait and retry — many 403/5xx are transient or rate-limit related","Read the status code: 403 → back off and reduce request rate; 5xx → eastmoney-side","Add exponential backoff/retry with jitter around fetch","Verify direct access: curl the same URL and compare status","If 403 persists, rotate IP/User-Agent or check for eastmoney API changes (path, fields, ut token)"],"exampleFix":"// before\nconst resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0', Accept: 'application/json' } });\nif (!resp.ok) throw new CliError('HTTP_ERROR', `eastmoney quote failed: HTTP ${resp.status}`);\n// after\nconst resp = await fetchWithBackoff(url, { headers: { 'User-Agent': 'Mozilla/5.0', Accept: 'application/json' } });\nif (!resp.ok) throw new CliError('HTTP_ERROR', `eastmoney quote failed: HTTP ${resp.status}`, `Status ${resp.status}; retry later or reduce polling rate`);","handlingStrategy":"retry","validationCode":"// Preflight: cheap single-symbol probe before bulk calls\nconst probe = await fetch('https://push2.eastmoney.com/api/qt/ulist.np/get?secids=1.600000&fltt=2&fields=f12&ut=bd1d9ddb04089700cf9c27f6f7426281', { headers: { 'User-Agent': 'Mozilla/5.0', Accept: 'application/json' } });\nif (!probe.ok) throw new Error(`eastmoney API unreachable: HTTP ${probe.status}`);","typeGuard":"null","tryCatchPattern":"try {\n  await runQuote(args);\n} catch (e) {\n  if (e.code === 'HTTP_ERROR') {\n    const status = Number(e.message.match(/HTTP (\\d+)/)?.[1]);\n    if (status === 429 || status >= 500) { await sleep(backoff()); return runQuote(args); }\n    if (status === 403) console.error('Blocked by eastmoney — reduce polling rate or change network/UA.');\n  } else throw e;\n}","preventionTips":["Throttle quote polling (e.g. ≥1 request/second, backoff on 403/429)","Use a realistic browser User-Agent and stable egress IP","Alert on persistent 403/404 — signals anti-bot escalation or API/token change","Add retries with exponential backoff + jitter for 5xx","Cache quotes briefly instead of re-fetching per display refresh"],"tags":["network","http","upstream-api","eastmoney"],"backgroundTag":"http-error-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}