This repository has been archived on 2026-08-07. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
kb-skills/.planning/phases/08-crawler/08-RESEARCH.md
T

15 KiB
Raw Blame History

Phase 8: 内容爬取实现 - Research

Researched: 2026-03-17 Domain: Playwright API Interception, Zhihu Crawler, Anti-Scraping Confidence: HIGH

Summary

本阶段实现知乎内容爬取的核心功能,使用 Playwright 拦截 API 请求的方式获取结构化数据。关键技术点是复用浏览器 Cookie,自动处理 x-zse-96 签名,无需逆向工程。

Primary recommendation: 使用 Playwright page.route() 拦截知乎 API 请求,直接获取 JSON 响应。热门评论通过 /api/v4/comment_v5/ 端点获取,代理通过环境变量 HTTP_PROXY/HTTPS_PROXY 配置并在 browser.new_context() 中应用。

<user_constraints>

User Constraints (from CONTEXT.md)

Implementation Decisions

爬取方式选择

  • 实现方式: 使用 Playwright 拦截 API 请求
  • 原理: 浏览器访问页面时拦截其发出的 API 请求(如 /api/v4/answers/xxx),直接获取 JSON 响应
  • 优势: 复用浏览器 Cookie,自动处理 x-zse-96 签名,无需逆向
  • 支持内容类型: 问答 (answer)、专栏 (zhuanlan)
  • 不支持: 知乎视频 — 推迟到后续版本

评论爬取范围

  • 爬取范围: 只爬取热门评论
  • 嵌套展开: 不展开回复的回复,只爬第一层评论
  • 可选开关: 评论爬取是可选功能,通过参数控制是否启用

代理配置方式

  • 配置方式: 通过环境变量配置(HTTP_PROXY/HTTPS_PROXY)
  • 配置文件: 添加到 ~/.kb_env,与现有配置方式一致
  • 使用策略: 强制使用代理 — 不配置代理则拒绝爬取,防止 IP 被封

反爬策略

  • 请求频率: 随机延迟,每个请求间隔 1-3 秒
  • 重试策略: 有限重试,失败后重试 3 次,间隔递增
  • 错误处理: 重试全部失败后抛出异常,由上层处理

Claude's Discretion

  • Playwright 拦截的具体实现方式(route/intercept)
  • 热门评论的具体数量限制
  • 重试间隔的具体递增策略
  • 错误提示信息的具体文案

Deferred Ideas (OUT OF SCOPE)

None — discussion stayed within phase scope </user_constraints>

<phase_requirements>

Phase Requirements

ID Description Research Support
CONT-01 爬取知乎问答 - 问题标题、问题描述、回答内容、元数据 Playwright route 拦截 /api/v4/answers/{id} 获取结构化 JSON
CONT-02 爬取知乎专栏/文章 - 标题、正文、元数据 Playwright route 拦截 zhuanlan API 或页面 DOM 解析
CONT-03 爬取内容下的评论(可选开关) /api/v4/comment_v5/{type}/{id}/comments?order=hot 热门评论 API
CONT-04 提取作者信息、发布时间、点赞数等元数据 API 响应中包含完整元数据字段
NET-01 支持代理 IP 配置 环境变量 HTTP_PROXY/HTTPS_PROXY + browser.new_context(proxy=...)
NET-02 请求频率控制,避免触发反爬 time.sleep(random.uniform(1, 3)) 随机延迟
NET-03 自动获取请求签名(x-zse-96),无需手动逆向 Playwright 浏览器自动处理签名
</phase_requirements>

Standard Stack

Core

Library Version Purpose Why Standard
playwright 1.40+ 浏览器自动化 项目已使用,API 拦截功能完善
pydantic 2.x 数据模型验证 项目标准,用于响应数据模型
tenacity 8.x 重试机制 项目已使用,支持可配置超时

Supporting

Library Version Purpose When to Use
httpx 0.25+ HTTP 客户端 如需直接 API 调用(非拦截模式)
python-dotenv 1.0+ 环境变量加载 代理配置读取

Alternatives Considered

Instead of Could Use Tradeoff
Playwright route requests + 逆向签名 需要持续维护签名算法,Playwright 方式更稳定
直接调用 API Selenium + DOM 解析 DOM 结构变化频繁,API 响应更稳定

Installation:

# 已安装,无需额外安装
playwright install chromium  # 如需更新浏览器

Architecture Patterns

kb_workflow/
├── crawler/                    # 新建爬虫模块
│   ├── __init__.py
│   ├── zhihu_crawler.py        # 主爬虫类
│   ├── api_interceptor.py      # API 拦截逻辑
│   ├── models.py               # 响应数据模型
│   └── config.py               # 爬虫配置(代理、延迟等)
├── auth/
│   └── zhihu_login.py          # 现有 - 复用 Cookie 管理
└── metadata/
    └── zhihu_extractor.py      # 现有 - 复用 PlaywrightBrowser

