jackwener/OpenCLI · error · CommandExecutionError

响应不是有效 JSON

Error message

响应不是有效 JSON

What it means

fetchKeJson got an HTTP 2xx response but res.json() failed, so the in-page fetch returns {__keErr:'parse'} and the helper throws CommandExecutionError('响应不是有效 JSON'). This usually means the endpoint returned HTML instead of JSON — most often a risk-control/captcha interstitial or a login page — so the hint points the user to check login state and retry later.

Source

Thrown at clis/ke/utils.js:92

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

/**
 * Build a ke.com city URL prefix. Default city is 'bj' (Beijing).
 */
export function cityUrl(city) {
    return `https://${city}.ke.com`;
}

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Open the exact API URL in the logged-in browser and inspect the raw response
  2. If it's a captcha/risk page, complete verification in the browser, wait, and retry
  3. Re-login (ke auth login) to refresh the session
  4. Verify the endpoint URL and query params are correct
  5. Add request delays — repeated soft blocks indicate rate limiting

Example fix

// before
const r = await fetch(url, {credentials:'include'}); // 200 but HTML body -> __keErr:'parse'
// after
const r = await fetch(url, {credentials:'include', headers:{'Accept':'application/json'}});
const ct = r.headers.get('content-type') || '';
if (!ct.includes('json')) return { __keErr: 'parse' }; // fail fast with clearer signal
Defensive patterns

Strategy: validation

Validate before calling

const r = await page.evaluate(`fetch(${JSON.stringify(url)}, {credentials:'include'}).then(async res => ({
  status: res.status,
  contentType: res.headers.get('content-type') || '',
  head: (await res.text()).slice(0, 200)
}))`);
if (!r.contentType.includes('json')) {
  console.log('Non-JSON response (' + r.contentType + '):', r.head);
  console.log('Likely a captcha/WAF page — complete verification in the browser');
}

Type guard

function looksLikeJson(html) {
  const t = html.trim();
  return t.startsWith('{') || t.startsWith('[');
}

Try / catch

try {
  const data = await fetchKeJson(page, apiUrl);
} catch (e) {
  if (/响应不是有效 JSON/.test(e.message)) {
    console.error('Endpoint returned non-JSON (risk-control page?). ' +
      'Verify login, solve any captcha, wait, and retry with lower frequency.');
    await new Promise(r => setTimeout(r, 30_000));
    return fetchKeJson(page, apiUrl); // single retry after cooldown
  }
  throw e;
}

Prevention

When it happens

Trigger: Endpoint returns an HTML page (captcha, WAF block, login redirect) with status 200; a proxy/gateway injects an error page; the URL is wrong and hits the site's SPA shell; CDN returns compressed/mangled content the page parser can't decode.

Common situations: Beike风控 soft-blocks a session with a 200 HTML challenge; API path typo redirects to the website homepage; corporate proxy rewriting responses.

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/948efb7fb857059d. Report an issue: GitHub.