{"record":{"id":"309741c5a509f544","repo":"jackwener/OpenCLI","slug":"zhihu-answer-navigation-changed-identity-for-answe","errorCode":null,"errorMessage":"Zhihu answer navigation changed identity for answer ${target.answerId}","messagePattern":"Zhihu answer navigation changed identity for answer (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/zhihu/download-helpers.js","lineNumber":159,"sourceCode":"    });\n    return requireArticle(raw);\n}\n\nexport async function extractAnswer(page, target) {\n    try {\n        await page.goto(`https://www.zhihu.com/answer/${target.answerId}`);\n    }\n    catch (error) {\n        throw new CommandExecutionError(\n            `Failed to open Zhihu answer ${target.answerId}: ${error instanceof Error ? error.message : String(error)}`,\n            'Open the answer URL in Chrome and retry after the page is reachable.',\n        );\n    }\n    const currentUrl = typeof page.getCurrentUrl === 'function' ? await page.getCurrentUrl().catch(() => '') : '';\n    const currentTarget = parseAnswerTarget(currentUrl);\n    if (!currentTarget || currentTarget.answerId !== target.answerId || !currentTarget.questionId\n        || (target.questionId && currentTarget.questionId && target.questionId !== currentTarget.questionId)) {\n        throw new CommandExecutionError(`Zhihu answer navigation changed identity for answer ${target.answerId}`);\n    }\n\n    const apiUrl = `https://www.zhihu.com/api/v4/answers/${target.answerId}?include=content,author,created_time,question,url`;\n    const normalize = `(${normalizeContentImages.toString()})`;\n    const raw = await page.evaluate(`\n      (async () => {\n        let response;\n        try {\n          response = await fetch(${JSON.stringify(apiUrl)}, { credentials: 'include' });\n        } catch (error) {\n          return { fetchError: error instanceof Error ? error.message : String(error) };\n        }\n        let payload;\n        try {\n          payload = await response.json();\n        } catch {\n          return { status: response.status, malformed: true };\n        }","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/zhihu/download-helpers.js#L141-L177","documentation":"After page.goto, extractAnswer reads the current URL and re-parses it with parseAnswerTarget to verify Zhihu actually landed on the requested answer. If the parsed URL lacks an answerId, has a different answerId, has no questionId, or the questionId mismatches the requested target, this error is thrown. It guards against Zhihu silently redirecting to a login page, captcha, question page, or a different answer, which would otherwise produce scraped data for the wrong content.","triggerScenarios":"Calling extractAnswer when, after navigation, the final URL is not /question/<qid>/answer/<aid>: redirect to sign-in/captcha wall, answer deleted or collapsed redirecting to the question page, Zhihu redirecting to a normalized/different answer URL, or the bridge driver not exposing getCurrentUrl (empty string fails the check).","commonSituations":"Not logged in and Zhihu forces a login redirect; anti-bot risk control redirects to a verification page; the answer was deleted so Zhihu bounces to the question; a mobile-app-style link redirects to a different canonical answer; target.questionId came from an outdated URL that no longer matches after Zhihu re-mapped the question.","solutions":["Log in to www.zhihu.com in the connected Chrome profile so navigation is not redirected to sign-in.","Open the answer URL manually and check whether it was deleted or redirects; use the current canonical URL to rebuild the target.","Print/inspect page.getCurrentUrl() after the failure to see where the navigation actually landed.","Retry later if a captcha/risk-control wall redirected you; verify the answerId/questionId target values are correct and current."],"exampleFix":"// before\nawait extractAnswer(page, { answerId: oldId, questionId: staleQuestionId });\n// after\n// confirm the canonical URL first, then use matching ids\nconst canonical = 'https://www.zhihu.com/question/999/answer/12345';\nconst t = parseDownloadTarget(canonical); // { kind:'answer', answerId:'12345', questionId:'999' }\nawait extractAnswer(page, t);","handlingStrategy":"validation","validationCode":"const target = parseDownloadTarget(input);\nif (!target || target.kind !== 'answer' || !/^\\d+$/.test(target.answerId)) {\n  throw new Error('invalid answer target');\n}\n// Prefer a fresh canonical URL from Zhihu over a stale stored questionId","typeGuard":"function isAnswerTarget(t) {\n  return !!t && typeof t === 'object' && t.kind === 'answer'\n    && typeof t.answerId === 'string' && /^\\d+$/.test(t.answerId)\n    && (t.questionId === undefined || /^\\d*$/.test(t.questionId || ''));\n}","tryCatchPattern":"try {\n  await extractAnswer(page, target);\n} catch (err) {\n  if (String(err.message).includes('changed identity')) {\n    // likely login/captcha redirect or deleted answer — inspect actual URL\n    const landed = await page.getCurrentUrl();\n    console.error('redirected to:', landed, '- log in or refresh the target');\n  }\n  throw err;\n}","preventionTips":["Log in to Zhihu in the connected Chrome profile before extracting answers","Refresh stored answer targets from canonical URLs; old questionId mappings can go stale","Check for deleted/collapsed answers before batch runs","Wait out captcha/risk-control walls instead of retrying immediately"],"tags":["redirect","authentication","scraping","zhihu"],"backgroundTag":"unexpected-redirect","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}