Files
hot_comment_radar/docs/Tasks.md
T

948 lines
36 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.
# Tasks.md:小红书 / 抖音热榜评论抓取 + AI 分析报告工具
## 1. 文档信息
- 文档阶段:Tasks(开发任务拆解)
- 需求依据:`docs/PRD.md``docs/FeatureSummary.md`
- 技术依据:`docs/DevelopmentPlan.md`
- UI 依据:`docs/UIDesign.md`
- 测试依据:`docs/TDD.md`
- API Spike 依据:`docs/API-Spike-Xiaohongshu.md``docs/API-Spike-Douyin.md`
- 当前目标:将 MVP 拆解为 4 天内可执行、可测试、可验收的开发任务
---
## 2. 开发总原则
1. 遵循 TDD:先写失败测试,再写最小实现,再重构。
2. MVP 优先:先跑通主链路,再做 P1 优化。
3. 不使用真实 TikHub / AI API 作为单元测试依赖。
4. 外部 API、AI 响应、字段缺失、限流等场景必须通过 mock 覆盖。
5. 任务主状态只使用 `running` / `success` / `failed`AI 质量使用 `analysis_status``analysis_success_rate` 表示,不新增“部分失败”任务状态。
6. 页面展示和导出必须读取同一份结构化报告数据。
7. API Key、AI Key 不写入代码仓库。
---
## 3. 目标目录结构
```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/
base.html
index.html
tasks/detail.html
hotspots/report.html
items/detail.html
partials/task_rows.html
macros/status_badge.html
macros/sentiment_badge.html
macros/label_tags.html
static/
app.css
app.js
prompts/
comment_analysis.txt
report_summary.txt
tests/
conftest.py
fixtures/
unit/
integration/
Dockerfile
docker-compose.yml
.env.example
pyproject.toml
```
---
## 4. 里程碑安排
| 日期 | 目标 | 验收结果 |
|---|---|---|
| Day 1 | 项目骨架、配置、数据库、任务创建、基础页面 | 可启动、可创建任务、任务列表可见 |
| Day 2 | 小红书 / 抖音抓取链路、字段映射、评论分页、容错 | mock 测试中两平台链路跑通 |
| Day 3 | AI 分析、报告生成、统计一致性 | 评论有情绪和标签,报告可生成 |
| Day 4 | 页面完善、导出、Docker、手工验收 | Docker 启动,双平台默认任务可演示 |
---
## 5. Day 1:项目骨架、数据模型、任务框架
### T01 初始化项目结构与依赖
**目标**:创建 FastAPI 单体项目骨架。
**涉及文件**
- 创建:`app/main.py`
- 创建:`app/config.py`
- 创建:`app/db.py`
- 创建:`app/models.py`
- 创建:`app/schemas.py`
- 创建:`pyproject.toml``requirements.txt`
- 创建:`.env.example`
**步骤**
- [ ] 创建 `app/``app/services/``app/platforms/``app/templates/``app/static/``tests/` 目录。
- [ ] 添加 FastAPI、SQLAlchemy、Pydantic、httpx、Jinja2、pytest、respx、beautifulsoup4 等依赖。
- [ ]`app/main.py` 中创建 FastAPI 应用。
- [ ] 实现 `/health`,返回 `{ "status": "ok" }`
- [ ] 编写 `tests/unit/test_config.py`,覆盖默认配置和环境变量读取。
- [ ] 编写 `/health` 集成测试。
- [ ] 运行 `pytest tests/unit tests/integration -q`
**验收**
- 本地可启动 FastAPI。
- `/health` 返回 200。
- 配置测试通过。
### T02 配置管理与环境变量
**目标**:集中管理 TikHub、AI、数据库、HTTP 超时和任务配置。
**涉及文件**
- 修改:`app/config.py`
- 修改:`.env.example`
- 测试:`tests/unit/test_config.py`
**配置项**
- `APP_ENV`
- `DATABASE_URL`
- `TIKHUB_API_KEY`
- `TIKHUB_BASE_URL`
- `AI_PROVIDER`
- `AI_BASE_URL`
- `AI_API_KEY`
- `AI_MODEL`
- `AI_BATCH_SIZE`
- `AI_CONCURRENCY`
- `AI_MAX_RETRIES`
- `AI_TIMEOUT_SECONDS`
- `HTTP_TIMEOUT_SECONDS`
- `HTTP_MAX_RETRIES`
- `CRAWL_PAGE_INTERVAL_SECONDS=1.5`:评论分页请求间隔秒数,支持浮点数,默认 1.5 秒
**步骤**
- [ ] 先写配置默认值测试。
- [ ] 实现 Pydantic Settings 或等价配置类。
- [ ] 校验 `AI_CONCURRENCY` 默认值为 2,硬上限不超过 3。
- [ ] 校验 `AI_MAX_RETRIES` 默认值为 3。
- [ ] 补充 `.env.example`
- [ ] 写测试:`CRAWL_PAGE_INTERVAL_SECONDS` 可从环境变量读取,默认值为 1.5。
- [ ]`.env.example` 中补充该配置项及注释说明。
**验收**
- 配置测试通过。
- `.env.example` 不包含真实 Key。
### T03 数据库初始化与模型
**目标**:实现 SQLite + SQLAlchemy 数据模型。
**涉及文件**
- 修改:`app/db.py`
- 修改:`app/models.py`
- 测试:`tests/unit/test_models.py`
**表结构**
- `tasks`
- `hotspots`
- `content_items`
- `comments`
- `reports`
**步骤**
- [ ]`tests/unit/test_models.py`,断言所有表可创建。
- [ ] 实现 SQLAlchemy Base、engine、session。
- [ ] SQLite 启用 `check_same_thread=False``timeout=10`、WAL。
- [ ] 实现 `Base.metadata.create_all(engine)` 启动初始化。
- [ ] 实现 `tasks.analysis_status``analysis_success_rate`、进度字段。
- [ ] 实现 reports 表业务约束在应用层校验所需字段。
- [ ] 【性能预留索引,不影响功能验收】在 comments 表上添加 `(task_id, content_item_id)` 联合索引。
- [ ] 【性能预留索引,不影响功能验收】在 content_items 表上添加 `(task_id, hotspot_id)` 联合索引。
- [ ] 【性能预留索引,不影响功能验收】在 reports 表上添加 `(task_id, report_type)` 联合索引。
**验收**
- 内存 SQLite 测试可创建并销毁全部表。
- `Task.status` 支持 `running` / `success` / `failed`
- `analysis_status=insufficient` 不改变 `Task.status`
### T04 任务创建 API 与单任务执行器
**目标**:用户可创建任务,后台任务框架可排队执行。
**涉及文件**
- 修改:`app/schemas.py`
- 创建:`app/services/task_service.py`
- 修改:`app/main.py`
- 测试:`tests/integration/test_task_creation.py`
- 测试:`tests/unit/test_task_executor.py`
**接口**
- `POST /api/tasks`
- `GET /api/tasks`
- `GET /api/tasks/{task_id}`
**步骤**
- [ ] 写创建任务 API 测试,合法参数返回 `task_id``status`
- [ ] 写非法参数测试:平台非法、热点数量越界、内容条目数越界、评论数越界。
- [ ] 实现 `CreateTaskRequest` schema。
- [ ] 实现 `ThreadPoolExecutor(max_workers=1)`
- [ ]`CreateTaskRequest` 处理逻辑中,查询当前是否存在 `status=running` 的任务;若存在,直接返回 HTTP 400,响应体为 `{"detail": "当前有正在运行的任务,请稍后再试"}`,不创建新任务。
- [ ] 写测试:当已有 `status=running` 任务时,`POST /api/tasks` 返回 400。
- [ ] 写测试:当无 running 任务时,`POST /api/tasks` 正常创建并返回 200/201。
- [ ] 创建任务时保存平台、配置规模、创建时间、状态和进度字段。
- [ ] 同一平台重复创建任务时生成独立任务。
- [ ] 实现任务列表查询。
**验收**
- 合法任务可创建。
- 非法配置返回 422。
- 任务执行器为单 worker。
### T05 僵尸任务恢复
**目标**:应用重启后,将遗留 running 任务标记失败。
**涉及文件**
- 修改:`app/main.py`
- 修改:`app/services/task_service.py`
- 测试:`tests/integration/test_task_recovery.py`
**步骤**
- [ ] 写测试:数据库中存在 `status=running` 的任务。
- [ ] 应用 lifespan 启动时执行恢复逻辑。
- [ ] 将 running 任务更新为 failed。
- [ ] 写入 `error_stage=system``error_type=unexpected_restart``error_message=系统重启,任务被中断`
**验收**
- 重启恢复测试通过。
### T06 首页 / 任务列表基础页面
**目标**:用户可在页面创建任务并查看任务列表。
**涉及文件**
- 创建:`app/templates/base.html`
- 创建:`app/templates/index.html`
- 创建:`app/templates/partials/task_rows.html`
- 创建:`app/static/app.css`
- 创建:`app/static/app.js`
- 修改:`app/main.py`
- 测试:`tests/integration/test_routes.py`
- 测试:`tests/unit/test_template_filters.py`
**步骤**
- [ ] 创建 `base.html`,包含 Bootstrap 5 CDN 和主布局。
- [ ] 创建首页任务表单:平台、热点数量、每热点内容数、每内容评论数。
- [ ] 添加规模预估提示。
- [ ] 添加任务列表:任务 ID、平台、创建时间、配置规模、进度、状态、操作。
- [ ] 实现异步 `fetch("/api/tasks")` 提交。
- [ ] 实现手动刷新按钮。
- [ ] 注册状态 Badge 渲染逻辑或 Macro。
- [ ] 测试首页无任务空状态。
- [ ] 测试状态文案不在模板中散落硬编码。
- [ ] 前端实时计算规模预估值:`hotspot_limit × item_limit_per_hotspot × comment_limit_per_item`,默认展示 1250(5×5×50)。
- [ ] 任意配置项输入值变化时立即更新预估值显示,无需点击提交。
- [ ] 写集成测试:首页默认预估规模展示为 1250。
- [ ]`base.html` 中实现面包屑组件,使用 Bootstrap `<nav aria-label="breadcrumb">`,通过 Jinja2 block 传入路径数据。
- [ ] `index.html` 面包屑:首页(不展示,或仅展示当前页标识)。
**验收**
- 首页可访问。
- 表单非法值前端阻止提交,后端也返回 422。
- 任务创建成功后前端跳转至 `/tasks/{new_task_id}`,使用户可立即看到任务初始 pending 状态。
---
## 6. Day 2:平台抓取链路
### T07 外部 API 基础客户端与重试
**目标**:封装 TikHub API 调用、超时、限流退避和错误类型。
**涉及文件**
- 创建:`app/platforms/base.py`
- 创建:`app/services/crawl_service.py`
- 测试:`tests/unit/test_comment_pagination.py`
- 测试:`tests/integration/test_failure_tolerance.py`
- 新建:`tests/fixtures/http_429_response.json`(模拟 429 响应体)
- 新建:`tests/conftest.py` 中补充 `mock_429_httpx_client` fixture
**步骤**
- [ ] 写 429 mock fixture。
- [ ] 写 HTTP 429 指数退避测试:1s → 2s → 4s。
- [ ] 写非 429 网络错误重试测试。
- [ ] 实现同步 `httpx.Client` 调用封装。
- [ ] 每次请求设置 20s 超时。
- [ ] 超过重试次数后返回结构化错误,不抛出到任务全局。
- [ ] 写测试:后台任务执行函数不是协程(`assert inspect.iscoroutinefunction(run_task) is False`)。
- [ ] 写测试:平台 HTTP 客户端实例类型为 `httpx.Client`,不为 `httpx.AsyncClient`
- [ ] 写测试:收到 HTTP 429 响应时,触发指数退避重试(1s → 2s → 4s),不抛出异常。
- [ ] 写测试:连续 3 次 429 重试后仍失败,抛出可被上层捕获的自定义异常。
**验收**
- 429 退避测试通过。
- API Key 不出现在日志中。
### T08 小红书热点、笔记、评论字段映射
**目标**:实现小红书最小抓取链路。
**涉及文件**
- 创建:`app/platforms/xiaohongshu.py`
- 测试:`tests/unit/test_xiaohongshu_mapping.py`
- Fixture`tests/fixtures/xhs_hot_list.json`
- Fixture`tests/fixtures/xhs_search_notes.json`
- Fixture`tests/fixtures/xhs_comments_page_1.json`
- Fixture`tests/fixtures/xhs_comments_page_2_empty.json`
- 新建:`tests/fixtures/xhs_comments_missing_fields.json`like_count / create_time 字段缺失的小红书评论样本)
**步骤**
- [ ] 写热榜字段映射测试,确保读取 `data.data.items[]`,不误用外层 `data.data.title`
- [ ] 写笔记筛选测试:优先 `comments_count > 0`
- [ ] 写兜底测试:有评论笔记不足时补充 `comments_count = 0`
- [ ] 写评论字段映射测试:`comment_id` 优先于 `id`
- [ ] 实现 `fetch_hotspots()`
- [ ] 实现 `search_items_by_hotspot()`
- [ ] 实现 `fetch_comments()`
- [ ] 保存 raw_data。
- [ ] 写测试:小红书评论数据中 like_count 字段缺失时,字段默认值为 0,不抛出异常。
- [ ] 写测试:小红书评论数据中 create_time 字段缺失时,字段默认值为 None,不抛出异常。
**验收**
- 小红书字段映射测试通过。
- 字段缺失不导致整批任务崩溃。
### T09 抖音热点、视频、评论字段映射
**目标**:实现抖音最小抓取链路。
**涉及文件**
- 创建:`app/platforms/douyin.py`
- 测试:`tests/unit/test_douyin_mapping.py`
- Fixture`tests/fixtures/douyin_hot_list.json`
- Fixture`tests/fixtures/douyin_search_videos.json`
- Fixture`tests/fixtures/douyin_comments_page_1.json`
- 新建:`tests/fixtures/douyin_comments_missing_fields.json`like_count / create_time 字段缺失的抖音评论样本)
**步骤**
- [ ] 写热点字段映射测试:`query_id``title``rank``hot_score`
- [ ] 写视频字段映射测试:`aweme_info.aweme_id``desc``author``statistics`
- [ ] 写评论字段映射测试:`comment_id` 优先于 `cid`
- [ ] 实现 `fetch_hotspots()`
- [ ] 实现 `search_items_by_hotspot()`
- [ ] 实现 `fetch_comments()`
- [ ] 保存 raw_data。
- [ ] 写测试:抖音评论数据中 like_count 字段缺失时,字段默认值为 0,不抛出异常。
- [ ] 写测试:抖音评论数据中 create_time 字段缺失时,字段默认值为 None,不抛出异常。
**验收**
- 抖音字段映射测试通过。
- 字段缺失不导致整批任务崩溃。
### T10 评论分页、间隔与去重
**目标**:按配置抓取一级评论,并处理分页、停止条件和去重。
**涉及文件**
- 修改:`app/platforms/xiaohongshu.py`
- 修改:`app/platforms/douyin.py`
- 修改:`app/services/crawl_service.py`
- 测试:`tests/unit/test_comment_pagination.py`
**步骤**
- [ ] 写分页停止测试:达到评论数上限停止。
- [ ] 写分页停止测试:API 返回空评论列表停止。
- [ ] 写分页停止测试:达到最大翻页轮次 5 停止。
- [ ] 写分页请求间隔测试:`time.sleep` 在 1.0 到 2.0 秒之间。
- [ ] 写去重测试:同一任务同一内容条目同一评论 ID 不重复。
- [ ] 实现小红书 cursor / index 推进。
- [ ] 实现抖音 cursor 推进。
- [ ] 实现去重逻辑。
**验收**
- 评论分页测试通过。
- 评论数不超过配置上限。
### T11 抓取任务主流程集成
**目标**:任务可串起热点、内容条目、评论抓取并入库。
**涉及文件**
- 修改:`app/services/task_service.py`
- 修改:`app/services/crawl_service.py`
- 测试:`tests/integration/test_task_flow_xiaohongshu.py`
- 测试:`tests/integration/test_task_flow_douyin.py`
- 测试:`tests/integration/test_failure_tolerance.py`
**步骤**
- [ ] 写小红书端到端服务流测试,mock 热榜、搜索、评论。
- [ ] 写抖音端到端服务流测试,mock 热点、搜索、评论。
- [ ] 写单个内容条目失败但任务继续测试。
- [ ] 写热点接口失败导致任务 failed 测试。
- [ ] 实现任务执行流程。
- [ ] 更新 `processed_items_count``successful_items_count``failed_items_count`
- [ ] 每处理完一个内容条目(抓取完成或失败)后立即执行 `session.commit()`,实时更新 `processed_items_count`,不等待整个任务完成后统一提交。
- [ ] 写测试:处理第 1 个内容条目后,数据库中 `processed_items_count` 已更新为 1,无需等待全部条目处理完成。
- [ ] 没有任何内容条目成功时任务 failed。
- [ ] 至少一个内容条目成功时任务 success。
- [ ] 写测试:同一 `source_item_id` 出现在两个不同热点的搜索结果中时,以不同 `hotspot_id` 分别入库,共产生 2 条 `content_items` 记录(符合 DevelopmentPlan §5.3 去重规则:`task_id + hotspot_id + source_item_id` 联合唯一)。
- [ ] 写测试:同一任务内,同一热点下相同 `source_item_id` 不重复入库(第二次插入被跳过,记录数仍为 1)。
**验收**
- 两个平台 mock 集成测试通过。
- 单条失败不阻断整批任务。
⏱ 时间盒约束(Day 2):若 T08/T09/T10 爬虫链路在 4 小时内无法完整处理所有异常字段,
立即启动降级方案:丢弃异常字段,保留 raw_data 和核心必需字段,推进至下一任务。
外部 API 字段畸变(缺失、层级变化)是 Day 2 最大的时间黑洞,不在此恋战。
---
## 7. Day 3AI 分析与报告生成
### T12 AI Prompt 与结构化输出校验
**目标**:实现评论级 AI 分析,强制 JSON Array,并用 schema 校验。
**涉及文件**
- 创建:`app/services/ai_service.py`
- 创建:`app/prompts/comment_analysis.txt`
- 测试:`tests/unit/test_ai_schema.py`
- Fixture`tests/fixtures/ai_comments_success.json`
- Fixture`tests/fixtures/ai_comments_invalid_json.txt`
- 新建:`tests/fixtures/ai_comments_all_sentiments.json`(包含 positive / neutral / negative / unknown 四种情绪值的评论样本,用于统计逻辑测试)
**步骤**
- [ ] 写 prompt 输入构造测试,包含 `comment_id` 和截断后的 `content`
- [ ] 写评论内容超过 150 字截断测试。
- [ ] 写 AI 输出 JSON Array 校验测试。
- [ ] 写 sentiment 枚举校验测试。
- [ ] 写 labels 最多 3 个测试。
- [ ]`comment_id` 不匹配时单条失败测试。
- [ ] 实现 prompt 构造。
- [ ] 实现 Pydantic schema 校验。
- [ ] 实现单条失败标记 `ai_analysis_status=failed`
- [ ] 写测试:情绪统计时,四种情绪值(positive / neutral / negative / unknown)均有输入数据时,统计结果各自独立计数,总数等于评论总数。
**验收**
- AI schema 单元测试通过。
### T13 AI 重试、成功率统计
**目标**:AI 失败不阻断任务,并计算 AI 分析质量。
**涉及文件**
- 修改:`app/services/ai_service.py`
- 修改:`app/services/task_service.py`
- 测试:`tests/unit/test_ai_schema.py`
**步骤**
- [ ] AI 批量分析重试策略(与 DevelopmentPlan §7.1 保持一致):batch size 固定为 20,不做动态缩减。
- [ ] 整批 JSON 解析失败时,整批重试,最多重试 3 次。
- [ ] 第 3 次重试仍失败,该批次全部评论标记 `ai_analysis_status=failed`,不阻断其他批次处理。
- [ ] 不实现 batch size 减半逻辑。
- [ ] 写 AI 请求最多重试 3 次测试。
- [ ] 写测试:整批 JSON 解析失败时,自动整批重试,最多 3 次。
- [ ] 写测试:第 3 次重试仍失败后,该批次全部评论 `ai_analysis_status` 标记为 failed。
- [ ] 写测试:一批次失败不影响其他批次的正常处理。
- [ ] 写测试:重试间隔符合指数退避(1s → 2s → 4s)。
- [ ] 写同一任务最多 2 个 AI 批量请求并发测试。
- [ ] 写同一内容条目多批评论串行测试。
- [ ]`analysis_success_rate >= 0.8``analysis_status=normal` 测试。
- [ ]`analysis_success_rate < 0.8``analysis_status=insufficient` 测试。
- [ ] 实现固定 batch size 的整批重试。
- [ ] 实现成功率统计。
**验收**
- AI 成功率低于 80% 时不改变任务 `status`
- 页面后续可读取 `analysis_status` 提示分析不足。
### T14 内容条目级报告生成
**目标**:为每条内容生成预生成报告。
**涉及文件**
- 创建:`app/services/report_service.py`
- 创建:`app/prompts/report_summary.txt`
- 测试:`tests/unit/test_report_stats.py`
**步骤**
- [ ] 写情绪统计测试。
- [ ] 写标签 Top 5 统计测试。
- [ ] 写典型评论选取测试:优先点赞数,缺失时按抓取顺序。
- [ ] 写内容条目级报告字段测试。
- [ ] 写总结 AI 失败时默认文案测试。
- [ ] 实现内容条目级报告生成。
- [ ] 保存 `metrics_json``typical_comments_json``summary``markdown_content`
- [ ] 写测试:内容条目级报告总结 AI 的输入包含统计摘要和典型评论文本(每条截断为 150 字符)。
- [ ] 写测试:报告总结 AI 使用纯文本输出,不使用 JSON Schema 约束。
- [ ] 写测试:总结文本超过 200 字时自动截断至 200 字。
- [ ] 写测试:总结 AI 调用超时或失败时,summary 字段使用默认文案“总结生成失败,请查看上方统计数据。”,不阻断报告创建流程。
- [ ] 创建 `app/prompts/report_summary.txt`,编写内容条目级报告总结的 Prompt 模板。
**验收**
- 内容条目级报告统计与评论结构化结果一致。
- 总结 AI 失败不阻断报告创建。
### T15 热点级报告生成
**目标**:聚合热点下所有内容条目,生成热点级报告。
**涉及文件**
- 修改:`app/services/report_service.py`
- 测试:`tests/unit/test_report_stats.py`
**步骤**
- [ ] 写热点级报告聚合测试。
- [ ] 写内容条目数量、样本数、情绪数量一致性测试。
- [ ] 写热点 Top 5 标签统计测试。
- [ ] 写热点 Markdown 生成测试。
- [ ] 实现热点级报告生成。
- [ ] 任务完成后先生成内容条目报告,再生成热点报告。
- [ ] 写测试:热点级报告总结文本超过 300 字时自动截断至 300 字。
- [ ] 写测试:热点总结 AI 超时或失败时,热点报告 summary 字段使用默认文案,不阻断热点报告创建流程。
- [ ] 写测试:热点总结 AI 的输入聚合了该热点下所有内容条目的统计摘要。
**验收**
- 热点级报告可生成。
- 页面展示和导出可读取同一份报告。
### T16 AI + 报告集成到任务流程
**目标**:任务完成后评论有情绪和标签,报告已预生成。
**涉及文件**
- 修改:`app/services/task_service.py`
- 修改:`app/services/ai_service.py`
- 修改:`app/services/report_service.py`
- 测试:`tests/integration/test_task_flow_xiaohongshu.py`
- 测试:`tests/integration/test_task_flow_douyin.py`
**步骤**
- [ ] 扩展小红书集成测试,断言评论有 sentiment、labels。
- [ ] 扩展抖音集成测试,断言 reports 入库。
- [ ] 扩展失败容错测试,AI 单批失败不阻断其他批次。
- [ ] 在任务流程中调用 AI 分析。
- [ ] 在任务流程中调用报告生成。
- [ ] 写入 `analysis_success_rate``analysis_status`
- [ ] 每完成一批 AI 分析(20 条评论处理完毕)后立即执行 `session.commit()`,不等待所有批次完成后统一提交。
- [ ] 写测试:第一批 AI 分析完成后,数据库中对应评论的 `ai_analysis_status` 已更新,不需等待全部批次完成。
**验收**
- 两个平台任务 mock 全流程通过。
⏱ 时间盒约束(Day 3):若 T12/T13 AI 链路的 JSON Schema 解析失败边缘 case 超过 2 小时仍未解决,
立即切换为纯文本输出降级方案,保存原始响应至 raw_data 字段,推进至报告生成任务。
Prompt 调优不在 MVP 关键路径上,允许以降级方案通过验收。
---
## 8. Day 4:页面、导出、Docker 验收
### T17 任务详情页
**目标**:展示任务概览、热点列表和内容条目入口。
**涉及文件**
- 创建:`app/templates/tasks/detail.html`
- 修改:`app/main.py`
- 测试:`tests/integration/test_routes.py`
- 测试:`tests/unit/test_template_filters.py`
**步骤**
- [ ] 写任务详情页路由测试。
- [ ] 写状态 Badge 渲染测试。
- [ ] 实现任务概览看板:任务 ID、平台、创建时间、耗时、状态、AI 分析状态、进度、错误信息。
- [ ] 实现热点手风琴列表。
- [ ] 默认展开 rank=1 的热点。
- [ ] 内容条目失败时展示失败原因。
- [ ] 任务 running 且热点为空时展示 Spinner。
- [ ] 渲染任务详情页面包屑路径:首页 › 任务 `#{task_id}`
- [ ] 验收:面包屑“首页”链接指向 `/`,可点击跳转。
**验收**
- 用户可从任务列表进入任务详情。
- 可进入热点报告和内容条目详情。
- 热点报告入口在 T18 完成前仅验证链接存在(href 属性非空),不验证报告页内容。
### T18 热点级报告页
**目标**:展示热点级聚合报告。
**涉及文件**
- 创建:`app/templates/hotspots/report.html`
- 修改:`app/main.py`
- 测试:`tests/integration/test_routes.py`
**步骤**
- [ ] 写热点报告页路由测试。
- [ ] 展示热点基础信息、内容条目数量、评论样本数。
- [ ] 展示情绪条数和百分比。
- [ ] 展示 Top 5 标签。
- [ ] 展示典型评论。
- [ ] 展示 AI 总结和分析不足 Alert。
- [ ] 添加 Markdown 导出按钮。
- [ ] 添加热点下全部评论 CSV 导出按钮。
- [ ] 渲染热点报告页面包屑路径:首页 › 任务 `#{task_id}` 热点 `#{rank}``{title}` 汇总报告。
- [ ] 各层级面包屑均为可点击链接,末级“汇总报告”为当前页,不可点击。
**验收**
- 热点报告页能读取预生成报告。
- 报告缺失时显示友好状态,不 500。
### T19 内容条目详情页与评论明细
**目标**:展示单条视频 / 笔记的报告和评论明细。
**涉及文件**
- 创建:`app/templates/items/detail.html`
- 修改:`app/main.py`
- 测试:`tests/integration/test_routes.py`
- 测试:`tests/unit/test_template_filters.py`
**步骤**
- [ ] 写内容条目详情页路由测试。
- [ ] 展示内容条目基础信息和原始内容链接。
- [ ] 展示内容条目级报告。
- [ ] 评论明细最多展示 100 条。
- [ ] 评论按点赞数降序、评论时间降序排序。
- [ ] 标签 JSON Array 渲染为多个标签块。
- [ ] 评论为空时展示空状态。
- [ ] P1:添加 `<details>` 原始 JSON 调试入口。
- [ ] 渲染内容条目详情页面包屑路径:首页 › 任务 `#{task_id}` 热点 `#{rank}``{title}` `{item_title}`
**验收**
- 用户可查看评论明细、情绪、标签。
- 评论为空不报错。
- 评论明细排序规则说明:按点赞数降序;点赞数相同或缺失时按评论时间降序。此规则由 UIDesign 补充定义,不在 DevelopmentPlan 原始范围内,为产品决策。
### T20 导出服务
**目标**:实现 CSV 和 Markdown 导出。
**涉及文件**
- 创建:`app/services/export_service.py`
- 修改:`app/main.py`
- 测试:`tests/unit/test_export.py`
- 测试:`tests/integration/test_routes.py`
**接口**
- `GET /api/export/items/{item_id}/comments.csv`
- `GET /api/export/hotspots/{hotspot_id}/comments.csv`
- `GET /api/export/items/{item_id}.md`
- `GET /api/export/hotspots/{hotspot_id}.md`
**步骤**
- [ ] 写 CSV 编码测试,断言使用 `UTF-8-SIG`
- [ ] 写 CSV 字段测试。
- [ ] 写 labels 中文逗号拼接测试。
- [ ] 写 CSV 公式注入防护测试:`=``+``-``@` 开头加单引号。
- [ ] 写文件名安全处理测试。
- [ ] 写 Markdown 导出测试。
- [ ] 写入 CSV 时,将评论内容字段中的换行符(`\n``\r``\r\n`)替换为空格,防止换行符撕裂 CSV 行列结构。
- [ ] 写测试:评论内容包含换行符时,导出的 CSV 文件中该字段不包含换行符,行数与评论条数一致。
- [ ] 实现 CSV 导出。
- [ ] 实现 Markdown 导出。
- [ ] 写模板测试:`task.status=running` 时,导出按钮渲染结果中包含 `disabled` 属性。
- [ ] 写模板测试:`task.status=failed` 且无成功内容条目(`successful_items_count=0`)时,导出按钮渲染结果中包含 `disabled` 属性。
- [ ] 写模板测试:`item.status=crawl_failed` 时,内容条目详情页导出按钮渲染结果中包含 `disabled` 属性。
- [ ] 写模板测试:`task.status=success` 且有报告数据时,导出按钮渲染结果不包含 `disabled` 属性。
- [ ] 说明:以上测试验证的是“按钮在什么条件下变灰”的业务逻辑,不测试 CSS 颜色和视觉样式。
- [ ] 写测试:评论内容首字符为 `=` `+` `-` `@` 时,导出 CSV 中该字段首字符前添加单引号前缀。
**验收**
- CSV 可用 Excel 正常打开中文。
- Markdown 与页面报告使用同一份数据。
### T21 模板宏、过滤器与静态交互
**目标**:统一状态展示、情绪展示、标签展示和前端交互。
**涉及文件**
- 创建:`app/templates/macros/status_badge.html`
- 创建:`app/templates/macros/sentiment_badge.html`
- 创建:`app/templates/macros/label_tags.html`
- 修改:`app/main.py`
- 修改:`app/static/app.js`
- 修改:`app/static/app.css`
- 测试:`tests/unit/test_template_filters.py`
**步骤**
- [ ] 注册 `from_json` Jinja2 filter。
- [ ] 编写状态 Badge macro。
- [ ] 编写情绪 Badge macro。
- [ ] 编写标签列表 macro。
- [ ] 前端实现表单范围校验。
- [ ] 前端实现规模预估实时计算。
- [ ] 前端实现导出 Blob 下载。
- [ ] 严禁对外部平台内容使用 `|safe`
- [ ]`base.html` 中定义 `{% block title %}热榜评论分析工具{% endblock %}` 占位。
- [ ] 各页面模板按以下规范填充 title block
`index.html` → “任务列表 - 热榜评论分析工具”
`tasks/detail.html` → “任务 #{task.id} - 热榜评论分析工具”
`hotspots/report.html` → “{hotspot.title} 汇总报告 - 热榜评论分析工具”
`items/detail.html` → “{item.title} 详情 - 热榜评论分析工具”
- [ ] 写测试:各页面响应的 `<title>` 标签内容符合上述规范。
**验收**
- 模板测试通过。
- 状态文案集中管理。
### T22 Docker Compose 与部署
**目标**:实现一键启动。
**涉及文件**
- 创建:`Dockerfile`
- 创建:`docker-compose.yml`
- 修改:`.env.example`
**步骤**
- [ ] 编写 Dockerfile。
- [ ] 编写 docker-compose,包含 app 服务和 `./data:/app/data` 数据卷。
- [ ] 容器启动后执行数据库初始化。
- [ ] 暴露 `8000` 端口。
- [ ] 验证 SQLite 写入 `./data/app.db`
- [ ] Dockerfile 中添加非 root 用户:
`RUN adduser --disabled-password --gecos "" appuser`
`USER appuser`
- [ ] 确认 `./data` 挂载卷目录对 `appuser` 具有写入权限(可通过 chown 或目录权限设置实现)。
- [ ] 写测试:`docker-compose up` 后,容器内进程以非 root 用户运行(`whoami` 不返回 root)。
**验收命令**
```bash
docker compose up --build
curl -f http://localhost:8000/health
```
### T23 最终测试与手工验收
**目标**:完成 MVP 验收闭环。
**涉及文件**
- 修改:`README.md`(如项目已有;没有则可跳过)
- 修改:`docs/Tasks.md` 勾选完成项
**自动化测试命令**
```bash
pytest tests/unit -q
pytest tests/integration -q
pytest tests/unit tests/integration --cov=app --cov-branch --cov-report=term-missing
```
**手工验收清单**
- [ ] Docker Compose 启动成功。
- [ ] 首页可访问。
- [ ] 创建小红书默认任务。
- [ ] 刷新任务状态直到完成。
- [ ] 查看热点列表。
- [ ] 查看热点级报告。
- [ ] 查看内容条目详情。
- [ ] 查看评论明细。
- [ ] 导出评论 CSV。
- [ ] 导出热点 Markdown。
- [ ] 导出内容条目 Markdown。
- [ ] 创建抖音默认任务并重复上述流程。
- [ ] 非法配置值被前后端拦截。
- [ ] 任务失败时展示失败阶段和错误类型。
**验收**
- MVP 关键验收清单全部通过,或记录明确缺口与降级说明。
---
## 9. P1 可选任务
以下任务在 P0 完成后再做。
### P1-01 任务列表自动轮询
- [ ] 使用 HTMX 或原生 JS 每 5 秒刷新运行中任务。
- [ ] 所有任务完成后自动停止轮询。
- [ ] 保留手动刷新按钮。
- [ ] 将任务列表行(`<tr>` 集合)抽离为独立局部模板 `app/templates/partials/task_rows.html`
- [ ]`index.html``<tbody>` 中使用 `{% include "partials/task_rows.html" %}` 初始渲染。
- [ ] 配置 HTMX 属性:`hx-get="/partials/tasks"``hx-trigger="every 5s [条件]"``hx-target="#task-table-body"``hx-swap="innerHTML"`
- [ ] 所有任务状态均不为 running 时,动态移除 `hx-trigger` 属性,停止轮询,避免无效请求。
### P1-02 原始 JSON 调试入口
- [ ] 在内容条目详情页用 `<details>` 展示 raw_data。
- [ ] 不作为正式用户功能入口突出展示。
### P1-03 基础进度条
- [ ] 在任务列表和任务详情展示 Bootstrap Progress Bar。
- [ ] 数据来源只使用后端 `processed_items_count``total_items_count``successful_items_count`
---
## 10. 开发顺序依赖
```text
T01 项目骨架
→ T02 配置
→ T03 数据模型
→ T04 任务创建
→ T05 僵尸任务恢复
→ T06 首页
→ T07 API 客户端
→ T08 小红书链路
→ T09 抖音链路
→ T10 评论分页
→ T11 抓取任务集成
→ T12 AI Schema
→ T13 AI 重试与成功率
→ T14 内容条目报告
→ T15 热点报告
→ T16 AI + 报告任务集成
→ T17 任务详情
→ T18 热点报告页
→ T19 内容详情页
→ T20 导出
→ T21 模板与静态交互
→ T22 Docker
→ T23 最终验收
```
注:T08(小红书字段映射)与 T09(抖音字段映射)逻辑上彼此独立,均以 T07(API 客户端)为前置依赖,
可并行开发。当前顺序为单人开发推荐执行序,多人协作时可同步开展。
依赖关系图示:
```text
T07 API 客户端
├── T08 小红书链路 ─┐
└── T09 抖音链路 ─┴→ T10 评论分页 → T11 抓取集成
```
---
## 11. 风险与降级
| 风险 | 表现 | 降级策略 |
|---|---|---|
| TikHub 字段变化 | 字段为空或映射失败 | 保留 raw_data,字段映射使用多候选字段 |
| 平台 API 限流 | 任务变慢或部分内容失败 | 429 指数退避,超过重试后跳过当前条目 |
| AI 输出不稳定 | JSON 解析失败 | JSON Schema 校验、最多 3 次整批重试;失败批次标记 failed,不阻断其他批次 |
| AI 成本或耗时过高 | 任务执行时间过长 | 降低抓取规模或 AI batch size |
| SQLite 写入冲突 | 任务失败 | 单 worker + WAL + timeout |
| 容器重启 | running 任务卡住 | lifespan 中恢复为 failed |
| 页面轮询未做 | 用户不知道进度 | P0 手动刷新 + 创建时间兜底 |
---
## 12. 完成定义
当以下条件全部满足时,MVP 开发任务视为完成:
1. P0 任务 T01T23 完成。
2. 单元测试和集成测试通过。
3. Docker Compose 可启动。
4. 小红书默认任务可完成抓取、分析、报告和导出。
5. 抖音默认任务可完成抓取、分析、报告和导出。
6. 页面可查看任务、热点、热点报告、内容条目详情和评论明细。
7. 任务失败时展示失败阶段和错误类型。
8. 敏感 Key 未写入代码仓库。
---
## 变更日志
| 日期 | 版本 | 变更内容 |
|---|---|---|
| 2025-07-10 | v1.0 | 初始版本 |
| 2025-07-10 | v1.1 | 基于双审阅报告合并修订(共 23 条指令):T04 补充并发任务拦截逻辑(已有 running 任务时返回 400);T02 补充 CRAWL_PAGE_INTERVAL_SECONDS 配置项;T03 补充 3 张表的性能预留索引步骤;T06 修正任务创建成功后跳转行为(删除“或”,明确跳转至 /tasks/{id}),补充规模预估实时计算步骤,补充面包屑导航实现步骤;T07 补充 httpx 同步实例类型测试和 429 退避测试,声明 http_429_response.json fixtureT08/T09 补充缺失字段容错测试和对应 Fixture 文件声明;T11 补充事务粒度约束(每条目 commit)和跨热点重复条目集成测试;T12 补充 ai_comments_all_sentiments.json fixture 和四情绪统计测试;T13 移除 batch 减半逻辑,改为纯“最多 3 次整批重试”与 DevelopmentPlan §7.1 保持一致;T14/T15 补充报告总结 AI 请求测试步骤(输入构造、截断、失败降级),补充 prompts/report_summary.txt 创建步骤;T16 补充批次级事务 commit 约束;T17/T18/T19 各补充面包屑渲染步骤,T17 验收注明热点报告入口仅验证链接存在;T19 补充评论排序规则来源说明;T20 补充 CSV 换行符替换逻辑和测试,将“按钮由模板控制”替换为 4 条具体业务逻辑测试(仅测 disabled 条件,不测视觉样式),补充公式注入测试;T21 补充 4 个页面的 <title> 命名规范实现步骤;T22 补充 Dockerfile 非 root 用户步骤;P1-01 补充 partials/task_rows.html 抽离和轮询停止逻辑;§10 补充 T08/T09 并行关系图示;Day 2/Day 3 补充时间盒约束策略说明。 |