Files
hot_comment_radar/docs/Tasks.md
T

36 KiB
Raw Blame History

Tasks.md:小红书 / 抖音热榜评论抓取 + AI 分析报告工具

1. 文档信息

  • 文档阶段:Tasks(开发任务拆解)
  • 需求依据:docs/PRD.mddocs/FeatureSummary.md
  • 技术依据:docs/DevelopmentPlan.md
  • UI 依据:docs/UIDesign.md
  • 测试依据:docs/TDD.md
  • API Spike 依据:docs/API-Spike-Xiaohongshu.mddocs/API-Spike-Douyin.md
  • 当前目标:将 MVP 拆解为 4 天内可执行、可测试、可验收的开发任务

2. 开发总原则

  1. 遵循 TDD:先写失败测试,再写最小实现,再重构。
  2. MVP 优先:先跑通主链路,再做 P1 优化。
  3. 不使用真实 TikHub / AI API 作为单元测试依赖。
  4. 外部 API、AI 响应、字段缺失、限流等场景必须通过 mock 覆盖。
  5. 任务主状态只使用 running / success / failedAI 质量使用 analysis_statusanalysis_success_rate 表示,不新增“部分失败”任务状态。
  6. 页面展示和导出必须读取同一份结构化报告数据。
  7. API Key、AI Key 不写入代码仓库。

3. 目标目录结构

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.tomlrequirements.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=Falsetimeout=10、WAL。
  • 实现 Base.metadata.create_all(engine) 启动初始化。
  • 实现 tasks.analysis_statusanalysis_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_idstatus
  • 写非法参数测试:平台非法、热点数量越界、内容条目数越界、评论数越界。
  • 实现 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=systemerror_type=unexpected_restarterror_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,默认展示 12505×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
  • Fixturetests/fixtures/xhs_hot_list.json
  • Fixturetests/fixtures/xhs_search_notes.json
  • Fixturetests/fixtures/xhs_comments_page_1.json
  • Fixturetests/fixtures/xhs_comments_page_2_empty.json
  • 新建:tests/fixtures/xhs_comments_missing_fields.jsonlike_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
  • Fixturetests/fixtures/douyin_hot_list.json
  • Fixturetests/fixtures/douyin_search_videos.json
  • Fixturetests/fixtures/douyin_comments_page_1.json
  • 新建:tests/fixtures/douyin_comments_missing_fields.jsonlike_count / create_time 字段缺失的抖音评论样本)

步骤

  • 写热点字段映射测试:query_idtitlerankhot_score
  • 写视频字段映射测试:aweme_info.aweme_iddescauthorstatistics
  • 写评论字段映射测试: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_countsuccessful_items_countfailed_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
  • Fixturetests/fixtures/ai_comments_success.json
  • Fixturetests/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.8analysis_status=normal 测试。
  • analysis_success_rate < 0.8analysis_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_jsontypical_comments_jsonsummarymarkdown_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_rateanalysis_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)。

验收命令

docker compose up --build
curl -f http://localhost:8000/health

T23 最终测试与手工验收

目标:完成 MVP 验收闭环。

涉及文件

  • 修改:README.md(如项目已有;没有则可跳过)
  • 修改:docs/Tasks.md 勾选完成项

自动化测试命令

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_counttotal_items_countsuccessful_items_count

10. 开发顺序依赖

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 客户端)为前置依赖, 可并行开发。当前顺序为单人开发推荐执行序,多人协作时可同步开展。

依赖关系图示:

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 个页面的