Files

1124 lines
38 KiB
Markdown
Raw Permalink 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.
# 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/)`<script src="https://unpkg.com/htmx.org@1.9.12"></script>`),用声明式属性替代手写 fetch + DOM 操作。
- 示例:任务列表轮询只需 `<div hx-get="/api/tasks" hx-trigger="every 5s" hx-swap="innerHTML">`
- 此项为可选优化,不引入不影响功能完整性,但可在 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` 范围 110。
- `item_limit_per_hot` 范围 110。
- `comment_limit_per_item` 范围 10100。
---
## 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 3AI 分析与报告生成
> ⚠️ **排期调整说明**:若抖音链路从 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 条修订指令。 |