{"record":{"id":"ee27802436ee0f66","repo":"jackwener/OpenCLI","slug":"error-ee2780","errorCode":null,"errorMessage":"未登录或登录已过期，请先在浏览器中登录贝壳找房","messagePattern":"未登录或登录已过期，请先在浏览器中登录贝壳找房","errorType":"exception","errorClass":"AuthRequiredError","httpStatus":null,"severity":"error","filePath":"clis/ke/utils.js","lineNumber":89,"sourceCode":"\n/**\n * Fetch a ke.com JSON API from inside the browser context (credentials included).\n */\nexport async function fetchKeJson(page, url) {\n    const result = await page.evaluate(`(async () => {\n    const res = await fetch(${JSON.stringify(url)}, { credentials: 'include' });\n    if (!res.ok) return { __keErr: res.status };\n    try {\n      return await res.json();\n    } catch {\n      return { __keErr: 'parse' };\n    }\n  })()`);\n    const r = result;\n    if (r?.__keErr !== undefined) {\n        const code = r.__keErr;\n        if (code === 401 || code === 403) {\n            throw new AuthRequiredError('ke.com', '未登录或登录已过期，请先在浏览器中登录贝壳找房');\n        }\n        if (code === 'parse') {\n            throw new CommandExecutionError('响应不是有效 JSON', '可能触发了风控，请检查登录状态或稍后重试');\n        }\n        throw new CommandExecutionError(`HTTP ${code}`, '请检查网络连接或登录状态');\n    }\n    return result;\n}\n\n/**\n * Build a ke.com city URL prefix. Default city is 'bj' (Beijing).\n */\nexport function cityUrl(city) {\n    return `https://${city}.ke.com`;\n}\n","sourceCodeStart":71,"sourceCodeEnd":105,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/ke/utils.js#L71-L105","documentation":"fetchKeJson performs a fetch with credentials from inside the browser page and inspects __keErr. When the endpoint responds 401 or 403 it throws AuthRequiredError meaning the Beike session is missing or expired — the server rejected the authenticated request, so the user must log into ke.com in the browser again.","triggerScenarios":"Calling any ke.com JSON endpoint via fetchKeJson with an expired/invalid lianjia_token; cookies never set; server-side session revoked; endpoint requires auth the current anonymous session lacks.","commonSituations":"Sessions expire after hours/days; running scheduled jobs without re-login; token invalidated by logging in on another device; risk control returns 403 for flagged IPs even with a cookie.","solutions":["Re-run the ke auth login flow to obtain a fresh lianjia_token","Confirm the cookie via ke auth verify before the batch job","If 403 persists with a valid cookie, the IP is likely flagged — switch network or slow down","Check the exact endpoint in a logged-in browser to confirm it isn't permanently restricted"],"exampleFix":"// before\nconst data = await fetchKeJson(page, api); // throws on expired session\n// after\nif (!(await authVerify(page))) await authLogin(page); // ensure fresh session first\nconst data = await fetchKeJson(page, api);","handlingStrategy":"try-catch","validationCode":"const ok = await page.evaluate(`fetch(${JSON.stringify(url)}, {credentials:'include', method:'HEAD'}).then(r => r.status)`);\nif (ok === 401 || ok === 403) {\n  console.log('Session expired — run `opencli ke auth login` before continuing');\n  process.exit(2);\n}","typeGuard":null,"tryCatchPattern":"import { AuthRequiredError } from '@jackwener/opencli/errors';\ntry {\n  const data = await fetchKeJson(page, apiUrl);\n} catch (e) {\n  if (e instanceof AuthRequiredError) {\n    await keLogin(page);            // refresh session\n    return fetchKeJson(page, apiUrl); // retry once\n  }\n  throw e;\n}","preventionTips":["Refresh the login before long-running or scheduled jobs","Catch AuthRequiredError and re-login + retry once automatically","Avoid sharing one account across machines (mutual session invalidation)","If 403 persists with a valid cookie, change IP / reduce request rate"],"tags":["auth","http-401","http-403","session-expired","ke"],"backgroundTag":"auth-token-expired","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}