jackwener/OpenCLI · error · CommandExecutionError

响应不是有效 JSON

Error message

响应不是有效 JSON

What it means

fetchXueqiuJson tries res.json() inside the browser context; when the body cannot be parsed as JSON it returns {__xqErr:'parse'} and the wrapper throws CommandExecutionError('响应不是有效 JSON'). The library throws this because a non-JSON body almost always means xueqiu served an anti-bot/risk-control HTML page instead of the API payload, so the result would be unusable garbage if passed through.

Source

Thrown at clis/xueqiu/utils.js:53

 */
export async function fetchXueqiuJson(page, url) {
    const result = await page.evaluate(`(async () => {
    const res = await fetch(${JSON.stringify(url)}, { credentials: 'include' });
    if (!res.ok) return { __xqErr: res.status };
    try {
      return await res.json();
    } catch {
      return { __xqErr: 'parse' };
    }
  })()`);
    const r = result;
    if (r?.__xqErr !== undefined) {
        const code = r.__xqErr;
        if (code === 401 || code === 403) {
            throw new AuthRequiredError('xueqiu.com', '未登录或登录已过期');
        }
        if (code === 'parse') {
            throw new CommandExecutionError('响应不是有效 JSON', '可能触发了风控,请检查登录状态或稍后重试');
        }
        throw new CommandExecutionError(`HTTP ${code}`, '请检查网络连接或登录状态');
    }
    return result;
}

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Check login status in the browser and re-authenticate if needed
  2. Wait and retry later — risk control blocks are usually temporary
  3. Reduce request rate / add delays between xueqiu calls
  4. Verify the URL is a valid xueqiu JSON API endpoint

Example fix

// before (tight loop triggers risk control)
for (const pid of pids) await fetchXueqiuJson(page, url(pid));
// after (throttle + retry on parse failure)
try {
  const d = await fetchXueqiuJson(page, url(pid));
} catch (e) {
  if (String(e.message).includes('JSON')) await sleep(5000);
}
Defensive patterns

Strategy: retry

Validate before calling

const html = await page.content();
if (html.includes('verify') || html.includes('captcha')) await sleep(10000); // risk-control page detected

Type guard

function isJsonLike(v) { return v != null && typeof v === 'object' && v.__xqErr === undefined; }

Try / catch

try {
  const d = await fetchXueqiuJson(page, url);
} catch (e) {
  if (String(e.message).includes('响应不是有效 JSON')) {
    await sleep(5000); // wait out risk control, then retry once
    return fetchXueqiuJson(page, url);
  }
  throw e;
}

Prevention

When it happens

Trigger: The fetch to a stock.xueqiu.com v5 endpoint returns 200 but with an HTML body (risk-control challenge, login interstitial, or WAF block page) so res.json() throws inside page.evaluate.

Common situations: Aggressive polling triggers xueqiu's anti-crawler; logged-out traffic redirected to an HTML page; CDN/WAF returns an error page with 200; network middleware injects HTML.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/efb7ed180cace3d2. Report an issue: GitHub.