Pattern 1: Playwright Route Interception

What: 拦截浏览器发出的 API 请求,直接获取 JSON 响应 When to use: 获取结构化数据,避免 DOM 解析 Example:

# Source: https://playwright.dev/python/docs/network
from playwright.sync_api import sync_playwright

def intercept_api(route):
    """拦截知乎 API 请求"""
    if "/api/v4/" in route.request.url:
        # 继续请求以获取响应
        response = route.fetch()
        json_data = response.json()
        # 存储拦截到的数据
        route.fulfill(response=response)
    else:
        route.continue_()

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.route("**/api/v4/**", intercept_api)
    page.goto("https://www.zhihu.com/question/123/answer/456")

Pattern 2: 代理配置

What: 通过环境变量配置代理,在 Playwright 中应用 When to use: 所有爬取请求,防止 IP 被封 Example:

# Source: https://playwright.dev/python/docs/network
import os
from playwright.sync_api import sync_playwright

def get_proxy_config():
    """从环境变量获取代理配置"""
    proxy_server = os.environ.get("HTTP_PROXY") or os.environ.get("HTTPS_PROXY")
    if proxy_server:
        # 解析代理 URL
        # http://user:pass@host:port 或 http://host:port
        return {"server": proxy_server}
    return None

# 在 new_context 中应用代理
browser = chromium.launch()
context = browser.new_context(proxy=get_proxy_config())

Pattern 3: 随机延迟与重试

What: 请求间随机延迟,失败时有限重试 When to use: 所有网络请求 Example:

import random
import time
from tenacity import Retrying, stop_after_attempt, wait_exponential

def crawl_with_retry(crawl_func, url):
    """带重试的爬取"""
    for attempt in Retrying(
        stop=stop_after_attempt(3),
        wait=wait_exponential(multiplier=1, min=2, max=10),
        reraise=True
    ):
        with attempt:
            # 随机延迟
            time.sleep(random.uniform(1, 3))
            return crawl_func(url)

Anti-Patterns to Avoid

  • 硬编码延迟时间: 使用固定延迟容易被检测,应使用随机值
  • 忽略代理检查: 不检查代理配置直接爬取可能导致 IP 被封
  • 无限重试: 重试次数应有限制,避免无限循环

Don't Hand-Roll

Problem Don't Build Use Instead Why
请求重试 自定义重试循环 tenacity.Retrying 支持指数退避、超时、回调
API 签名 逆向 x-zse-96 算法 Playwright 浏览器自动处理 签名算法频繁更新,逆向成本高
Cookie 管理 手动解析存储 现有 ZhihuLogin + PlaywrightBrowser 已有成熟实现
代理解析 自定义 URL 解析 Playwright proxy 参数 支持认证、SOCKS5

Key insight: Playwright 拦截模式的核心优势是复用浏览器的完整上下文(Cookie、签名、指纹),无需维护逆向代码。

Common Pitfalls

Pitfall 1: Service Worker 干扰

What goes wrong: 使用 page.route() 时某些请求未被捕获 Why it happens: Service Worker 拦截了网络请求 How to avoid: 在 new_context() 中设置 service_workers="block" Warning signs: API 拦截处理器未被调用

context = browser.new_context(service_workers="block")

Pitfall 2: 代理未生效

What goes wrong: 配置了环境变量但请求仍使用直连 Why it happens: Playwright 默认不读取环境变量 How to avoid: 显式读取环境变量并传入 new_context(proxy=...) Warning signs: IP 被封、请求频率限制

Pitfall 3: 评论 API 分页处理

What goes wrong: 只获取了第一页评论 Why it happens: 评论 API 使用 cursor 分页 How to avoid: 使用 offset 参数处理分页,但根据决策只爬热门评论(单页) Warning signs: 评论数量固定为 20 条

What goes wrong: 爬取时返回登录页面 Why it happens: Cookie 已过期但未提前验证 How to avoid: 爬取前调用 /api/v4/me 验证 Cookie 有效性 Warning signs: 响应包含"登录"关键词

Code Examples

Verified patterns from official sources:

API 拦截存储响应

# Source: https://playwright.dev/python/docs/network
class ZhihuApiInterceptor:
    """知乎 API 拦截器"""

    def __init__(self):
        self.captured_data = {}

    def handle_route(self, route):
        """处理拦截的请求"""
        url = route.request.url

        # 拦截答案 API
        if "/api/v4/answers/" in url:
            response = route.fetch()
            data = response.json()
            self.captured_data["answer"] = data
            route.fulfill(response=response)
            return

        # 拦截评论 API
        if "/comment_v5/" in url:
            response = route.fetch()
            data = response.json()
            self.captured_data["comments"] = data
            route.fulfill(response=response)
            return

        route.continue_()

    def setup(self, page):
        """设置路由拦截"""
        page.route("**/api/v4/**", self.handle_route)

