# DevelopmentPlan.md:小红书 / 抖音热榜评论抓取 + AI 分析报告工具
## 1. 文档信息
- 文档阶段:DevelopmentPlan(技术方案与开发计划)
- 需求来源:`docs/RequirementsDoc.md`、`docs/PRD.md`、`docs/FeatureSummary.md`
- API Spike 依据:`docs/API-Spike-Xiaohongshu.md`、`docs/API-Spike-Douyin.md`
- 项目类型:学习型小工具 / 全栈流程演示项目
- MVP 周期:4 天单人开发
- 当前版本目标:锁定 MVP 技术选型、系统架构、数据模型、接口契约、任务流程、AI 方案、开发排期与验收方式
---
## 2. 核心技术决策
### 2.1 总体技术栈
MVP 采用轻量单体架构,优先保证 4 天内跑通完整链路。
| 层级 | 技术选型 | 决策理由 |
|---|---|---|
| 后端框架 | Python 3.12 + FastAPI | 上手快,适合 API、模板页面、后台任务与导出接口统一实现 |
| 数据库 | SQLite | 单人 MVP、Docker Compose 本机部署足够;无须额外数据库服务 |
| ORM / 数据访问 | SQLAlchemy 2.x | 明确数据模型,便于后续迁移 PostgreSQL |
| 数据校验 | Pydantic | 与 FastAPI 生态一致,适合请求、配置、AI 输出 schema 校验 |
| HTTP 客户端 | httpx | MVP 统一使用同步 `httpx.Client`,支持超时、重试封装和后续异步演进 |
| 前端 | FastAPI Jinja2 模板 + 原生 JavaScript | 页面数量有限,避免引入 React/Vite 构建复杂度 |
| 样式 | 简单 CSS | MVP 以可用和清晰为主,不做复杂视觉系统 |
| 后台任务 | 进程内 ThreadPoolExecutor | 满足手动任务与低并发演示;不引入 Redis / Celery |
| AI 服务 | OpenAI-compatible 结构化输出接口 | 通过 JSON Schema 降低解析不稳定性,并保留替换模型供应商空间 |
| 部署 | Docker Compose | 符合 PRD 要求,一键启动 Web 服务和持久化 SQLite 数据 |
#### 并发与数据库约束
- 任务执行线程池固定为 `ThreadPoolExecutor(max_workers=1)`,确保同一时间只有一个任务在执行,从根本上避免 SQLite 写入冲突。
- SQLAlchemy 引擎初始化时须启用以下配置:
```python
from sqlalchemy import create_engine, event
engine = create_engine(
"sqlite:///data/app.db",
connect_args={"check_same_thread": False, "timeout": 10},
)
@event.listens_for(engine, "connect")
def set_sqlite_pragma(dbapi_connection, connection_record):
cursor = dbapi_connection.cursor()
cursor.execute("PRAGMA journal_mode=WAL")
cursor.close()
```
- 若未来需要支持多任务并行执行,应将其作为迁移 PostgreSQL 的触发条件。
#### httpx 使用模式(MVP)
- 后台任务线程(ThreadPoolExecutor)中的所有外部 API 调用(TikHub、AI 服务)使用 `httpx.Client`(同步模式)。
- FastAPI 路由层统一使用 `def`(同步端点),由 FastAPI 自动将其分配到线程池执行,避免阻塞 ASGI 事件循环。
- MVP 阶段**不使用** `async def` 路由 + `httpx.AsyncClient`,降低并发模型复杂度。
- 后续如需异步演进,将路由改为 `async def` 并替换为 `httpx.AsyncClient` 即可,httpx API 兼容。
#### 可选:HTMX 辅助局部刷新
- 可通过 CDN 引入 [HTMX](https://htmx.org/)(``),用声明式属性替代手写 fetch + DOM 操作。
- 示例:任务列表轮询只需 `
`。
- 此项为可选优化,不引入不影响功能完整性,但可在 Day 4 节省约 1-2 小时前端开发时间。
#### 数据库初始化
- MVP 使用 `SQLAlchemy Base.metadata.create_all(engine)` 在应用首次启动时自动创建表结构。
- 不引入 Alembic 迁移工具。
- 开发阶段如需变更数据模型,直接删除 SQLite 文件后重启应用即可重建。
### 2.2 不采用的技术
- 不使用 Celery / Redis:MVP 不做复杂任务队列、分布式调度、任务恢复。
- 不使用 PostgreSQL:当前数据量小,SQLite 更省部署成本。
- 不使用 WebSocket:任务状态通过手动刷新按钮兜底,P1 可增加简单轮询。
- 不使用前后端分离 SPA:页面复杂度不高,模板页面更利于 4 天交付。
- 不做登录鉴权:MVP 默认内部环境使用,不面向公网。
### 2.3 AI 服务选型
AI 层采用 OpenAI-compatible 接口,首选支持 Structured Outputs / JSON Schema 的模型服务。
技术要求:
- 支持通过环境变量配置:
- `AI_BASE_URL`
- `AI_API_KEY`
- `AI_MODEL`
- `AI_PROVIDER`
- 评论分析请求必须要求模型输出严格 JSON Array。
- 后端必须使用 Pydantic / JSON Schema 做二次校验。
- 单批评论建议 20 条。
- AI 请求并发数上限为 2,硬上限不超过 3。
- 单次 AI 请求超时时间为 30s。
- AI 输出解析失败时最多重试 3 次。
参考依据:
- OpenAI Structured Outputs 官方文档说明,结构化输出可通过 JSON Schema 约束模型响应,并比普通 JSON mode 更强调 schema adherence。
- 文档链接:`https://developers.openai.com/api/docs/guides/structured-outputs`
---
## 3. MVP 范围
### 3.1 P0 必须实现
1. Docker Compose 启动系统,并能通过浏览器访问。
2. 首页 / 任务列表支持选择平台并手动创建任务。
3. 任务创建页面支持抓取规模配置:
- 热点关键词数量上限:默认 5,范围 1–10;
- 每热点内容条目数上限:默认 5,范围 1–10;
- 每内容条目评论数上限:默认 50,范围 10–100。
4. 小红书链路:
- 热榜;
- 热榜标题搜索笔记;
- 笔记一级评论。
5. 抖音链路:
- 创作者热点榜单;
- 热点标题搜索视频;
- 视频一级评论。
6. 评论分页抓取,默认最多 50 条,配置最多 100 条,最大翻页轮次 5。
7. 数据入库并保留原始 API 响应。
8. AI 评论级结构化分析。
9. 预生成内容条目级报告和热点级报告。
10. 页面查看任务、热点、内容条目、报告和评论明细。
11. 导出:
- CSV 评论明细;
- Markdown 热点级报告;
- Markdown 内容条目级报告。
12. 基础容错:
- 单条内容条目失败不阻断整批任务;
- HTTP 429 指数退避;
- AI 输出解析失败重试。
### 3.2 P1 建议实现
- 任务列表自动轮询。
- 基础进度展示:
- `processed_items_count / total_items_count`
- `successful_items_count / total_items_count`
- 内容条目详情页开发调试 JSON 入口。
### 3.3 P2 明确不做
- 定时任务。
- 多用户 / 登录 / 权限。
- 分布式任务队列。
- 复杂任务恢复、自动补跑、单条重试按钮。
- 二级评论抓取。
- Top 50 以上热点或单内容 200 条以上评论。
- 平台级日报、跨热点深度洞察。
- Excel 导出、正式 JSON 导出。
- 任务取消 / 中断:用户主动终止正在运行的任务。
- 历史数据自动清理:基于时间策略自动删除过期任务数据。
- 搜索笔记分页:搜索接口翻页获取更多候选内容条目。
- 热点级 CSV 导出的高级格式定制。
---
## 4. 系统架构
### 4.1 架构形态
```text
Browser
↓
FastAPI Web App
├─ HTML Templates / Static Assets
├─ REST Endpoints
├─ Task Service
├─ Platform Crawlers
│ ├─ Xiaohongshu Client
│ └─ Douyin Client
├─ AI Analysis Service
├─ Report Service
├─ Export Service
└─ SQLite Database
```
### 4.2 推荐目录结构
```text
app/
main.py
config.py
db.py
models.py
schemas.py
services/
task_service.py
crawl_service.py
ai_service.py
report_service.py
export_service.py
platforms/
base.py
xiaohongshu.py
douyin.py
templates/
tasks.html
task_detail.html
hotspot_report.html
item_detail.html
static/
app.css
app.js
tests/
test_config.py
test_models.py
test_platform_mapping.py
test_ai_schema.py
test_report_stats.py
test_export.py
Dockerfile
docker-compose.yml
.env.example
```
### 4.3 模块边界
- `platforms/*`:只负责调用外部平台 API、字段映射、分页、保留 raw_data。
- `task_service.py`:负责任务生命周期、进度字段、错误聚合和后台执行。
- `ai_service.py`:负责 prompt、批处理、JSON Schema 校验、重试、AI 成功率统计。
- `report_service.py`:负责结构化统计、典型评论选取、AI 简短总结、报告预生成。
- `export_service.py`:负责 Markdown / CSV 文件内容与响应头。
- `models.py`:统一定义任务、热点、内容条目、评论、报告表。
- `templates/*`:只做展示,不承载业务计算。
#### 僵尸任务恢复
- 在 FastAPI `lifespan` 启动事件中,执行以下逻辑:
```python
# main.py lifespan 启动阶段
UPDATE tasks SET status = 'failed',
error_stage = 'system',
error_type = 'unexpected_restart',
error_message = '系统重启,任务被中断'
WHERE status = 'running'
```
- 目的:Docker 容器重启或进程 Crash 后,避免任务永久停留在 `running` 状态(僵尸任务)。
- 该逻辑在应用启动时自动执行一次,无需用户干预。
---
## 5. 数据模型设计
### 5.1 tasks
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | UUID |
| platform | string | `xiaohongshu` / `douyin` |
| status | string | `running` / `success` / `failed` |
| analysis_status | string | `normal` / `insufficient` |
| analysis_success_rate | float | AI 结构化成功率 |
| hot_limit | integer | 本任务热点数量配置 |
| item_limit_per_hot | integer | 每热点内容条目配置 |
| comment_limit_per_item | integer | 每内容条目评论配置 |
| total_items_count | integer | 总内容条目数 |
| processed_items_count | integer | 已处理内容条目数 |
| successful_items_count | integer | 成功内容条目数 |
| failed_items_count | integer | 失败内容条目数 |
| error_stage | string nullable | 失败阶段 |
| error_type | string nullable | 错误类型 |
| error_message | text nullable | 简要错误原因 |
| created_at | datetime | 创建时间 |
| started_at | datetime nullable | 开始时间 |
| finished_at | datetime nullable | 完成时间 |
状态规则:
- 没有任何内容条目成功抓取并完成分析:`status = failed`。
- 至少 1 条内容条目成功抓取并完成分析:`status = success`。
- AI 成功率低于 80%:`status` 不变,`analysis_status = insufficient`。
### 5.2 hotspots
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | UUID |
| task_id | string | 所属任务 |
| platform | string | 平台 |
| source_hot_id | string nullable | 平台原始热点 ID |
| rank | integer nullable | 排名 |
| title | text | 热点标题 |
| heat_value | string nullable | 热度值或榜单指标 |
| raw_data | text | 原始 JSON |
| created_at | datetime | 创建时间 |
### 5.3 content_items
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | UUID |
| task_id | string | 所属任务 |
| hotspot_id | string | 所属热点 |
| platform | string | 平台 |
| source_item_id | string | 平台原始内容 ID |
| item_type | string | `note` / `video` |
| title | text nullable | 标题 |
| summary | text nullable | 摘要 |
| url | text nullable | 内容 URL |
| status | string | `pending` / `success` / `failed` |
| error_stage | string nullable | 失败阶段 |
| error_type | string nullable | 错误类型 |
| error_message | text nullable | 错误说明 |
| raw_data | text | 原始 JSON |
| created_at | datetime | 创建时间 |
唯一性建议:
- MVP 保留跨热点重复内容。
- 使用 `task_id + hotspot_id + source_item_id` 作为业务去重依据。
### 5.4 comments
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | UUID |
| task_id | string | 所属任务 |
| hotspot_id | string | 所属热点 |
| content_item_id | string | 所属内容条目 |
| platform | string | 平台 |
| source_comment_id | string | 平台原始评论 ID |
| content | text | 评论内容 |
| author_name | text nullable | 作者昵称 |
| author_id | text nullable | 作者 ID |
| like_count | integer nullable | 点赞数 |
| comment_time | datetime nullable | 评论时间 |
| sentiment | string nullable | `positive` / `negative` / `neutral` / `unknown` |
| labels | text nullable | JSON Array 字符串 |
| reason | text nullable | 简短理由 |
| ai_analysis_status | string | `pending` / `success` / `failed` |
| ai_raw_data | text nullable | AI 原始响应 |
| raw_data | text | 评论原始 JSON |
| created_at | datetime | 创建时间 |
唯一性建议:
- 同一任务、同一内容条目、同一评论 ID 不重复:`task_id + content_item_id + source_comment_id`。
#### labels 字段格式与转换规则
- **入库格式**:统一为 JSON Array 字符串,如 `["价格吐槽", "物流慢"]`。
- **导出格式**:由 `export_service.py` 负责将 JSON Array 转换为中文逗号拼接字符串,如 `价格吐槽,物流慢`。
- **页面展示**:由 Jinja2 模板层解析 JSON Array 后逐个渲染为标签元素。
- 三个消费场景的格式转换各自负责,入库层只保证 JSON Array 格式正确。
### 5.5 reports
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | UUID |
| task_id | string | 所属任务 |
| hotspot_id | string nullable | 热点级报告使用 |
| content_item_id | string nullable | 内容条目级报告使用 |
| report_type | string | `hotspot` / `item` |
| metrics_json | text | 情绪、标签、样本数等结构化统计 |
| typical_comments_json | text | 典型评论 |
| summary | text | AI 简短总结 |
| markdown_content | text | 预生成 Markdown |
| created_at | datetime | 创建时间 |
| updated_at | datetime | 更新时间 |
报告生成策略:
- MVP 采用预生成型。
- 任务完成后先生成内容条目级报告,再生成热点级报告。
- 报告数据不做自动重算;如任务重新执行,生成新任务和新报告。
#### reports 表业务约束
- 当 `report_type = 'hotspot'` 时:`hotspot_id` 不为空,`content_item_id` 为空。
- 当 `report_type = 'item'` 时:`hotspot_id` 不为空,`content_item_id` 不为空。
- 该约束在应用层(`report_service.py` 创建报告时)保证,SQLite 不设数据库级约束。
### 5.6 建议索引(按需添加)
以下索引在 MVP 数据量下非必需,当性能出现瓶颈时可添加:
```sql
CREATE INDEX idx_comments_task_item ON comments(task_id, content_item_id);
CREATE INDEX idx_comments_dedup ON comments(task_id, content_item_id, source_comment_id);
CREATE INDEX idx_content_items_task_hotspot ON content_items(task_id, hotspot_id);
CREATE INDEX idx_reports_task_type ON reports(task_id, report_type);
```
#### 已知技术债:数据保留策略
- MVP 不实现自动数据清理机制。
- 所有任务及关联数据(热点、内容条目、评论、报告)永久保留在 SQLite 中。
- 后续版本可基于 `tasks.created_at` 索引实现过期数据自动清理(如保留最近 30 天)。
- 在此之前,用户可通过删除 SQLite 文件并重启应用来手动清理全部数据。
---
## 6. 外部 API 设计
### 6.1 通用调用策略
- 所有外部 API 通过 `httpx` 调用。
- 每次请求设置 20s 超时。
- 对 HTTP 429 使用指数退避:1s → 2s → 4s。
- 429 超过最大重试次数后,当前条目标记失败,任务继续。
- 非 429 网络错误最多重试 2 次。
- 所有成功响应和关键失败响应尽量保留 raw_data 或错误摘要。
### 6.2 小红书链路
```text
fetch_hot_list
→ data.data.items[].title
→ search_notes(keyword = hot.title)
→ note.id
→ get_note_comments(note_id)
→ comments
```
字段映射:
| 层级 | 来源字段 | 入库字段 |
|---|---|---|
| 热点 | `id` | `source_hot_id` |
| 热点 | `title` | `title` |
| 热点 | `score` | `heat_value` |
| 笔记 | `note.id` | `source_item_id` |
| 笔记 | `note.title` | `title` |
| 笔记 | `note.desc` | `summary` |
| 笔记 | `note.comments_count` | 用于优先筛选 |
| 评论 | `source_comment_id ← data.get("comment_id") or data.get("id")` | 优先 `comment_id` |
| 评论 | `content` / `text` | `content` |
| 评论 | `like_count` | `like_count` |
| 评论 | `create_time` | `comment_time` |
筛选策略:
- 优先选择 `comments_count > 0` 的笔记。
- 不足目标数量时补充 `comments_count = 0` 的笔记。
- 平台返回笔记总数不足目标数量时,以实际数量为准,不视为任务失败。
#### 搜索笔记分页策略(MVP)
- MVP 阶段搜索笔记接口**不做分页**,仅使用首页返回结果。
- 若首页结果中 `comments_count > 0` 的笔记不足目标数量,执行 FeatureSummary 中定义的降级策略(补充 `comments_count = 0` 的笔记,或以实际可用数量为准)。
- 搜索分页作为后续优化项,不在 MVP 范围内。
### 6.3 抖音链路
```text
fetch_creator_hot_spot_billboard
→ hot.title
→ fetch_video_search_v2(keyword = hot.title)
→ aweme_info.aweme_id
→ fetch_video_comments(aweme_id)
→ comments
```
字段映射:
| 层级 | 来源字段 | 入库字段 |
|---|---|---|
| 热点 | `query_id` | `source_hot_id` |
| 热点 | `title` | `title` |
| 热点 | `rank` | `rank` |
| 热点 | `hot_score` | `heat_value` |
| 视频 | `aweme_info.aweme_id` | `source_item_id` |
| 视频 | `aweme_info.desc` | `title` / `summary` |
| 视频 | `aweme_info.author` | `raw_data` 中保留 |
| 评论 | `source_comment_id ← data.get("comment_id") or data.get("cid")` | 优先 `comment_id` |
| 评论 | `text` | `content` |
| 评论 | `digg_count` | `like_count` |
| 评论 | `create_time` | `comment_time` |
### 6.4 评论分页策略
统一终止条件:
1. 已抓取评论数达到 `comment_limit_per_item`。
2. API 返回评论列表为空。
3. 达到最大翻页轮次 5。
4. 连续请求失败且超过重试次数。
分页实现要求:
- 小红书使用 `cursor` / `index` 字段推进。
- 抖音使用 `cursor` 字段推进,单次 `count=20`。
- 若 API 未返回明确下一页游标,则停止翻页。
#### 分页请求间隔
- 每次评论分页请求之间须等待 **1-2 秒**(建议默认 1.5 秒),降低触发平台限流的概率。
- 该间隔独立于 §6.1 的 429 指数退避策略;收到 429 响应后切换为退避策略,退避结束后恢复基础间隔。
---
## 7. AI 分析方案
### 7.1 评论级输出 Schema
AI 评论分析必须返回 JSON Array,每一项对应一条输入评论。
```json
[
{
"comment_id": "string",
"sentiment": "positive | negative | neutral | unknown",
"labels": ["string"],
"reason": "string"
}
]
```
校验规则:
- `comment_id` 必须能匹配输入评论。
- `sentiment` 必须属于枚举值。
- `labels` 必须是数组,最多 3 个标签。
- `reason` 可为空字符串。
- 任意一条结果不合法时,该条评论标记为 `ai_analysis_status = failed`。
- 整批无法解析时,整批重试,最多 3 次。
### 7.2 Prompt 约束
#### Prompt 输入格式
传递给 LLM 的评论数据须采用以下 JSON Array 格式:
```json
[
{ "comment_id": "abc123", "content": "这个产品太好了,强烈推荐" },
{ "comment_id": "def456", "content": "物流太慢了,等了一周才到" }
]
```
**要求**:
- Prompt 模板中须明确指示 AI:"请原样回填输入中的 comment_id,不得修改或生成新 ID"。
- 传递前对单条评论内容做截断处理:**保留前 150 个字符**,超出部分丢弃。目的是控制 Token 消耗并提高输出格式稳定性(情绪和标签通常在评论开头即可判断)。
System Prompt 要求:
- 只返回 JSON Array。
- 不输出 Markdown。
- 不输出解释性自然语言。
- 标签使用简短中文短语。
- 每条评论输出 1–3 个标签;确实无法判断时可返回空数组并将 `sentiment` 标记为 `unknown`。
### 7.3 批量与并发
- 默认每批 20 条评论。
- AI 并发数默认为 2。
- 最大并发数不超过 3。
- 单次请求超时 30s。
- 任务内按内容条目逐步分析,便于进度统计和失败隔离。
#### AI 请求并发控制
- **并发粒度**:AI 并发数 2 指同一任务内最多 **2 个 AI 批量请求同时进行**,粒度为**跨内容条目级别**。即同一时间可以并行分析 2 个不同内容条目的评论批次,但同一内容条目的多批评论串行处理。
- **默认并发数**:2(通过环境变量 `AI_CONCURRENCY` 配置)。
#### 降级重试策略
- 单批 AI 请求失败后按 §7.1 规则重试,最多 `AI_MAX_RETRIES` 次(默认 3)。
- **降级拆分**:若连续 2 次重试均因 JSON 解析失败(`ai_parse_failed`),则第 3 次重试时自动将当前 batch_size **减半**(如 20 → 10)后重新请求。
- 若减半重试仍失败,将该批次所有评论的 `ai_analysis_status` 标记为 `failed`,继续处理下一批次。
### 7.4 AI 质量状态
任务完成后计算:
```text
analysis_success_rate = analysis_success_comments / total_comments
```
判定:
- `analysis_success_rate >= 0.8`:`analysis_status = normal`
- `analysis_success_rate < 0.8`:`analysis_status = insufficient`
注意:
- `analysis_status` 不改变任务 `status`。
- 任务生命周期状态仍只有 `running` / `success` / `failed`。
- 页面须展示 AI 分析成功率或“分析不足”提示。
---
## 8. 报告生成方案
### 8.1 内容条目级报告
输入:
- 内容条目基础信息;
- 该内容条目下所有已分析评论;
- 情绪和标签结构化结果。
生成步骤:
1. 统计评论样本数。
2. 统计正向、负向、中性、未知数量和占比。
3. 按标签字面值统计 Top 5。
4. 按情绪分组选取典型评论:
- 优先按点赞数降序;
- 点赞数缺失时按抓取顺序。
5. 调用 AI 生成内容条目总结。
6. 生成 Markdown 内容并保存到 `reports`。
#### 报告总结 AI 调用规格
- **调用方式**:独立 AI 请求,不复用评论分析的批量调用。
- **输入内容**:
- 统计数据摘要:情绪分布(正面/中性/负面各占比)、标签 Top 5 及其出现次数。
- 典型评论文本:每个情绪类别取 2-3 条代表性评论原文(截断前 150 字符)。
- **输出要求**:纯文本,不使用 JSON Schema,控制在 **200 字以内**。
- **Prompt 模板**:独立模板文件,存放于 Prompt 模板目录(如 `prompts/report_summary.txt`)。
- **超时与失败策略**:复用 §7.3 的 `AI_TIMEOUT_SECONDS` 配置;总结生成失败**不阻断报告创建**,报告中该字段显示为默认文本"总结生成失败,请查看详细数据"。
### 8.2 热点级报告
输入:
- 热点基础信息;
- 热点下所有内容条目;
- 热点下所有已分析评论;
- 内容条目级统计结果。
生成步骤:
1. 聚合内容条目数量。
2. 聚合评论样本数。
3. 聚合情绪分布。
4. 聚合 Top 5 标签。
5. 选取典型评论。
6. 调用 AI 生成热点总结。
7. 生成 Markdown 内容并保存到 `reports`。
#### 报告总结 AI 调用规格
- **调用方式**:独立 AI 请求,不复用评论分析的批量调用。
- **输入内容**:
- 统计数据摘要:情绪分布(正面/中性/负面各占比)、标签 Top 5 及其出现次数。
- 典型评论文本:每个情绪类别取 2-3 条代表性评论原文(截断前 150 字符)。
- **输出要求**:纯文本,不使用 JSON Schema,控制在 **200 字以内**。
- **Prompt 模板**:独立模板文件,存放于 Prompt 模板目录(如 `prompts/report_summary.txt`)。
- **超时与失败策略**:复用 §7.3 的 `AI_TIMEOUT_SECONDS` 配置;总结生成失败**不阻断报告创建**,报告中该字段显示为默认文本"总结生成失败,请查看详细数据"。
### 8.3 统计一致性原则
- 情绪数量、标签数量、样本数必须由评论结构化结果计算。
- AI 总结只能基于统计结果和典型评论生成,不反向覆盖结构化统计。
- 页面展示和导出均读取同一份报告数据。
---
## 9. 后端接口契约
### 9.1 页面路由
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/` | 任务列表 / 创建任务页 |
| GET | `/tasks/{task_id}` | 热点与内容条目列表页 |
| GET | `/hotspots/{hotspot_id}/report` | 热点级报告页 |
| GET | `/items/{item_id}` | 内容条目详情页 |
### 9.2 API 路由
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | `/api/tasks` | 创建抓取任务 |
| GET | `/api/tasks` | 查询任务列表 |
| GET | `/api/tasks/{task_id}` | 查询任务详情 |
| GET | `/api/tasks/{task_id}/hotspots` | 查询任务热点与内容条目 |
| GET | `/api/items/{item_id}/comments` | 查询评论明细 |
| GET | `/api/export/items/{item_id}/comments.csv` | 导出 CSV |
| GET | `/api/export/hotspots/{hotspot_id}.md` | 导出热点 Markdown |
| GET | `/api/export/items/{item_id}.md` | 导出内容条目 Markdown |
| GET | `/health` | 健康检查 |
#### 补充路由
- `GET /api/export/hotspots/{hotspot_id}/comments.csv` — 导出指定热点下所有内容条目的评论汇总 CSV。
#### 接口参数预留
- `GET /api/items/{item_id}/comments` 预留可选分页参数:`?page=1&page_size=50`。MVP 默认返回全部评论(不分页),但接口签名须支持这两个参数以备后续启用。
### 9.3 创建任务请求
```json
{
"platform": "xiaohongshu",
"hot_limit": 5,
"item_limit_per_hot": 5,
"comment_limit_per_item": 50
}
```
校验:
- `platform` 必须为 `xiaohongshu` 或 `douyin`。
- `hot_limit` 范围 1–10。
- `item_limit_per_hot` 范围 1–10。
- `comment_limit_per_item` 范围 10–100。
---
## 10. 前端页面计划
### 10.1 任务列表 / 首页
展示:
- 平台选择;
- 抓取规模配置;
- 开始抓取按钮;
- 刷新任务列表按钮;
- 任务列表:
- 创建时间;
- 平台;
- 状态;
- AI 分析状态 / 成功率;
- 成功 X / 共 Y 条内容条目;
- 错误阶段与错误类型;
- 进入任务结果入口。
交互:
- 提交前做前端范围校验。
- 后端仍必须做同样校验。
- P0 使用手动刷新。
- P1 可每 5 秒轮询运行中任务。
### 10.2 热点与内容条目列表页
展示:
- 任务基础信息;
- 任务状态和 AI 分析状态;
- 热点列表;
- 每个热点下内容条目列表;
- 进入热点报告和内容条目详情入口。
### 10.3 热点级报告页
展示:
- 热点基础信息;
- 内容条目数量;
- 评论样本数;
- 情绪分布;
- Top 5 标签;
- 典型评论;
- 热点总结;
- Markdown 导出入口。
### 10.4 内容条目详情页
展示:
- 热点与内容条目基础信息;
- 内容条目级报告;
- 评论明细;
- Markdown 导出入口;
- CSV 导出入口;
- P1 可展示原始 JSON 调试入口。
---
## 11. 导出方案
### 11.1 CSV 评论明细
- 编码:`UTF-8-SIG`。
- 文件名:`{platform}_{task_id}_{hotspot_keyword}.csv`。
- `hotspot_keyword` 超过 20 字符时截断并附加省略号。
- 标签字段使用中文逗号拼接或 JSON 字符串,MVP 建议中文逗号拼接,便于 Excel 查看。
#### 文件名安全处理
- `hotspot_keyword` 超过 20 字符时截断。
- 文件名中的非法字符(包括但不限于 `/`、`\`、`:`、`*`、`?`、`"`、`<`、`>`、`|`)统一替换为下划线 `_`。
- 连续多个下划线合并为单个下划线。
字段:
1. 平台;
2. 任务 ID;
3. 热点 ID;
4. 热点标题;
5. 内容条目 ID;
6. 内容条目标题;
7. 评论 ID;
8. 评论内容;
9. 情绪倾向;
10. 方向标签;
11. 点赞数;
12. 评论时间。
### 11.2 Markdown 报告
- 直接读取 `reports.markdown_content`。
- 响应头设置下载文件名。
- 页面展示与 Markdown 导出必须来自同一份报告数据。
---
## 12. 配置与部署
### 12.1 环境变量
```text
APP_ENV=development
APP_HOST=0.0.0.0
APP_PORT=8000
DATABASE_URL=sqlite:///./data/app.db
TIKHUB_API_KEY=
TIKHUB_BASE_URL=https://api.tikhub.io
AI_PROVIDER=openai-compatible
AI_BASE_URL=
AI_API_KEY=
AI_MODEL=
AI_BATCH_SIZE=20
AI_CONCURRENCY=2
AI_MAX_RETRIES=3
AI_TIMEOUT_SECONDS=30
HTTP_TIMEOUT_SECONDS=20
HTTP_MAX_RETRIES=3
```
| 变量名 | 默认值 | 说明 |
|---|---|---|
| `AI_MAX_RETRIES` | `3` | AI 单批请求最大重试次数 |
| `AI_CONCURRENCY` | `2` | AI 请求最大并发数(跨内容条目级别) |
### 12.2 Docker Compose
MVP 只需要一个 app 服务和一个数据卷:
```yaml
services:
app:
build: .
ports:
- "8000:8000"
env_file:
- .env
volumes:
- ./data:/app/data
```
验收:
- `docker compose up --build` 可启动;
- 浏览器访问 `http://localhost:8000`;
- `/health` 返回 200;
- SQLite 数据写入 `./data/app.db`。
---
## 13. 错误处理与日志
### 13.1 错误类型
| error_stage | error_type | 示例 |
|---|---|---|
| crawl_hotspots | api_error | 热点接口失败 |
| crawl_items | api_response_invalid | 搜索结果字段缺失 |
| crawl_comments | rate_limited | HTTP 429 |
| ai_analysis | ai_timeout | AI 请求超时 |
| ai_analysis | ai_parse_failed | JSON 解析失败 |
| report_generation | report_failed | 报告生成失败 |
| database | db_error | 入库失败 |
### 13.2 容错规则
- 热点列表获取失败:任务失败。
- 单个热点搜索内容失败:记录错误,继续下一个热点。
- 单个内容条目评论抓取失败:该内容条目标记失败,继续下一个内容条目。
- 单条评论 AI 分析失败:该评论标记分析失败,继续其他评论。
- 没有任何内容条目成功:任务失败。
- 至少 1 条内容条目成功:任务成功,并展示失败数量和错误摘要。
### 13.3 日志
- 使用 Python 标准 logging。
- 每个任务日志必须带 `task_id`。
- 外部 API 错误日志记录:
- 平台;
- 接口名称;
- HTTP 状态码;
- 错误摘要;
- 不记录完整 API Key。
---
## 14. 测试计划
### 14.1 单元测试
必须覆盖:
- 配置范围校验;
- 小红书字段映射;
- 抖音字段映射;
- 评论分页终止条件;
- 评论去重;
- AI JSON Schema 校验;
- `analysis_success_rate` 与 `analysis_status` 计算;
- 情绪和标签统计;
- Markdown 生成;
- CSV `UTF-8-SIG` 导出。
### 14.2 集成测试
建议覆盖:
- 使用 mock 外部 API 创建一条小红书任务并完成全流程;
- 使用 mock 外部 API 创建一条抖音任务并完成全流程;
- 单个内容条目失败但任务成功;
- AI 解析失败重试后成功;
- AI 成功率低于 80% 时任务成功但 `analysis_status = insufficient`。
### 14.3 手工验收
1. `docker compose up --build` 启动。
2. 打开首页。
3. 创建小红书默认规模任务。
4. 刷新任务列表直到任务完成。
5. 查看热点列表、热点报告、内容条目详情、评论明细。
6. 导出 CSV 和 Markdown。
7. 创建抖音默认规模任务并重复验收。
8. 手动填入非法配置值,确认前后端均阻止提交。
---
## 15. 开发周期安排
若从 2026-07-01 开始,建议排期如下。
### Day 1:项目骨架、数据模型、任务框架
目标:
- FastAPI 项目可启动;
- SQLite 表结构完成;
- 任务创建和状态流转可用;
- 页面能创建任务并看到任务列表。
任务:
1. 初始化项目结构。
2. 编写配置管理和 `.env.example`。
3. 定义 SQLAlchemy 数据模型。
4. 实现数据库初始化。
5. 实现任务创建 API。
6. 实现任务列表页面。
7. 实现 `/health`。
8. 编写基础单元测试。
验收:
- 本地启动后可创建一条空任务;
- 任务列表展示平台、创建时间、状态;
- Docker Compose 能启动 app。
### Day 2:平台抓取链路
> ⚠️ **排期风险备注**:Day 2 优先完成小红书完整链路(搜索 + 笔记详情 + 评论含分页 + 字段映射 + raw_data 保存)。若进度受阻,抖音链路可延至 Day 3 上午。判断标准:如果到 Day 2 下午 4 点小红书链路尚未跑通端到端测试,立即停止并将抖音推迟。
目标:
- 小红书和抖音最小链路工程化;
- 热点、内容条目、评论可入库;
- 分页、限流和字段兼容策略落地。
任务:
1. 实现 TikHub HTTP client。
2. 实现小红书热点、笔记、评论抓取。
3. 实现抖音热点、视频、评论抓取。
4. 实现评论分页与 429 退避。
5. 实现 raw_data 保存。
6. 实现任务进度统计字段。
7. 编写字段映射和分页测试。
验收:
- 默认规模可抓取至少一个平台的真实数据;
- 小红书 / 抖音链路均可在 mock 测试中通过;
- 字段缺失不导致整批任务崩溃。
### Day 3:AI 分析与报告生成
> ⚠️ **排期调整说明**:若抖音链路从 Day 2 延入,Day 3 上午优先完成抖音链路,下午实现 AI 分析 + 报告生成。报告的 Markdown 排版以信息可读为标准,不追求视觉效果,必要时直接使用字符串拼接。
目标:
- 评论级结构化分析可用;
- AI 输出校验、重试和质量状态可用;
- 内容条目级和热点级报告预生成。
任务:
1. 编写评论分析 JSON Schema。
2. 实现 AI client。
3. 实现批量评论分析。
4. 实现 AI 解析失败重试。
5. 实现 `analysis_success_rate` 和 `analysis_status`。
6. 实现情绪 / 标签统计。
7. 实现典型评论选取。
8. 实现报告总结和 Markdown 生成。
9. 编写 AI schema、统计和报告测试。
验收:
- 任务完成后评论有情绪和标签;
- 报告统计与评论明细一致;
- AI 成功率低于 80% 时页面可见分析不足提示。
### Day 4:页面、导出、Docker 验收
目标:
- 所有页面可用;
- CSV / Markdown 导出可用;
- Docker Compose 端到端验收通过;
- 文档与环境示例补齐。
任务:
1. 完成热点与内容条目列表页。
2. 完成热点级报告页。
3. 完成内容条目详情页和评论明细。
4. 完成 CSV 导出。
5. 完成 Markdown 导出。
6. 完成前端手动刷新和配置校验。
7. 完成 Dockerfile 和 docker-compose.yml。
8. 执行端到端手工验收。
9. 修复高优先级问题。
验收:
- 两个平台至少各跑通一次默认任务;
- 页面与导出内容一致;
- Docker Compose 一键启动;
- MVP 关键验收清单全部通过或记录明确缺口。
---
## 16. 风险与降级方案
| 风险 | 影响 | 降级方案 |
|---|---|---|
| TikHub 接口字段变化 | 抓取失败或字段为空 | 保留 raw_data,字段映射使用多候选字段 |
| 平台 API 限流 | 任务变慢或部分内容失败 | 429 指数退避,超过重试后跳过当前条目 |
| AI 输出不稳定 | 评论无法结构化 | JSON Schema 校验 + 3 次重试 + 单条失败隔离 |
| AI 成本或耗时过高 | 任务执行时间变长 | 降低默认抓取规模或 AI batch size |
| SQLite 写入冲突 | 任务失败 | `ThreadPoolExecutor(max_workers=1)` + WAL 模式 + `timeout=10`(见 §2.1) |
| AI 批量 JSON 解析失败 | 评论无法结构化 | 3 次重试 + 第 3 次自动 batch_size 减半(见 §7.3) |
| 容器重启导致任务僵尸 | 任务永久停留在运行中 | `lifespan` 启动时自动将 `running` 任务标记为 `failed`(见 §4.3) |
| 页面轮询未实现 | 用户不知道任务进度 | P0 保留手动刷新按钮和创建时间 |
| 报告生成失败 | 结果不可查看 | 内容条目标记失败;已生成评论明细仍可展示 |
---
## 17. 关键验收清单
MVP 完成时必须满足:
1. 系统可通过 Docker Compose 启动,并能在浏览器访问。
2. 用户可从页面选择小红书或抖音并手动触发任务。
3. 用户可配置热点数、每热点内容条目数、每内容评论数,且非法值无法提交。
4. 系统可获取默认 Top 5 热点。
5. 系统可为每个热点拆分默认最多 5 条内容条目。
6. 系统可为每条内容条目抓取默认最多 50 条一级评论。
7. 任务内至少 80% 的评论成功生成情绪分类和方向标签;低于 80% 时 `analysis_status` 标记为分析不足。
8. 系统可生成热点级汇总报告。
9. 系统可生成内容条目级分析报告。
10. 页面可查看任务列表、热点列表、热点级报告、内容条目详情和评论明细。
11. 用户可导出 `UTF-8-SIG` 编码的 CSV 评论明细。
12. 用户可导出 Markdown 热点级汇总报告。
13. 用户可导出 Markdown 内容条目级报告。
14. 任务失败时,页面可展示失败状态,错误原因至少包含失败阶段和错误类型。
15. 报告统计数据与评论结构化结果一致。
---
## 18. 后续文档衔接
DevelopmentPlan 完成后,建议继续产出:
1. `UIDesign.md`
- 页面信息结构;
- 表单布局;
- 任务状态展示;
- 报告页展示结构。
2. `TDD.md`
- 单元测试、集成测试、端到端验收用例;
- mock API 响应样例;
- AI 输出解析失败用例。
3. `Tasks.md`
- 按 Day 1–Day 4 拆成可执行开发任务;
- 标明每个任务的输入、输出、验收和依赖关系。
---
## 变更日志
| 日期 | 版本 | 变更内容 |
|---|---|---|
| 2025-07-10 | v1.0 | 初始版本 |
| 2025-07-10 | v1.1 | 基于双审阅报告合并修订:锁定 ThreadPoolExecutor(max_workers=1) + SQLite WAL 模式;明确 httpx 同步使用策略;补充 AI Prompt 输入格式与 comment_id 回填要求;补充报告总结 AI 调用独立规格;新增僵尸任务恢复机制;AI 批量重试增加降级拆分策略;明确 AI 并发粒度为跨内容条目级别;补充评论分页请求间隔;明确搜索笔记不分页;补充 reports 表业务约束与建议索引;补充 labels 格式转换归属;字段映射候选字段标注优先级;移除冗余 refresh 路由;补充热点级 CSV 导出路由与评论接口分页预留;文件名安全处理规则;环境变量补充 AI_MAX_RETRIES 和 AI_CONCURRENCY;数据保留策略记录为技术债;P2 列表扩充;Day 2/3 排期风险备注;风险表引用更新。共 23 条修订指令。 |