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/research/ARCHITECTURE.md
T

1936 lines
69 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Architecture Research
**Domain:** Skill-based Workflow Orchestration System
**Researched:** 2026-03-13 (updated 2026-03-18 for v1.3)
**Confidence:** HIGH
---
## v1.3 补充:反爬虫优化架构
**Milestone:** Anti-Crawler Optimization
**Researched:** 2026-03-18
**Focus:** 代理池、行为伪装、验证码检测、错误恢复
---
## Executive Summary
本文档研究如何将反爬虫优化功能集成到现有的 kb-skills 架构中。基于对现有代码结构和反爬虫最佳实践的分析,推荐采用 **模块化的中间件/装饰器模式**,将反爬虫功能作为独立组件注入到现有的 Pipeline 和 Crawler 中。
关键架构决策:
1. **代理池管理** — 新建 `kb_workflow/crawler/proxy_pool.py` 模块,作为独立服务
2. **行为伪装** — 通过装饰器/中间件模式集成到 Playwright 调用
3. **验证码处理** — 检测-暂停机制,不自动解决(避免付费服务依赖)
4. **错误恢复** — 增强现有 `retry.py`,添加 jitter 和状态感知
---
## Current Architecture Overview
```
┌─────────────────────────────────────────────────────────────────────────┐
│ CLI Layer │
│ kb_workflow/cli.py - process/login subcommands │
├─────────────────────────────────────────────────────────────────────────┤
│ Pipeline Layer │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ ParseProcessor │→ │DownloadProcessor│→ │ ASRProcessor │ → ... │
│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │ │
│ ↓ ↓ ↓ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Platform Adapters (BilibiliAdapter, ZhihuAdapter) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────────┤
│ Crawler Layer │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ZhihuCrawler (Playwright) - API Interception + Cookie Auth │ │
│ └─────────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────────┤
│ Infrastructure │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ CrawlerConfig │ │ KBSettings │ │ WorkflowState│ │
│ └───────────────┘ └───────────────┘ └───────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
```
---
## Recommended New Architecture (v1.3)
```
┌─────────────────────────────────────────────────────────────────────────┐
│ CLI Layer │
│ kb_workflow/cli.py - process/login subcommands │
├─────────────────────────────────────────────────────────────────────────┤
│ Pipeline Layer │
│ [Existing processors - unchanged] │
├─────────────────────────────────────────────────────────────────────────┤
│ Platform Adapters Layer │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ZhihuAdapter / BilibiliAdapter │ │
│ │ (uses @with_anti_detection decorator) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────────┤
│ Anti-Crawler Layer (NEW) │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ ProxyPool │ │BehaviorCamouflag│ │ CaptchaDetector │ │
│ │ Manager │ │ e │ │ │ │
│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │ │
│ └────────────────────┴────────────────────┘ │
│ │ │
│ ┌───────────┴───────────┐ │
│ │ @with_anti_detection │ │
│ │ (decorator) │ │
│ └───────────────────────┘ │
├─────────────────────────────────────────────────────────────────────────┤
│ Crawler Layer │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ZhihuCrawler (enhanced with anti-detection hooks) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────────┤
│ Infrastructure │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │CrawlerConfig │ │AntiCrawlerConf│ │ KBSettings │ │
│ │ (enhanced) │ │ (NEW) │ │ (enhanced) │ │
│ └───────────────┘ └───────────────┘ └───────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
```
---
## Component Design
### 1. Proxy Pool Manager
**Location:** `kb_workflow/crawler/proxy_pool.py`
**Rationale:** 独立模块,与现有 `CrawlerConfig` 分离,因为:
- 代理池是状态性的(需要跟踪健康状态、使用次数)
- 可能被多个 Crawler 共享
- 需要独立的配置源(环境变量、文件、API)
**Design:**
```python
# kb_workflow/crawler/proxy_pool.py
from typing import Optional, List, Dict, Any
from pydantic import BaseModel
import random
from dataclasses import dataclass, field
from datetime import datetime
@dataclass
class ProxyInfo:
"""代理信息"""
url: str
protocol: str = "http" # http, https, socks5
username: Optional[str] = None
password: Optional[str] = None
last_used: Optional[datetime] = None
fail_count: int = 0
success_count: int = 0
is_healthy: bool = True
def to_playwright_format(self) -> Dict[str, Any]:
config = {"server": self.url}
if self.username:
config["username"] = self.username
if self.password:
config["password"] = self.password
return config
class ProxyPoolConfig(BaseModel):
"""代理池配置"""
enabled: bool = False
# 代理列表(支持多个代理)
proxies: List[str] = []
# 轮换策略: round_robin, random, least_used
rotation_strategy: str = "random"
# 健康检查
health_check_enabled: bool = True
max_fail_count: int = 3 # 连续失败 N 次后标记为不健康
# 冷却时间(秒)
cooldown_seconds: float = 0 # 同一代理两次使用之间的最小间隔
@classmethod
def from_env(cls) -> "ProxyPoolConfig":
"""从环境变量加载代理池配置"""
import os
proxies_str = os.environ.get("KB_PROXY_POOL", "")
proxies = [p.strip() for p in proxies_str.split(",") if p.strip()]
return cls(
enabled=len(proxies) > 0,
proxies=proxies,
rotation_strategy=os.environ.get("KB_PROXY_STRATEGY", "random"),
)
class ProxyPoolManager:
"""代理池管理器
职责:
- 管理代理列表
- 轮换策略
- 健康状态跟踪
"""
def __init__(self, config: Optional[ProxyPoolConfig] = None):
self.config = config or ProxyPoolConfig.from_env()
self._proxies: List[ProxyInfo] = []
self._current_index = 0
self._init_proxies()
def _init_proxies(self):
"""初始化代理列表"""
for proxy_url in self.config.proxies:
self._proxies.append(ProxyInfo(url=proxy_url))
def get_proxy(self) -> Optional[ProxyInfo]:
"""获取下一个可用代理"""
healthy_proxies = [p for p in self._proxies if p.is_healthy]
if not healthy_proxies:
return None
if self.config.rotation_strategy == "round_robin":
proxy = healthy_proxies[self._current_index % len(healthy_proxies)]
self._current_index += 1
else: # random
proxy = random.choice(healthy_proxies)
proxy.last_used = datetime.now()
return proxy
def report_success(self, proxy_url: str):
"""报告代理使用成功"""
for p in self._proxies:
if p.url == proxy_url:
p.success_count += 1
p.fail_count = 0 # 重置失败计数
break
def report_failure(self, proxy_url: str):
"""报告代理使用失败"""
for p in self._proxies:
if p.url == proxy_url:
p.fail_count += 1
if p.fail_count >= self.config.max_fail_count:
p.is_healthy = False
break
# Module-level singleton
_proxy_pool: Optional[ProxyPoolManager] = None
def get_proxy_pool() -> ProxyPoolManager:
"""获取代理池管理器单例"""
global _proxy_pool
if _proxy_pool is None:
_proxy_pool = ProxyPoolManager()
return _proxy_pool
```
---
### 2. Behavior Camouflage
**Location:** `kb_workflow/crawler/behavior.py`
**Rationale:** 行为伪装逻辑应该:
- 可以独立测试
- 可以被装饰器或直接调用
- 支持可配置的随机性
**Design:**
```python
# kb_workflow/crawler/behavior.py
import random
import time
from typing import Optional, Callable
from functools import wraps
from pydantic import BaseModel, Field
class BehaviorConfig(BaseModel):
"""行为伪装配置"""
# 请求间隔
min_delay: float = Field(default=1.0, description="最小延迟(秒)")
max_delay: float = Field(default=3.0, description="最大延迟(秒)")
# Jitter 配置(防止模式检测)
jitter_enabled: bool = True
jitter_range: float = Field(default=0.3, description="抖动范围(+/- 比例)")
# 人类模拟
mouse_movement_enabled: bool = False # Playwright 场景下可选
scroll_simulation_enabled: bool = False
# User-Agent 随机化
user_agent_rotation: bool = True
# 常用 User-Agent 池
USER_AGENTS = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15",
]
class BehaviorCamouflage:
"""行为伪装管理器"""
def __init__(self, config: Optional[BehaviorConfig] = None):
self.config = config or BehaviorConfig()
def get_random_delay(self) -> float:
"""获取随机延迟时间(带 jitter)"""
base_delay = random.uniform(self.config.min_delay, self.config.max_delay)
if self.config.jitter_enabled:
jitter = base_delay * self.config.jitter_range * random.uniform(-1, 1)
base_delay += jitter
return max(0.1, base_delay) # 确保非负
def apply_delay(self):
"""应用随机延迟"""
delay = self.get_random_delay()
time.sleep(delay)
return delay
def get_random_user_agent(self) -> str:
"""获取随机 User-Agent"""
if self.config.user_agent_rotation:
return random.choice(USER_AGENTS)
return USER_AGENTS[0]
def get_playwright_context_options(self) -> dict:
"""获取 Playwright context 选项(包含反检测设置)"""
options = {
"user_agent": self.get_random_user_agent(),
}
return options
def with_behavior_camouflage(config: Optional[BehaviorConfig] = None):
"""装饰器:为函数添加行为伪装
用于同步函数,在调用前添加随机延迟。
Example:
@with_behavior_camouflage()
def fetch_page(url):
return requests.get(url)
"""
camouflage = BehaviorCamouflage(config)
def decorator(func: Callable):
@wraps(func)
def wrapper(*args, **kwargs):
camouflage.apply_delay()
return func(*args, **kwargs)
return wrapper
return decorator
```
---
### 3. CAPTCHA Detection
**Location:** `kb_workflow/crawler/captcha.py`
**Rationale:**
- **不实现自动解决** — 根据项目约束(Out of Scope: 付费打码服务)
- **检测+暂停机制** — 检测到验证码时暂停并通知用户
**Design:**
```python
# kb_workflow/crawler/captcha.py
import re
from typing import Optional, Tuple
from enum import Enum
from dataclasses import dataclass
from pydantic import BaseModel
from kb_workflow.pipeline.errors import KBError, ErrorType
class CaptchaType(str, Enum):
"""验证码类型"""
RECAPTCHA_V2 = "recaptcha_v2"
RECAPTCHA_V3 = "recaptcha_v3"
HCAPTCHA = "hcaptcha"
CLOUDFLARE = "cloudflare"
IMAGE_CAPTCHA = "image_captcha"
SLIDER = "slider"
UNKNOWN = "unknown"
@dataclass
class CaptchaDetectionResult:
"""验证码检测结果"""
detected: bool
captcha_type: Optional[CaptchaType] = None
confidence: float = 0.0
indicators: list = None
def __post_init__(self):
if self.indicators is None:
self.indicators = []
class CaptchaDetector:
"""验证码检测器
职责:
- 检测页面是否包含验证码
- 识别验证码类型
- 不负责解决验证码(需要人工介入)
"""
# 验证码指示器(页面元素/URL/内容)
CAPTCHA_INDICATORS = {
CaptchaType.RECAPTCHA_V2: [
"g-recaptcha",
"recaptcha-checkbox",
"google.com/recaptcha",
],
CaptchaType.RECAPTCHA_V3: [
"grecaptcha.execute",
"recaptcha-v3",
],
CaptchaType.HCAPTCHA: [
"h-captcha",
"hcaptcha.com",
],
CaptchaType.CLOUDFLARE: [
"cloudflare",
"cf-browser-verification",
"challenge-platform",
],
CaptchaType.IMAGE_CAPTCHA: [
"captcha.png",
"verifycode",
"img captcha",
],
CaptchaType.SLIDER: [
"slider",
"slide-verify",
"drag verify",
],
}
def detect_from_html(self, html_content: str) -> CaptchaDetectionResult:
"""从 HTML 内容检测验证码"""
html_lower = html_content.lower()
indicators = []
for captcha_type, patterns in self.CAPTCHA_INDICATORS.items():
for pattern in patterns:
if pattern.lower() in html_lower:
indicators.append(pattern)
return CaptchaDetectionResult(
detected=True,
captcha_type=captcha_type,
confidence=0.8,
indicators=indicators,
)
return CaptchaDetectionResult(detected=False)
def detect_from_response(
self,
status_code: int,
headers: dict,
url: str
) -> CaptchaDetectionResult:
"""从 HTTP 响应检测验证码"""
# 403 通常表示被拦截或需要验证
if status_code == 403:
return CaptchaDetectionResult(
detected=True,
captcha_type=CaptchaType.UNKNOWN,
confidence=0.6,
indicators=["403 Forbidden"],
)
# 检查 URL 重定向
if "captcha" in url.lower() or "verify" in url.lower():
return CaptchaDetectionResult(
detected=True,
captcha_type=CaptchaType.UNKNOWN,
confidence=0.7,
indicators=[f"URL redirect: {url}"],
)
return CaptchaDetectionResult(detected=False)
class CaptchaError(KBError):
"""验证码错误
当检测到验证码时抛出,需要人工介入。
"""
def __init__(
self,
step_name: str,
input_summary: str,
captcha_type: CaptchaType,
suggestion: str = "请手动完成验证码后重试"
):
super().__init__(
message=f"检测到验证码: {captcha_type.value}",
error_type=ErrorType.AUTH,
step_name=step_name,
input_summary=input_summary,
suggestion=suggestion,
)
self.captcha_type = captcha_type
```
---
### 4. Enhanced Retry Mechanism
**Location:** `kb_workflow/pipeline/retry.py` (enhance existing)
**Rationale:**
- 现有实现使用 tenacity,已经很好
- 增强点:添加 jitter、状态感知、验证码感知
**Enhanced Design:**
```python
# kb_workflow/pipeline/retry.py (enhancements)
import random
import logging
from tenacity import (
retry,
stop_after_attempt,
wait_exponential,
retry_if_exception_type,
before_sleep_log,
wait_combine,
wait_random,
)
from .errors import NetworkError, AuthError
logger = logging.getLogger(__name__)
def wait_exponential_jitter(
multiplier: float = 1,
min: float = 2,
max: float = 10,
jitter: float = 0.5
):
"""指数退避 + Jitter
Args:
multiplier: 退避乘数
min: 最小等待时间
max: 最大等待时间
jitter: 随机抖动范围(0-1)
"""
return wait_combine(
wait_exponential(multiplier=multiplier, min=min, max=max),
wait_random(0, jitter)
)
# 增强版重试装饰器(带 jitter)
retry_on_network_error_with_jitter = retry(
stop=stop_after_attempt(3),
wait=wait_exponential_jitter(multiplier=1, min=2, max=10, jitter=0.5),
retry=retry_if_exception_type(NetworkError),
before_sleep=before_sleep_log(logger, logging.WARNING),
reraise=True,
)
"""带 Jitter 的网络错误重试装饰器
在指数退避基础上增加随机抖动,避免多个请求同时重试的"惊群效应"。
"""
# 验证码感知重试(遇到 CaptchaError 不重试)
retry_on_transient_error = retry(
stop=stop_after_attempt(3),
wait=wait_exponential_jitter(multiplier=1, min=2, max=10, jitter=0.5),
retry=(
retry_if_exception_type(NetworkError) &
~retry_if_exception_type(AuthError) # AuthError 不重试
),
before_sleep=before_sleep_log(logger, logging.WARNING),
reraise=True,
)
"""瞬态错误重试装饰器
只重试网络错误,不重试认证错误(包括验证码)。
"""
```
---
### 5. Unified Anti-Detection Decorator
**Location:** `kb_workflow/crawler/anti_detection.py`
**Rationale:**
- 统一入口点,组合所有反爬虫功能
- 便于在 PlatformAdapter 中使用
**Design:**
```python
# kb_workflow/crawler/anti_detection.py
from typing import Optional, Callable, Dict, Any
from functools import wraps
from dataclasses import dataclass
from .proxy_pool import ProxyPoolManager, get_proxy_pool, ProxyInfo
from .behavior import BehaviorCamouflage, BehaviorConfig
from .captcha import CaptchaDetector, CaptchaError, CaptchaType
@dataclass
class AntiDetectionConfig:
"""反检测配置"""
proxy_enabled: bool = True
behavior_camouflage_enabled: bool = True
captcha_detection_enabled: bool = True
behavior_config: Optional[BehaviorConfig] = None
class AntiDetectionContext:
"""反检测上下文
在一次爬取过程中传递的状态信息。
"""
def __init__(self):
self.proxy: Optional[ProxyInfo] = None
self.delays_applied: list = []
self.captcha_detected: bool = False
def with_anti_detection(config: Optional[AntiDetectionConfig] = None):
"""装饰器:集成所有反检测功能
组合使用:
1. 代理池轮换
2. 行为伪装
3. 验证码检测
Example:
@with_anti_detection()
async def fetch_content(self, url: str, context: AntiDetectionContext = None):
# context.proxy 可用于 Playwright
# 延迟已自动应用
...
"""
_config = config or AntiDetectionConfig()
behavior = BehaviorCamouflage(_config.behavior_config)
proxy_pool = get_proxy_pool() if _config.proxy_enabled else None
captcha_detector = CaptchaDetector() if _config.captcha_detection_enabled else None
def decorator(func: Callable):
@wraps(func)
async def async_wrapper(*args, **kwargs):
context = AntiDetectionContext()
# 1. 获取代理
if proxy_pool and proxy_pool.config.enabled:
context.proxy = proxy_pool.get_proxy()
# 2. 应用行为伪装延迟
if _config.behavior_camouflage_enabled:
delay = behavior.apply_delay()
context.delays_applied.append(delay)
# 3. 执行原始函数
try:
result = await func(*args, context=context, **kwargs)
# 报告代理成功
if context.proxy and proxy_pool:
proxy_pool.report_success(context.proxy.url)
return result
except Exception as e:
# 报告代理失败
if context.proxy and proxy_pool:
proxy_pool.report_failure(context.proxy.url)
raise
@wraps(func)
def sync_wrapper(*args, **kwargs):
context = AntiDetectionContext()
# 1. 获取代理
if proxy_pool and proxy_pool.config.enabled:
context.proxy = proxy_pool.get_proxy()
# 2. 应用行为伪装延迟
if _config.behavior_camouflage_enabled:
delay = behavior.apply_delay()
context.delays_applied.append(delay)
# 3. 执行原始函数
try:
result = func(*args, context=context, **kwargs)
if context.proxy and proxy_pool:
proxy_pool.report_success(context.proxy.url)
return result
except Exception as e:
if context.proxy and proxy_pool:
proxy_pool.report_failure(context.proxy.url)
raise
# 根据函数类型选择包装器
import asyncio
if asyncio.iscoroutinefunction(func):
return async_wrapper
return sync_wrapper
return decorator
```
---
## Integration Points
### Integration with ZhihuCrawler
**Modified File:** `kb_workflow/crawler/zhihu_crawler.py`
**Changes:**
```python
# In ZhihuCrawler.__init__:
from .anti_detection import AntiDetectionConfig, get_proxy_pool
def __init__(
self,
headless: bool = True,
timeout: int = 30,
config: Optional[CrawlerConfig] = None,
anti_detection_config: Optional[AntiDetectionConfig] = None
):
self.headless = headless
self.timeout = timeout
self.config = config or get_crawler_config()
self.anti_detection_config = anti_detection_config or AntiDetectionConfig()
# ... rest of init
# In _start_browser_with_proxy:
def _start_browser_with_proxy(self, cookie: str) -> None:
# Use proxy pool if enabled
if self.anti_detection_config.proxy_enabled:
proxy_pool = get_proxy_pool()
if proxy_pool.config.enabled:
proxy = proxy_pool.get_proxy()
if proxy:
self.config.proxy = ProxyConfig(
server=proxy.url,
username=proxy.username,
password=proxy.password
)
# ... rest of method
```
### Integration with ZhihuAdapter
**Modified File:** `kb_workflow/pipeline/platforms/zhihu.py`
**Changes:**
```python
from kb_workflow.crawler.anti_detection import (
with_anti_detection,
AntiDetectionContext,
AntiDetectionConfig
)
class ZhihuAdapter(PlatformAdapter):
def __init__(
self,
headless: bool = True,
timeout: int = 30,
output_dir: Optional[Path] = None,
idempotency_checker: Optional[IdempotencyChecker] = None,
anti_detection_config: Optional[AntiDetectionConfig] = None
):
# ... existing init
self.anti_detection_config = anti_detection_config or AntiDetectionConfig()
@with_anti_detection()
async def fetch_metadata(
self,
content_id: str,
content_type: str,
source_url: str = None,
context: AntiDetectionContext = None # 注入的上下文
) -> Dict[str, Any]:
# Use context.proxy if needed
# ...
```
### Integration with Configuration
**Modified File:** `kb_workflow/pipeline/config.py`
**Changes:**
```python
class KBSettings(BaseSettings):
# ... existing fields
# Anti-crawler settings
proxy_pool: str = Field(
default="",
description="代理池配置(逗号分隔的代理URL)"
)
anti_detection_enabled: bool = Field(
default=True,
description="是否启用反检测功能"
)
```
---
## Data Flow
### Request Flow with Anti-Detection
```
[CLI] process command
|
v
[Pipeline] DownloadProcessor.process()
|
v
[ZhihuAdapter] @with_anti_detection decorator
|
+-> [ProxyPool] get_proxy() -> returns available proxy
|
+-> [BehaviorCamouflage] apply_delay() -> wait random time
|
+-> [ZhihuCrawler] crawl_answer(url, proxy=...)
|
+-> [Playwright] start browser with proxy
|
+-> [CaptchaDetector] detect_from_html() -> check for captcha
| |
| +-> If detected -> raise CaptchaError
|
+-> Return CrawlResult
|
v
[ProxyPool] report_success() / report_failure()
|
v
[Pipeline] continue to next processor
```
### Error Recovery Flow
```
[Request fails]
|
v
[Retry Logic] retry_on_transient_error
|
+-> Is NetworkError? -> Retry with exponential backoff + jitter
|
+-> Is CaptchaError? -> Do NOT retry, surface to user
|
+-> Is AuthError? -> Do NOT retry, trigger QR login
|
v
[After max retries] -> Log error, update state, raise to caller
```
---
## Recommended Build Order
基于依赖关系,建议按以下顺序实现:
### Phase 1: Foundation (No external dependencies)
1. **`kb_workflow/crawler/behavior.py`** — 行为伪装模块
- 独立模块,无外部依赖
- 可独立测试
2. **`kb_workflow/crawler/captcha.py`** — 验证码检测模块
- 独立模块,只需要 HTML 解析
- 定义 CaptchaError 错误类型
### Phase 2: Integration
3. **`kb_workflow/crawler/proxy_pool.py`** — 代理池管理
- 依赖配置系统
- 需要与 CrawlerConfig 集成
4. **`kb_workflow/pipeline/retry.py` (enhance)** — 增强重试机制
- 添加 jitter 支持
- 添加验证码感知
### Phase 3: Unified API
5. **`kb_workflow/crawler/anti_detection.py`** — 统一装饰器
- 组合所有组件
- 提供 `@with_anti_detection` 装饰器
### Phase 4: Platform Integration
6. **`kb_workflow/crawler/zhihu_crawler.py` (modify)** — 集成到 ZhihuCrawler
7. **`kb_workflow/pipeline/platforms/zhihu.py` (modify)** — 集成到 ZhihuAdapter
8. **`kb_workflow/pipeline/config.py` (modify)** — 添加配置项
### Phase 5: Bilibili Support (if needed)
9. **`kb_workflow/pipeline/platforms/bilibili.py` (modify)** — B站反爬适配
---
## Anti-Patterns (v1.3 Specific)
### Anti-Pattern 1: Hardcoding Proxy Settings in Crawler
**What people do:** 在 Crawler 类中硬编码代理配置
```python
# BAD
class ZhihuCrawler:
def __init__(self):
self.proxy = "http://127.0.0.1:7890" # Hardcoded
```
**Why it's wrong:** 无法动态切换代理,无法支持代理池
**Do this instead:** 使用独立的 ProxyPoolManager
---
### Anti-Pattern 2: Retry All Errors Blindly
**What people do:** 对所有错误都进行重试
```python
# BAD
@retry(stop=stop_after_attempt(3))
def crawl():
...
```
**Why it's wrong:** 验证码错误重试会立即再次触发验证码,造成无限循环
**Do this instead:** 使用错误类型过滤
```python
# GOOD
@retry(retry=retry_if_exception_type(NetworkError))
def crawl():
...
```
---
### Anti-Pattern 3: Fixed Delay Intervals
**What people do:** 使用固定延迟
```python
# BAD
time.sleep(2) # Fixed 2 second delay
```
**Why it's wrong:** 固定延迟容易被模式检测识别
**Do this instead:** 使用随机延迟 + jitter
```python
# GOOD
delay = random.uniform(1, 3) + random.uniform(-0.5, 0.5)
time.sleep(delay)
```
---
### Anti-Pattern 4: Captcha Auto-Solve Without Fallback
**What people do:** 依赖第三方验证码解决服务,没有降级方案
**Why it's wrong:** 增加成本,服务不可用时无法工作
**Do this instead:** 检测 + 暂停 + 通知用户(本项目约束)
---
## Scaling Considerations (v1.3)
| Scale | Architecture Adjustments |
|-------|--------------------------|
| 个人使用(当前) | 单进程,内存代理池,本地配置 |
| 小规模(10-100 URL/天) | 添加代理持久化,简单的健康检查 |
| 中规模(100-1000 URL/天) | 考虑使用代理池服务 API,分布式状态存储 |
### Scaling Priorities
1. **First bottleneck:** 代理被封 — 通过代理池轮换解决
2. **Second bottleneck:** 账号被封 — 通过行为伪装和降低频率解决
3. **Third bottleneck:** 验证码频繁 — 考虑暂停机制 + 人工介入
---
## v1.3 Sources
- [ZenRows - How to Avoid Bot Detection with Playwright](https://www.zenrows.com/blog/avoid-playwright-bot-detection) - HIGH confidence
- [Zyte - Best Proxy Providers for Web Scraping 2026](https://www.zyte.com/learn/best-proxy-providers-for-web-scraping/) - HIGH confidence
- [Rebrowser - Python Requests Retry Guide](https://rebrowser.net/blog/python-requests-retry-the-ultimate-guide-to-handling-failed-http-requests-in-python) - HIGH confidence
- [GitHub - backoff Python library](https://github.com/litl/backoff) - HIGH confidence
- [Stack Overflow - How to rotate proxies in Python](https://stackoverflow.com/questions/55872164/how-to-rotate-proxies-on-a-python-requests) - HIGH confidence
- [Medium - Exponential Backoff Decorator](https://medium.com/@suryasekhar/implementing-exponential-backoff-decorator-in-python-26ddf783aea0) - MEDIUM confidence
- [Trickster.dev - Custom Scrapy Middleware for Proxy Pool](https://www.trickster.dev/post/writing-custom-scrapy-middleware-for-proxy-pool-integration/) - HIGH confidence
---
## Original Architecture Research (v1.0)
*Below is the original architecture research from v1.0, preserved for reference.*
---
## Standard Architecture
### System Overview
技能型工作流编排系统采用分层架构,将处理逻辑、状态管理和执行协调分离。基于当前研究和行业最佳实践,推荐以下四层架构:
```
+---------------------------------------------------------------+
| Orchestration Layer |
| [Workflow Coordinator] [State Manager] [Error Handler] |
+---------------------------------------------------------------+
| Skill Definition Layer |
| [SKILL.md specs] [Schema Definitions] [Trigger Rules] |
+---------------------------------------------------------------+
| Execution Layer |
| [Platform Skills] [Processing Skills] [Analysis Skills] |
+---------------------------------------------------------------+
| Infrastructure Layer |
| [File System] [External APIs] [AI Services] [State Store] |
+---------------------------------------------------------------+
```
### Component Responsibilities
| Component | Responsibility | Typical Implementation |
|-----------|----------------|------------------------|
| Workflow Coordinator | 协调多个技能的执行顺序、依赖管理 | kb-workflow 主控模块 |
| State Manager | 步骤级状态持久化、断点续传支持 | workflow_state.json + 原子写入 |
| Error Handler | 错误分类、降级策略、重试逻辑 | 统一错误处理模块 |
| Skill Registry | 技能发现、能力声明、Schema 注册 | SKILL.md + JSON Schema |
| Platform Skills | 平台特定内容获取(B站/知乎) | kb-bilibili-*, kb-zhihu-* |
| Processing Skills | 内容转换(语音识别/OCR) | kb-whisper, kb-funasr, kb-ocr |
| Analysis Skills | AI 深度分析、报告生成 | kb-deep-analyzer |
## Recommended Project Structure
```
kb-skills/
+-- skills/ # 技能模块目录
| +-- core/ # 核心基础设施
| | +-- state_manager.py # 状态管理
| | +-- schema_registry.py # Schema 注册
| | +-- pipeline.py # 管道执行引擎
| | +-- error_handler.py # 错误处理
| |
| +-- platforms/ # 平台技能
| | +-- bilibili/ # B站技能组
| | | +-- SKILL.md
| | | +-- metadata.py
| | | +-- content.py
| | +-- zhihu/ # 知乎技能组
| | +-- SKILL.md
| | +-- metadata.py
| | +-- content.py
| |
| +-- processing/ # 处理技能
| | +-- whisper/ # 语音识别
| | +-- funasr/ # 中文语音识别
| | +-- ocr/ # OCR 处理
| |
| +-- analysis/ # 分析技能
| +-- deep_analyzer/ # 深度分析
|
+-- schemas/ # JSON Schema 定义
| +-- workflow_state.schema.json
| +-- skill_output.schema.json
| +-- platform_metadata.schema.json
|
+-- workflows/ # 工作流定义
| +-- video_pipeline.yaml # 视频处理流程
| +-- article_pipeline.yaml # 文章处理流程
|
+-- cli.py # 统一 CLI 入口
+-- config.py # 配置管理
```
### Structure Rationale
- **skills/core/:** 核心基础设施,所有技能共享的状态管理、Schema 验证和管道执行
- **skills/platforms/:** 按平台组织,每个平台有自己的元数据提取和内容下载技能
- **skills/processing/:** 内容转换技能,独立于平台
- **skills/analysis/:** AI 分析技能,作为管道的最后阶段
- **schemas/:** 统一的 JSON Schema 定义,确保接口一致性
- **workflows/:** 声明式工作流定义,指定技能组合和执行顺序
## Architectural Patterns
### Pattern 1: Pipeline Orchestration (管道编排)
**What:** 将处理流程建模为一系列串联的步骤,每个步骤接收输入、执行处理、产生输出。
**When to use:** 可预测的、重复性的任务,如内容处理管道。
**Trade-offs:**
- 优点:确定性执行、易于调试、支持断点续传
- 缺点:灵活性有限,不适合需要动态决策的场景
**Example:**
```python
# Pipeline definition
class ContentPipeline:
steps = [
("parse", LinkParserSkill),
("metadata", MetadataSkill), # depends on parse
("download", ContentSkill), # depends on metadata
("transcribe", TranscribeSkill), # conditional: only for video
("analyze", AnalyzeSkill), # depends on download + transcribe
]
def run(self, url: str, state: WorkflowState):
for step_name, skill in self.steps:
if state.is_completed(step_name):
continue # Skip completed steps
result = skill.execute(state.get_step_input(step_name))
state.mark_completed(step_name, result)
if not result.success:
return self.handle_error(result.error)
```
### Pattern 2: Step-Level State Management (步骤级状态管理)
**What:** 每个主要步骤完成后立即持久化状态,支持从任意断点恢复执行。
**When to use:** 长时间运行的批处理任务,可能因网络、API 限制等原因中断。
**Trade-offs:**
- 优点:断点续传、执行追踪、幂等性保证
- 缺点:状态管理开销、需要设计状态 Schema
**Example:**
```python
# workflow_state.json
{
"url": "https://www.bilibili.com/video/BV1xx411c7mD",
"platform": "bilibili",
"content_type": "video",
"created_at": "2026-03-13T10:00:00Z",
"updated_at": "2026-03-13T10:15:00Z",
"steps": {
"parse": {
"status": "completed",
"output": {"video_id": "BV1xx411c7mD"},
"completed_at": "2026-03-13T10:00:05Z"
},
"metadata": {
"status": "completed",
"output": {"title": "...", "duration": 1800},
"completed_at": "2026-03-13T10:00:30Z"
},
"download": {
"status": "in_progress",
"output": null,
"started_at": "2026-03-13T10:01:00Z"
}
}
}
```
### Pattern 3: Schema-First Interface Design (Schema 优先接口设计)
**What:** 先定义输入输出的 JSON Schema,再实现技能逻辑。所有技能遵守统一的接口规范。
**When to use:** 多人协作、需要接口稳定性和可测试性的项目。
**Trade-offs:**
- 优点:接口清晰、易于测试、支持自动验证
- 缺点:前期设计成本、Schema 维护负担
**Example:**
```json
// schemas/skill_output.schema.json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["success", "data"],
"properties": {
"success": {"type": "boolean"},
"data": {"type": "object"},
"error": {
"type": "object",
"properties": {
"code": {"type": "string"},
"message": {"type": "string"},
"detail": {"type": "string"}
}
},
"metadata": {
"type": "object",
"properties": {
"duration_ms": {"type": "number"},
"tokens_used": {"type": "number"}
}
}
}
}
```
### Pattern 4: Idempotent Execution (幂等执行)
**What:** 相同输入重复执行产生相同结果,不会产生副作用(如重复文件)。
**When to use:** 任何可能需要重试的处理步骤。
**Trade-offs:**
- 优点:安全重试、简化错误恢复
- 缺点:需要设计去重逻辑
**Example:**
```python
class ContentSkill:
def execute(self, input: dict) -> SkillOutput:
# Generate deterministic filename from input
content_hash = hashlib.sha256(input['url'].encode()).hexdigest()[:12]
output_path = self.get_output_dir() / f"{content_hash}.mp4"
# Check if already exists
if output_path.exists():
return SkillOutput(success=True, data={"path": str(output_path), "cached": True})
# Download content
self.download_content(input['url'], output_path)
return SkillOutput(success=True, data={"path": str(output_path), "cached": False})
```
## Data Flow
### Content Processing Pipeline
```
[URL Input]
|
v
+-------------+ +----------------+
| Link Parser | --> | Platform/Type |
+-------------+ | Detection |
+----------------+
|
+---------------+---------------+
| |
v v
+-------------+ +-------------+
| Bilibili | | Zhihu |
| Pipeline | | Pipeline |
+-------------+ +-------------+
| |
+------+------+ +-------+-------+
| | | |
v v v v
[Metadata] [Download] [Metadata] [Download]
| | | |
+------+------+ +-------+-------+
| |
v v
+-------------+ +-------------+
| Video? | | Article |
| - Whisper | | - Direct |
| - FunASR | | Extract |
| - OCR | +-------------+
+-------------+ |
| |
+---------------+--------------+
|
v
+-----------------+
| Deep Analyzer |
| (AI Analysis) |
+-----------------+
|
v
+-----------------+
| Archive to |
| Knowledge Base |
+-----------------+
```
### State Management Flow
```
[Workflow Start]
|
v
+------------------+
| Load/Init State | <-- workflow_state.json
+------------------+
|
v
+------------------+ +------------------+
| For Each Step | --> | Check Status |
+------------------+ | - completed? skip|
| | - failed? retry |
v | - pending? run |
+------------------+ +------------------+
| Execute Step | |
+------------------+ v
| +------------------+
v | Mark Completed |
+------------------+ | Save State |
| Validate Output | -->| (atomic write) |
+------------------+ +------------------+
|
v
[Next Step / Complete]
```
### Key Data Flows
1. **URL to Metadata:** URL -> Link Parser -> (platform, type, id) -> Platform Metadata Skill -> Metadata JSON
2. **Metadata to Content:** Metadata JSON -> Content Skill -> (video file / article text)
3. **Content to Transcript:** Video File -> Transcribe Skill (Whisper/FunASR) -> Transcript JSON
4. **Content + Transcript to Analysis:** Content + Transcript -> Deep Analyzer -> Analysis Report
## Scaling Considerations
| Scale | Architecture Adjustments |
|-------|--------------------------|
| 1-100 items/day | 当前架构足够,单进程执行 |
| 100-1000 items/day | 添加任务队列,并行处理独立任务 |
| 1000+ items/day | 分布式任务调度,API 请求限流,缓存层 |
### Scaling Priorities
1. **First bottleneck:** 外部 API 限流(B站/知乎)。解决方案:请求队列 + 指数退避重试
2. **Second bottleneck:** AI 分析耗时。解决方案:批处理 + 并行分析
## Anti-Patterns
### Anti-Pattern 1: Global State Mutation
**What people do:** 在多个技能间共享可变全局状态
**Why it's wrong:** 难以追踪状态变化、难以测试、难以恢复
**Do this instead:** 每个步骤从 state 读取输入,将输出写回 state,状态变更通过 state manager 集中管理
### Anti-Pattern 2: Implicit Dependencies
**What people do:** 技能之间通过隐式约定传递数据(如固定文件路径)
**Why it's wrong:** 脆弱的耦合,修改一个技能可能破坏其他技能
**Do this instead:** 在 workflow_state.json 中显式声明依赖关系,通过 state manager 传递数据
### Anti-Pattern 3: Silent Failure
**What people do:** 捕获异常后只打印日志,继续执行
**Why it's wrong:** 下游步骤收到无效输入,产生难以调试的错误
**Do this instead:** 明确的失败处理策略 - 要么终止并记录,要么降级并提供默认值
### Anti-Pattern 4: Non-Idempotent Operations
**What people do:** 每次执行都创建新文件(如 `output_1.mp4`, `output_2.mp4`)
**Why it's wrong:** 重试产生垃圾文件,磁盘占用增长
**Do this instead:** 基于输入生成确定性文件名,已存在则跳过
## Integration Points
### External Services
| Service | Integration Pattern | Notes |
|---------|---------------------|-------|
| Bilibili API | HTTP + Cookie Auth | 需要 BILIBILI_COOKIE 环境变量 |
| Zhihu API | HTTP + Cookie Auth | 需要 ZHIHU_COOKIE 环境变量 |
| AI Provider | OpenAI-compatible API | 需要 AI_API_KEY 环境变量 |
| HuggingFace | API + Local Cache | 需要 HF_TOKEN 环境变量 |
| Git | Command Line | 需要 KB_GIT_USER/EMAIL 配置 |
### Internal Boundaries
| Boundary | Communication | Notes |
|----------|---------------|-------|
| Orchestrator <-> Skills | JSON via stdin/stdout | 标准化输入输出格式 |
| Skills <-> State Manager | File I/O | workflow_state.json |
| Skills <-> External APIs | HTTP | 统一错误处理和重试 |
## Build Order Implications
基于组件依赖关系,建议的构建顺序:
### Phase 1: Foundation (基础设施)
1. **State Manager** - 所有后续组件的基础
2. **Schema Registry** - 定义统一的输入输出格式
3. **Error Handler** - 统一错误处理模式
### Phase 2: Core Skills (核心技能)
4. **Link Parser** - 入口点,无依赖
5. **Platform Metadata Skills** - 依赖 Link Parser 输出
6. **Platform Content Skills** - 依赖 Metadata 输出
### Phase 3: Processing Skills (处理技能)
7. **Whisper/FunASR** - 依赖视频文件
8. **OCR Processor** - 依赖视频截图
### Phase 4: Analysis Skills (分析技能)
9. **Deep Analyzer** - 依赖内容和转录文本
### Phase 5: Orchestration (编排层)
10. **Pipeline Engine** - 组合所有技能
11. **Workflow Definitions** - 声明式流程定义
## Sources
- [AI Architecture Patterns 101: Workflows, Agents, MCPs, and A2A Systems](https://aipmguru.substack.com/p/ai-architecture-patterns-101-workflows) - HIGH confidence, comprehensive pattern overview
- [Data Pipeline Architecture: 5 Design Patterns with Examples - Dagster](https://dagster.io/guides/data-pipeline-architecture-5-design-patterns-with-examples) - HIGH confidence, pipeline patterns
- [Agentic Workflows in 2026: The ultimate guide - Vellum](https://www.vellum.ai/blog/agentic-workflows-emerging-architectures-and-design-patterns) - HIGH confidence, agent architecture patterns
- [State of Open Source Workflow Orchestration Systems 2025](https://www.pracdata.io/p/state-of-workflow-orchestration-ecosystem-2025) - MEDIUM confidence, ecosystem overview
- [ML Pipeline Architecture Design Patterns - Neptune.ai](https://neptune.ai/blog/ml-pipeline-architecture-design-patterns) - MEDIUM confidence, ML-specific patterns
---
*Architecture research for: Skill-based Workflow Orchestration System*
*Original research: 2026-03-13*
*Updated for v1.3 Anti-Crawler: 2026-03-18*
---
## v1.6 语义分段架构研究
**Milestone:** LLM-based Semantic Segmentation
**Researched:** 2026-03-21
**Focus:** 使用 LLM 替代基于规则的语义分段,实现智能章节识别和标题生成
---
### Executive Summary
本文档研究如何将 LLM 语义分段集成到现有的 SRT 处理架构中。基于对现有代码(`kb_workflow/srt/` 模块和 `SRTProcessor`)的分析,推荐采用 **渐进式迁移模式**,保留现有 RuleBasedSegmenter 作为 fallback,新增 LLMBasedSegmenter 作为主分段器。
关键架构决策:
1. **新增 SemanticSegmenter 组件** — 替代/增强现有的 `segmenter.py`
2. **单次 LLM 调用策略** — 完整上下文分析,避免边界拼接问题
3. **三层降级策略** — LLM 失败 → 响应解析失败 → AI 不可用 → RuleBasedSegmenter
4. **渐进式集成** — 不破坏现有功能,新组件作为可选增强
---
### Current Architecture (v1.5)
```
┌─────────────────────────────────────────────────────────────┐
│ SRTProcessor │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │
│ │ parser.py │→ │segmenter.py │→ │corrector.py │ │
│ │ (SRT解析) │ │ (规则分段) │ │ (ASR纠错) │ │
│ └─────────────┘ └──────────────┘ └─────────────┘ │
│ │ │ │ │
│ ↓ ↓ ↓ │
│ List[Segment] List[str] List[str] │
│ (时间戳+文本) (段落) (纠错后) │
└─────────────────────────────────────────────────────────────┘
```
**现有组件:**
- `parser.py`: 解析 SRT → Segment(id, start_time, end_time, text)
- `segmenter.py`: SemanticSegmenter (基于时间间隔3s + 长度500字符规则)
- `corrector.py`: ASRCorrector (使用 SimpleAIClient 纠错)
---
### Recommended Architecture (v1.6)
```
┌─────────────────────────────────────────────────────────────────┐
│ SRTProcessor │
│ ┌─────────────┐ │
│ │ parser.py │ │
│ │ (SRT解析) │ │
│ └──────┬──────┘ │
│ │ │
│ ↓ List[Segment] (id, start_time, end_time, text) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ semantic_segmenter.py (NEW) │ │
│ │ ┌────────────────────────────────────────────────────┐ │ │
│ │ │ 1. 构建 Prompt (视频信息 + 字幕内容) │ │ │
│ │ └──────────────────┬─────────────────────────────────┘ │ │
│ │ ↓ │ │
│ │ ┌────────────────────────────────────────────────────┐ │ │
│ │ │ 2. 调用 LLM (SimpleAIClient.generate) │ │ │
│ │ └──────────────────┬─────────────────────────────────┘ │ │
│ │ ↓ │ │
│ │ ┌────────────────────────────────────────────────────┐ │ │
│ │ │ 3. 解析响应 (## 标题 + 内容) │ │ │
│ │ └──────────────────┬─────────────────────────────────┘ │ │
│ │ ↓ │ │
│ │ ┌────────────────────────────────────────────────────┐ │ │
│ │ │ 4. 返回 List[SemanticParagraph] │ │ │
│ │ │ - title: str (章节标题) │ │ │
│ │ │ - content: str (段落内容) │ │ │
│ │ │ - time_range: (start, end) (可选) │ │ │
│ │ └──────────────────┬─────────────────────────────────┘ │ │
│ └─────────────────────┼─────────────────────────────────────┘ │
│ ↓ │
│ List[SemanticParagraph] │
│ (标题 + 结构化内容) │
│ ↓ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Fallback Strategy │ │
│ │ LLM 失败 → 响应解析失败 → AI 不可用 │ │
│ │ ↓ │ │
│ │ segmenter.py (RuleBasedSegmenter) - 保留作为 fallback │ │
│ └────────────────────┬─────────────────────────────────────┘ │
│ ↓ │
│ List[str] (向后兼容) │
│ ↓ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │corrector.py │ │ Markdown生成 │ │
│ │ (ASR纠错) │ │ (标题+内容) │ │
│ └─────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
---
### Component Boundaries
| Component | Responsibility | Communicates With |
|-----------|---------------|-------------------|
| **parser.py** | 解析 SRT 格式 → Segment 对象 | 无(输入层) |
| **semantic_segmenter.py** (NEW) | LLM 语义分析 → 结构化段落 | parser (输入), corrector (输出), SimpleAIClient |
| **segmenter.py** (EXIST) | 基于规则的分段(fallback) | semantic_segmenter (降级) |
| **corrector.py** | ASR 纠错 | semantic_segmenter (输入), SimpleAIClient |
| **SRTProcessor** | 编排处理流程 | 所有模块 |
---
### Data Flow Changes
**旧流程 (v1.5):**
```
SRT File → parser.parse_srt_file() → List[Segment]
↓
segmenter.segment() (基于规则)
↓
List[str] (段落)
↓
corrector.correct() (逐段落纠错)
↓
生成 Markdown
```
**新流程 (v1.6):**
```
SRT File → parser.parse_srt_file() → List[Segment]
↓
[NEW] semantic_segmenter.segment()
├─ 构建 Prompt (视频信息 + 字幕文本)
├─ 调用 LLM (SimpleAIClient)
├─ 解析响应 (## 标题 + 内容)
└─ 返回 List[SemanticParagraph]
├─ title: str
├─ content: str
└─ time_range: (float, float) (可选)
↓
[FALLBACK] segmenter.segment() (LLM 失败时)
↓
List[SemanticParagraph] → flatten → List[str]
↓
corrector.correct() (逐段落纠错)
↓
生成 Markdown (标题 + 内容结构)
```
---
### Response Parsing Strategy
**LLM 响应格式要求:**
```
## 第一部分:机器学习基础概念
段落内容1...
段落内容2...
## 第二部分:深度学习原理
段落内容3...
段落内容4...
```
**解析方法:**
```python
@dataclass
class SemanticParagraph:
"""语义分段结果"""
title: str # 章节标题
content: str # 段落内容
start_time: float # 开始时间(可选)
end_time: float # 结束时间(可选)
def parse_llm_response(response: str) -> List[SemanticParagraph]:
"""解析 LLM 响应为结构化段落"""
paragraphs = []
current_title = "无标题"
current_content = []
for line in response.split('\n'):
if line.startswith('##'):
# 保存上一个段落
if current_content:
content = '\n'.join(current_content).strip()
if content:
paragraphs.append(SemanticParagraph(
title=current_title,
content=content
))
# 开始新段落
current_title = line.lstrip('#').strip()
current_content = []
else:
current_content.append(line)
# 保存最后一个段落
if current_content:
content = '\n'.join(current_content).strip()
if content:
paragraphs.append(SemanticParagraph(
title=current_title,
content=content
))
return paragraphs
```
---
### LLM Call Strategy
**推荐:单次调用**
**原因:**
1. **上下文完整性** — LLM 理解完整视频内容,分段更准确
2. **避免边界问题** — 多次调用的拼接处容易出现语义不连贯
3. **简化实现** — 单次调用更容易测试和调试
**Prompt 设计:**
```python
SEGMENTATION_PROMPT = """你是一个视频内容分析师。请将以下字幕内容按语义分段,并为每个段落生成标题。
【视频信息】
标题:{video_title}
时长:{duration}
【字幕内容】
{concatenated_segments}
【输出要求】
1. 按语义完整性分段(保持话题/论点完整)
2. 识别主题变化边界
3. 为每个段落生成简洁标题(不超过20字)
4. 输出格式:
## 第一部分:{标题}
{段落内容}
## 第二部分:{标题}
{段落内容}
...(以此类推)
注意:
- 段落长度建议 200-800 字符
- 在自然句号处分段
- 标题要概括该段核心内容
"""
```
**输入处理选项:**
**选项 1: 纯文本(推荐)**
```python
# 合并所有 Segment.text
full_text = '\n'.join(seg.text for seg in segments)
```
**选项 2: 带时间戳标记(可选,用于辅助理解)**
```python
# 添加时间戳辅助 LLM 理解结构
marked_text = '\n'.join(
f"[{format_time(seg.start_time)}] {seg.text}"
for seg in segments
)
```
**替代方案:流式调用**
- 用于超长视频(>1小时字幕)
- 分块调用 LLM,每块 10-15 分钟字幕
- 需要额外的边界拼接逻辑
- **建议:** 初期使用单次调用,后续根据实际需求优化
---
### Fallback Strategy
**三层降级策略:**
```python
class LLMBasedSegmenter:
def __init__(self, ai_client: SimpleAIClient):
self.ai_client = ai_client
self.rule_based = SemanticSegmenter() # Fallback
def segment(self, segments: List[Segment]) -> List[SemanticParagraph]:
# Level 3: AI 客户端不可用
if not self.ai_client or not self.ai_client.api_key:
logger.info("AI client not available, using rule-based")
return self._to_semantic_paragraphs(
self.rule_based.segment(segments)
)
try:
# Level 1: LLM 调用
prompt = self._build_prompt(segments)
response = self.ai_client.generate(prompt)
# Level 2: 响应解析
try:
paragraphs = self._parse_llm_response(response)
if not paragraphs:
raise ValueError("Empty parsed result")
return paragraphs
except Exception as e:
logger.warning(f"Failed to parse LLM response: {e}")
return self._to_semantic_paragraphs(
self.rule_based.segment(segments)
)
except Exception as e:
logger.warning(f"LLM call failed: {e}, using rule-based")
return self._to_semantic_paragraphs(
self.rule_based.segment(segments)
)
```
---
### Suggested Build Order
**Phase 1: 基础组件(不影响现有功能)**
1. 创建 `kb_workflow/srt/semantic_segmenter.py`
2. 实现 `SemanticParagraph` 数据类
3. 实现 `parse_llm_response()` 方法(独立于 LLM 调用)
4. 编写单元测试(mock LLM 响应,测试解析逻辑)
**Phase 2: LLM 集成**
5. 实现 `_build_prompt()` 方法
6. 集成 SimpleAIClient
7. 实现 `segment()` 主方法
8. 实现降级策略(fallback)
9. 编写集成测试(使用真实 LLM 或 mock)
**Phase 3: 处理器集成**
10. 修改 `SRTProcessor.__init__()`:
- 添加 `use_llm_segmenter: bool = False` 参数
- 条件性初始化 `llm_segmenter`
11. 修改 `SRTProcessor.process()`:
- 调用新分段器或保留旧分段器
- 处理 `SemanticParagraph` 输出
12. 更新 `_generate_markdown()`:
- 处理标题和内容结构
- 生成带层级的 Markdown
13. 保留 RuleBasedSegmenter 作为 fallback
**Phase 4: 输出优化**
14. 优化 Markdown 格式(标题层级 `###`)
15. 添加时间戳(可选,在段落中标注时间范围)
16. 更新文档和测试
---
### Schema Changes
**新增数据类:**
```python
@dataclass
class SemanticParagraph:
"""语义分段结果"""
title: str # 章节标题
content: str # 段落内容
start_time: Optional[float] = None # 开始时间(可选)
end_time: Optional[float] = None # 结束时间(可选)
```
**修改 SRTOutput (向后兼容):**
```python
class SRTOutput(BaseModel):
success: bool
output_file: str
paragraphs: List[str] # 保留向后兼容
semantic_paragraphs: Optional[List[SemanticParagraph]] = None # 新增
tags: List[str]
corrected: bool
```
---
### Patterns to Follow
#### Pattern 1: 渐进式迁移
**What:** 保留现有组件,新组件作为可选增强
**When:** 重构关键路径时
**Example:**
```python
class SRTProcessor:
def __init__(self, use_llm_segmenter: bool = False):
self.rule_based_segmenter = SemanticSegmenter() # 保留
if use_llm_segmenter:
self.llm_segmenter = LLMBasedSegmenter() # 新增
async def process(self, input_data):
# 优先使用 LLM,失败时降级
if hasattr(self, 'llm_segmenter'):
try:
paragraphs = self.llm_segmenter.segment(segments)
except Exception:
paragraphs = self.rule_based_segmenter.segment(segments)
else:
paragraphs = self.rule_based_segmenter.segment(segments)
```
#### Pattern 2: 协议兼容性
**What:** 新旧组件共享相同接口
**When:** 替换组件时
**Example:**
```python
# 两个分段器实现相同接口
class Segmenter(Protocol):
def segment(self, segments: List[Segment]) -> List[str]:
...
class RuleBasedSegmenter:
def segment(self, segments: List[Segment]) -> List[str]:
# 基于规则
...
class LLMBasedSegmenter:
def segment(self, segments: List[Segment]) -> List[str]:
# 基于 LLM
# 内部使用 SemanticParagraph,输出时合并标题和内容
return [
f"## {p.title}\n\n{p.content}"
for p in self._segment_internal(segments)
]
```
---
### Anti-Patterns to Avoid
#### Anti-Pattern 1: 破坏性替换
**What:** 直接删除 RuleBasedSegmenter
**Why bad:** LLM 可能失败,失去降级选项
**Instead:** 保留旧组件作为 fallback
#### Anti-Pattern 2: 紧耦合 Prompt 逻辑
**What:** 将 prompt 构建硬编码在 segmenter 中
**Why bad:** 难以测试和优化
**Instead:** 提取为独立方法或模板,便于测试
#### Anti-Pattern 3: 忽略时间戳信息
**What:** 仅传递文本给 LLM,忽略时间信息
**Why bad:** LLM 无法理解视频结构(停顿、章节)
**Instead:** 在 prompt 中包含时间戳标记或视频时长信息
---
### Scalability Considerations
| Concern | At 100 videos | At 10K videos | At 1M videos |
|---------|--------------|--------------|-------------|
| **LLM 调用延迟** | 可接受(~10-30s/视频) | 需要并发处理 | 需要队列+批处理 |
| **LLM 成本** | 可忽略 | 需要监控 | 需要缓存+降级策略 |
| **错误率** | 低,手动处理 | 需要自动重试 | 需要监控告警 |
| **响应解析** | 简单 | 需要容错增强 | 需要多模型支持 |
**建议:**
- 初期:单线程处理,完整错误日志
- 中期:添加并发处理(asyncio.Semaphore 限制并发数)
- 长期:分布式队列(Celery/RQ)+ 结果缓存
---
### Integration Points Summary
**新增集成点:**
1. `SRTProcessor.__init__()` → 条件性初始化 `LLMBasedSegmenter`
2. `SRTProcessor.process()` → 调用新分段器或旧分段器
3. `_generate_markdown()` → 处理 `SemanticParagraph` 结构
**保留集成点:**
1. `parser.parse_srt_file()` — 不变
2. `corrector.correct()` — 不变(处理 `List[str]`)
3. `WorkflowStateManager` — 不变
**修改点:**
1. `kb_workflow/srt/__init__.py` — 导出 `SemanticParagraph`
2. `kb_workflow/pipeline/schemas.py` — 更新 `SRTOutput`
---
### v1.6 Sources
- 现有架构分析: `kb_workflow/srt/` 模块
- SRTProcessor 实现: `kb_workflow/pipeline/processors/srt.py`
- SimpleAIClient 实现: `kb_workflow/deep_analyzer/__init__.py`
- 测试用例: `tests/test_srt_processor.py`
- 项目上下文: `.planning/PROJECT.md`
---
*Architecture research for: LLM-based Semantic Segmentation*
*Research date: 2026-03-21*