17 KiB
Phase 7: 登录认证体系 - Research
Researched: 2026-03-17 Domain: Playwright 浏览器自动化、知乎登录认证、Cookie 管理 Confidence: HIGH
Summary
本阶段实现知乎登录认证体系,支持二维码扫码和手动 Cookie 两种认证方式。核心技术基于 Playwright 浏览器自动化,参考 MediaCrawler 项目的成熟实现模式。
关键实现点:
- 二维码登录流程:访问登录页 → Canvas 截图获取二维码 → 保存图片 → 轮询检测
z_c0Cookie → 超时退出 - Cookie 验证:通过
GET /api/v4/me接口检测登录状态 - Cookie 持久化:存储到
~/.kb_env文件,格式为ZHIHU_COOKIE="name1=value1; name2=value2"
Primary recommendation: 扩展现有 PlaywrightBrowser 类添加二维码登录能力,复用 KBSettings 的 zhihu_cookie 字段,新增 login CLI 子命令。
<user_constraints>
User Constraints (from CONTEXT.md)
Locked Decisions
二维码显示方式
- 显示方式: 保存为图片文件,作为标准 step 输出(让 AI 代理能够获取并展示)
- 存储位置: 中间目录(与现有中间文件存放位置一致)
- 等待超时: 60 秒
- 超时行为: 轮询检测,超时退出(不自动重试)
Cookie 存储策略
- 存储位置:
~/.kb_env文件(与现有配置文件一致) - 输入格式: 支持 JSON 导入(Chrome Cookie 导出插件格式),内部存储为字符串
- 存储格式: 原始 Cookie 字符串,例如
ZHIHU_COOKIE="name1=value1; name2=value2" - 加密: 不加密,用户需自行保护
~/.kb_env文件权限
过期检测机制
- 检测时机: 每次使用前检测 Cookie 有效性
- 检测方法: 请求知乎用户信息接口,检查返回状态
- 过期处理: 自动触发扫码重新登录
命令行交互流程
- 登录命令:
python3 kb-workflow/kb_workflow.py login - 默认模式: 扫码模式(弹出二维码让用户扫码)
- 手动设置:
--import-cookie cookies.json从文件导入 JSON 格式 Cookie - 状态查看:
login --status检查当前 Cookie 是否有效
Claude's Discretion
- 二维码图片的具体文件名格式
- 用户信息接口的具体 URL 和返回格式
- JSON Cookie 解析的具体实现
- 错误提示信息的具体文案
Deferred Ideas (OUT OF SCOPE)
None - discussion stayed within phase scope </user_constraints>
<phase_requirements>
Phase Requirements
| ID | Description | Research Support |
|---|---|---|
| AUTH-01 | 用户可通过二维码扫码登录知乎 | Playwright Canvas 截图 + z_c0 Cookie 轮询检测 |
| AUTH-02 | 用户可手动设置 Cookie 进行认证 | JSON 解析 + _add_cookies() 方法复用 |
| AUTH-03 | Cookie 自动持久化到本地文件,下次自动复用 | KBSettings.zhihu_cookie + ~/.kb_env 写入 |
| AUTH-04 | Cookie 过期时自动检测并提示重新登录 | /api/v4/me 接口验证 + 状态码检测 |
| </phase_requirements> |
Standard Stack
Core
| Library | Version | Purpose | Why Standard |
|---|---|---|---|
| playwright | 已安装 | 浏览器自动化 | 现有项目已使用,支持 Cookie、Canvas 操作 |
| pydantic | v2 | Schema 验证 | 现有项目使用,类型安全 |
| pydantic-settings | 已安装 | 配置管理 | 现有 KBSettings 使用 |
| tenacity | 需安装 | 重试/轮询逻辑 | 登录状态轮询的标准库 |
Supporting
| Library | Version | Purpose | When to Use |
|---|---|---|---|
| rich | 可选 | 终端输出美化 | CLI 交互提示 |
| httpx | 可选 | HTTP 请求 | Cookie 验证接口调用(或复用 Playwright) |
Alternatives Considered
| Instead of | Could Use | Tradeoff |
|---|---|---|
| playwright | selenium | Playwright 更现代,项目已使用 |
| playwright | requests + 手动逆向 | 免维护,无需逆向签名算法 |
| tenacity | 自实现轮询 | tenacity 提供声明式重试,更可靠 |
Installation:
pip install tenacity
Architecture Patterns
Recommended Project Structure
kb_workflow/
├── auth/ # 新增:认证模块
│ ├── __init__.py
│ ├── zhihu_login.py # 知乎登录核心逻辑
│ └── cookie_manager.py # Cookie 持久化管理
├── metadata/
│ └── zhihu_extractor.py # 现有:PlaywrightBrowser 类(可扩展)
├── pipeline/
│ └── config.py # 现有:KBSettings(已有 zhihu_cookie)
└── kb_workflow.py # 现有:CLI 入口(添加 login 子命令)
Pattern 1: 二维码登录流程
What: 使用 Playwright 访问知乎登录页,从 Canvas 元素截图获取二维码,轮询检测登录状态
When to use: 默认登录模式
Example:
# Source: MediaCrawler media_platform/zhihu/login.py
import asyncio
from tenacity import retry, stop_after_attempt, wait_fixed
class ZhihuLogin:
LOGIN_URL = "https://www.zhihu.com/signin"
CANVAS_SELECTOR = "canvas.SignIn-qrcode"
@retry(stop=stop_after_attempt(60), wait=wait_fixed(1))
async def check_login_state(self) -> bool:
"""轮询检测登录状态 - 检查 z_c0 Cookie"""
cookies = await self.context.cookies()
for cookie in cookies:
if cookie["name"] == "z_c0":
return True
return False
async def login_by_qrcode(self):
"""二维码登录主流程"""
# 1. 访问登录页
await self.page.goto(self.LOGIN_URL)
# 2. 从 Canvas 截图获取二维码
canvas = await self.page.wait_for_selector(self.CANVAS_SELECTOR)
screenshot = await canvas.screenshot()
qr_base64 = base64.b64encode(screenshot).decode()
# 3. 保存二维码图片
qrcode_path = Path(f"~/work/intermediate/zhihu_qrcode_{timestamp}.png")
qrcode_path.write_bytes(screenshot)
# 4. 轮询等待扫码(60秒超时)
try:
logged_in = await self.check_login_state()
if logged_in:
# 5. 提取并保存 Cookie
cookies = await self.context.cookies()
cookie_str = self._format_cookies(cookies)
return cookie_str
except RetryError:
raise TimeoutError("扫码超时,请重试")
Pattern 2: Cookie 验证
What: 通过知乎 API 检测 Cookie 是否有效
When to use: 每次使用 Cookie 前、login --status 命令
Example:
# Source: 知乎 API 实测
VALIDATION_URL = "https://www.zhihu.com/api/v4/me"
async def validate_cookie(self, cookie_str: str) -> dict:
"""验证 Cookie 有效性"""
# 方式1:使用 Playwright context(推荐,与登录流程一致)
self.browser.start(cookie=cookie_str)
response = await self.page.request.get(self.VALIDATION_URL)
if response.status == 200:
user_info = await response.json()
return {
"valid": True,
"username": user_info.get("name", ""),
"user_id": user_info.get("id", "")
}
elif response.status == 401:
return {"valid": False, "reason": "Cookie 已过期"}
else:
return {"valid": False, "reason": f"验证失败: {response.status}"}
Pattern 3: JSON Cookie 导入
What: 解析 Chrome Cookie Editor 导出的 JSON 格式
When to use: --import-cookie 参数
Example:
# Chrome Cookie Editor JSON 格式:
# [{"name": "z_c0", "value": "xxx", "domain": ".zhihu.com"}, ...]
def parse_json_cookies(json_path: str) -> str:
"""从 JSON 文件解析 Cookie 为字符串格式"""
import json
with open(json_path, 'r') as f:
cookies = json.load(f)
# 过滤知乎域名,转换为 name=value 格式
zhihu_cookies = [
f"{c['name']}={c['value']}"
for c in cookies
if 'zhihu.com' in c.get('domain', '')
]
return '; '.join(zhihu_cookies)
Pattern 4: Cookie 持久化
What: 将 Cookie 写入 ~/.kb_env 文件
When to use: 登录成功后、导入 Cookie 后
Example:
# 复用现有 KBSettings 的 zhihu_cookie 字段
# 存储格式: ZHIHU_COOKIE="name1=value1; name2=value2"
def save_cookie_to_env(cookie_str: str):
"""保存 Cookie 到 ~/.kb_env"""
env_path = Path.home() / ".kb_env"
# 读取现有内容
lines = []
if env_path.exists():
lines = env_path.read_text().splitlines()
# 更新或添加 ZHIHU_COOKIE 行
cookie_line = f'ZHIHU_COOKIE="{cookie_str}"'
found = False
for i, line in enumerate(lines):
if line.startswith("ZHIHU_COOKIE="):
lines[i] = cookie_line
found = True
break
if not found:
lines.append(cookie_line)
# 写回文件
env_path.write_text('\n'.join(lines) + '\n')
# 清除配置缓存
get_settings.cache_clear()
Anti-Patterns to Avoid
- 手动逆向 x-zse-96 签名: 维护成本高,算法经常变化。使用二维码登录完全避免此问题。
- 在终端直接显示二维码 ASCII: 兼容性差,AI 代理无法读取。保存为图片文件是更可靠的方案。
- 硬编码超时逻辑: 使用 tenacity 的
@retry装饰器更清晰、可测试。 - 忽略 Cookie 域名过滤: 导入时必须过滤
.zhihu.com域名,否则会导入无关 Cookie。
Don't Hand-Roll
| Problem | Don't Build | Use Instead | Why |
|---|---|---|---|
| 轮询重试逻辑 | while loop + sleep | tenacity @retry | 声明式、可配置、有异常处理 |
| Cookie 字符串解析 | 手动 split(';') | 现有 _add_cookies() 方法 |
已处理边界情况 |
| 配置文件读写 | 手动文件操作 | pydantic-settings | 自动类型转换、环境变量支持 |
| Canvas 截图 | 手动像素提取 | Playwright element.screenshot() | 内置优化,支持多种格式 |
Key insight: 现有代码库已有 Playwright 封装和配置管理,优先复用而非新建。
Common Pitfalls
Pitfall 1: Canvas 选择器变化
What goes wrong: 知乎更新前端后,Canvas 选择器 canvas.SignIn-qrcode 失效
Why it happens: 前端类名可能随版本变化
How to avoid:
- 使用多个备选选择器
- 添加选择器存在性检测
- 失败时给出明确错误提示
Warning signs: wait_for_selector 超时
CANVAS_SELECTORS = [
"canvas.SignIn-qrcode",
"canvas[class*='qrcode']",
"canvas" # 最宽松的备选
]
async def find_qrcode_canvas(self):
for selector in self.CANVAS_SELECTORS:
try:
return await self.page.wait_for_selector(selector, timeout=5000)
except TimeoutError:
continue
raise RuntimeError("无法找到二维码 Canvas 元素")
Pitfall 2: z_c0 Cookie 延迟出现
What goes wrong: 用户扫码后,z_c0 Cookie 不是立即出现
Why it happens: 服务端验证需要时间
How to avoid: 使用 tenacity 的 1 秒间隔轮询,60 次重试(60 秒总超时)
Warning signs: 扫码成功但检测失败
Pitfall 3: Cookie 格式不一致
What goes wrong: 不同来源的 Cookie 格式不同(字符串 vs JSON vs 字典) Why it happens: Chrome 插件、手动复制、Playwright 导出格式各异 How to avoid:
- 统一内部格式为字符串
name1=value1; name2=value2 - 在边界层做格式转换
- 提供格式自动检测
Warning signs: Cookie 设置后仍显示未登录
Pitfall 4: ~/.kb_env 文件权限
What goes wrong: Cookie 明文存储可能被其他用户读取 Why it happens: 文件创建时使用默认 umask How to avoid:
- 创建文件时设置 600 权限
- 提示用户检查权限
env_path = Path.home() / ".kb_env"
env_path.touch(mode=0o600, exist_ok=True)
Code Examples
CLI 子命令扩展
# Source: 现有 kb_workflow.py 模式
# 在 main() 函数中添加 login 子命令
def add_login_subparser(subparsers):
"""添加 login 子命令"""
login_parser = subparsers.add_parser('login', help='知乎登录认证')
login_parser.add_argument('--import-cookie', '-i', type=str,
help='从 JSON 文件导入 Cookie')
login_parser.add_argument('--status', action='store_true',
help='检查当前 Cookie 状态')
return login_parser
# 在 main() 中:
if args.command == 'login':
if args.status:
result = check_cookie_status()
elif args.import_cookie:
result = import_cookie_from_file(args.import_cookie)
else:
result = login_by_qrcode()
print(json.dumps(result, ensure_ascii=False, indent=2))
扩展 PlaywrightBrowser
# Source: 现有 zhihu_extractor.py PlaywrightBrowser 类
# 添加二维码登录相关方法
class PlaywrightBrowser:
# 现有方法保持不变...
async def get_qrcode_screenshot(self) -> bytes:
"""获取登录二维码截图"""
canvas = await self.page.wait_for_selector("canvas.SignIn-qrcode")
return await canvas.screenshot()
async def check_login_cookie(self) -> bool:
"""检测是否已登录(检查 z_c0 Cookie)"""
cookies = await self.context.cookies()
return any(c["name"] == "z_c0" for c in cookies)
async def get_all_cookies_str(self) -> str:
"""获取所有 Cookie 为字符串格式"""
cookies = await self.context.cookies()
return '; '.join(f"{c['name']}={c['value']}" for c in cookies)
State of the Art
| Old Approach | Current Approach | When Changed | Impact |
|---|---|---|---|
| 逆向 x-zse-96 签名 | 二维码扫码登录 | 2024+ | 免维护,无需逆向 |
| 终端 ASCII 二维码 | 图片文件保存 | 本项目决定 | AI 代理可读取 |
| 手动 Cookie 管理 | 自动持久化 | 本项目决定 | 用户体验提升 |
Deprecated/outdated:
- 纯 API 请求(无浏览器):需要维护签名算法,知乎经常更新
- 第三方打码服务:付费且不稳定,二维码登录免费可靠
Open Questions
-
二维码图片文件名格式
- What we know: 存储在中间目录,包含时间戳
- What's unclear: 是否需要包含随机数避免冲突
- Recommendation: 使用
zhihu_qrcode_{timestamp}.png格式,同一秒内多次登录概率低
-
验证接口的精确返回格式
- What we know:
/api/v4/me返回用户信息 - What's unclear: 具体字段名称(name vs username)
- Recommendation: 实现时先打印完整响应,根据实际返回调整字段名
- What we know:
Validation Architecture
Test Framework
| Property | Value |
|---|---|
| Framework | pytest |
| Config file | pytest.ini |
| Quick run command | pytest tests/test_auth/ -x -v |
| Full suite command | pytest tests/ -v |
Phase Requirements → Test Map
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|---|---|---|---|---|
| AUTH-01 | 二维码扫码登录 | unit + integration | pytest tests/test_auth/test_qrcode_login.py -x |
Wave 0 |
| AUTH-02 | 手动设置 Cookie | unit | pytest tests/test_auth/test_cookie_import.py -x |
Wave 0 |
| AUTH-03 | Cookie 持久化 | unit | pytest tests/test_auth/test_cookie_persistence.py -x |
Wave 0 |
| AUTH-04 | Cookie 过期检测 | unit | pytest tests/test_auth/test_cookie_validation.py -x |
Wave 0 |
Sampling Rate
- Per task commit:
pytest tests/test_auth/ -x - Per wave merge:
pytest tests/ -v - Phase gate: Full suite green before
/gsd:verify-work
Wave 0 Gaps
tests/test_auth/__init__.py— 测试模块初始化tests/test_auth/test_qrcode_login.py— AUTH-01 二维码登录测试tests/test_auth/test_cookie_import.py— AUTH-02 Cookie 导入测试tests/test_auth/test_cookie_persistence.py— AUTH-03 Cookie 持久化测试tests/test_auth/test_cookie_validation.py— AUTH-04 Cookie 验证测试tests/test_auth/conftest.py— 共享 fixtures (mock browser, mock env file)
测试策略:
- 使用
unittest.mock模拟 Playwright browser 和 context - 使用
tmp_pathfixture 创建临时~/.kb_env文件 - Cookie 验证测试使用 mock HTTP 响应
Sources
Primary (HIGH confidence)
- MediaCrawler 源码 -
media_platform/zhihu/login.py,tools/crawler_util.py- 二维码登录完整实现
- Cookie 格式转换函数
- 现有项目代码 -
kb_workflow/metadata/zhihu_extractor.py- PlaywrightBrowser 类实现
- Cookie 设置方法
_add_cookies()
- 现有项目代码 -
kb_workflow/pipeline/config.py- KBSettings 配置类
- zhihu_cookie 字段定义
Secondary (MEDIUM confidence)
- 知乎 API 实测 -
/api/v4/me端点行为 - Chrome Cookie Editor JSON 格式规范
Tertiary (LOW confidence)
- 网络博客关于知乎登录流程的描述(需实现时验证)
Metadata
Confidence breakdown:
- Standard stack: HIGH - 现有项目已使用 Playwright 和 pydantic-settings
- Architecture: HIGH - MediaCrawler 提供了成熟的参考实现
- Pitfalls: MEDIUM - 部分边界情况需要在实现时验证
Research date: 2026-03-17 Valid until: 2026-04-17 (知乎 API 可能变化,选择器可能更新)