2561de6ad7
PR Test / lint (pull_request) Successful in 2s
- Add architecture research for proxy pool, behavior camouflage, captcha detection, and error recovery - Update features, pitfalls, and stack documentation - Add phase 10 validation checklist Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1440 lines
50 KiB
Markdown
1440 lines
50 KiB
Markdown
# 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*
|