代理配置读取

# Source: https://playwright.dev/python/docs/network
import os
from urllib.parse import urlparse

def parse_proxy_env():
    """解析代理环境变量"""
    proxy_url = os.environ.get("HTTPS_PROXY") or os.environ.get("HTTP_PROXY")
    if not proxy_url:
        return None

    parsed = urlparse(proxy_url)
    config = {"server": f"{parsed.scheme}://{parsed.hostname}:{parsed.port}"}

    if parsed.username and parsed.password:
        config["username"] = parsed.username
        config["password"] = parsed.password

    return config

热门评论 API 调用

# Source: https://docs.tikhub.io/262614090e0
# 知乎评论 V5 API 参数说明
COMMENT_API_TEMPLATE = (
    "https://www.zhihu.com/api/v4/comment_v5/"
    "{content_type}/{content_id}/comments"
    "?order=hot&limit=20"
)

# content_type: "answers" (回答) 或 "articles" (文章)
# order: "hot" (热门) 或 "ts" (最新)
# limit: 每页数量,默认 20

State of the Art

Old Approach Current Approach When Changed Impact
requests + 逆向签名 Playwright 拦截 2024+ 无需维护签名算法
DOM 选择器解析 API 响应 JSON 2024+ 数据结构更稳定
固定延迟 随机延迟 最佳实践 降低被检测风险

Deprecated/outdated:

  • 逆向 x-zse-96 签名算法: 知乎频繁更新签名,维护成本高
  • 基于 DOM 的内容提取: 页面结构变化导致解析失败

Open Questions

  1. 热门评论数量限制

    • What we know: API 默认返回 20 条,支持分页
    • What's unclear: 决策说"只爬热门评论",是否限制为 20 条
    • Recommendation: 默认 20 条,通过参数可配置
  2. 专栏文章的 API 端点

    • What we know: 专栏页面有对应的 API 调用
    • What's unclear: 具体的 API 路径格式
    • Recommendation: 实现时通过 route 拦截自动发现
  3. 重试间隔递增策略

    • What we know: 使用 tenacity 支持指数退避
    • What's unclear: 具体的 min/max 值
    • Recommendation: min=2s, max=10s, multiplier=1

Validation Architecture

Test Framework

Property Value
Framework pytest 7.x
Config file pytest.ini
Quick run command pytest tests/test_crawler/ -x -v
Full suite command pytest tests/ -v

Phase Requirements -> Test Map

Req ID Behavior Test Type Automated Command File Exists?
CONT-01 爬取问答内容 integration pytest tests/test_crawler/test_answer_crawler.py -x Wave 0
CONT-02 爬取专栏文章 integration pytest tests/test_crawler/test_article_crawler.py -x Wave 0
CONT-03 爬取热门评论 integration pytest tests/test_crawler/test_comment_crawler.py -x Wave 0
CONT-04 提取元数据 unit pytest tests/test_crawler/test_metadata_extraction.py -x Wave 0
NET-01 代理配置 unit pytest tests/test_crawler/test_proxy_config.py -x Wave 0
NET-02 请求频率控制 unit pytest tests/test_crawler/test_rate_limiter.py -x Wave 0
NET-03 自动签名处理 integration pytest tests/test_crawler/test_api_intercept.py -x Wave 0

Sampling Rate

  • Per task commit: pytest tests/test_crawler/ -x --tb=short
  • Per wave merge: pytest tests/ -v
  • Phase gate: Full suite green before /gsd:verify-work

Wave 0 Gaps

  • tests/test_crawler/__init__.py - 测试模块初始化
  • tests/test_crawler/conftest.py - 共享 fixtures (mock browser, mock API responses)
  • tests/test_crawler/test_answer_crawler.py - 问答爬取测试
  • tests/test_crawler/test_article_crawler.py - 专栏爬取测试
  • tests/test_crawler/test_comment_crawler.py - 评论爬取测试
  • tests/test_crawler/test_metadata_extraction.py - 元数据提取测试
  • tests/test_crawler/test_proxy_config.py - 代理配置测试
  • tests/test_crawler/test_rate_limiter.py - 频率控制测试
  • tests/test_crawler/test_api_intercept.py - API 拦截测试

Sources

Primary (HIGH confidence)

Secondary (MEDIUM confidence)

Tertiary (LOW confidence)

Metadata

Confidence breakdown:

  • Standard stack: HIGH - 项目已有 Playwright 和 tenacity 使用经验
  • Architecture: HIGH - Playwright 官方文档详细,模式清晰
  • Pitfalls: MEDIUM - 部分问题需要实际运行验证

Research date: 2026-03-17 Valid until: 2026-04-17 (Playwright API 稳定,知乎 API 可能变化)