{"record":{"id":"bfcc2a0e7fc275c6","repo":"jackwener/OpenCLI","slug":"error-bfcc2a","errorCode":null,"errorMessage":"触发了验证码，请先在浏览器中完成验证","messagePattern":"触发了验证码，请先在浏览器中完成验证","errorType":"exception","errorClass":"AuthRequiredError","httpStatus":null,"severity":"warning","filePath":"clis/ke/utils.js","lineNumber":54,"sourceCode":"      } catch(e) {\n        return { href: '', title: '', body_text: '' };\n      }\n    })()\n  `);\n    if (!result) {\n        return { href: '', title: '', body_text: '' };\n    }\n    return {\n        href: cleanText(result.href),\n        title: cleanText(result.title),\n        body_text: cleanText(result.body_text),\n    };\n}\n\nexport function assertNotBlocked(state) {\n    const { href, title, body_text } = state;\n    if (href.includes('hip.ke.com/captcha') || href.includes('/captcha')) {\n        throw new AuthRequiredError('ke.com', '触发了验证码，请先在浏览器中完成验证');\n    }\n    if (CAPTCHA_TEXT_PATTERNS.some(p => title.includes(p) || body_text.includes(p))) {\n        throw new AuthRequiredError('ke.com', '触发了验证码，请先在浏览器中完成滑块验证');\n    }\n    if (LOGIN_TEXT_PATTERNS.some(p => title.includes(p))) {\n        throw new AuthRequiredError('ke.com', '未登录，请先在浏览器中登录贝壳找房');\n    }\n}\n\nexport async function gotoKe(page, url) {\n    await page.goto(url, { settleMs: 2500 });\n    await page.wait(2);\n    const state = await readPageState(page);\n    assertNotBlocked(state);\n    return state;\n}\n\n/**","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/ke/utils.js#L36-L72","documentation":"assertNotBlocked inspects the rendered page state after every gotoKe navigation. If the URL was redirected to a captcha page (hip.ke.com/captcha or any /captcha path), it throws AuthRequiredError telling the user to complete the captcha in the browser before CLI scraping can continue. This is Beike's anti-bot gate, not a bug in the command.","triggerScenarios":"Any ke.com command whose page.goto results in a redirect to a captcha URL — typically after too-frequent requests, datacenter IP, or a flagged session.","commonSituations":"Heavy polling/looping over ke.com endpoints; running from a cloud/VPN IP that Beike rate-limits; cookie-less or freshly logged-in session triggering risk control.","solutions":["Open the blocked URL in the managed browser and solve the captcha manually","Slow down / add delays between ke.com requests","Log in (lianjia_token) — authenticated sessions get fewer captchas","Switch network/IP (leave VPN/datacenter ranges) and retry"],"exampleFix":"// before\nfor (const id of ids) await gotoKe(page, url(id)); // captcha after N rapid hits\n// after\nfor (const id of ids) {\n  await gotoKe(page, url(id));\n  await page.wait(5 + Math.random() * 5); // human-like pacing\n}","handlingStrategy":"retry","validationCode":"const state = await readPageState(page);\nif (state.href.includes('/captcha')) {\n  console.log('Captcha detected — solve it in the browser, then re-run');\n  process.exit(2);\n}","typeGuard":null,"tryCatchPattern":"try {\n  const state = await gotoKe(page, url);\n} catch (e) {\n  if (e instanceof AuthRequiredError && e.message.includes('验证码')) {\n    console.error('Solve the captcha at ' + url + ' in the browser, then retry');\n    await backoff(60_000);\n    return gotoKe(page, url); // retry after manual solve / cooldown\n  }\n  throw e;\n}","preventionTips":["Throttle ke.com requests (seconds of jitter between pages)","Prefer logged-in sessions; anonymous hits get more captchas","Avoid datacenter/VPN IPs for scraping","Back off exponentially on repeated captcha errors instead of hammering"],"tags":["captcha","anti-bot","rate-limit","ke"],"backgroundTag":"captcha-challenge","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}