38 KiB
38 KiB
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 引擎初始化时须启用以下配置:
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(
<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_URLAI_API_KEYAI_MODELAI_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 必须实现
- Docker Compose 启动系统,并能通过浏览器访问。
- 首页 / 任务列表支持选择平台并手动创建任务。
- 任务创建页面支持抓取规模配置:
- 热点关键词数量上限:默认 5,范围 1–10;
- 每热点内容条目数上限:默认 5,范围 1–10;
- 每内容条目评论数上限:默认 50,范围 10–100。
- 小红书链路:
- 热榜;
- 热榜标题搜索笔记;
- 笔记一级评论。
- 抖音链路:
- 创作者热点榜单;
- 热点标题搜索视频;
- 视频一级评论。
- 评论分页抓取,默认最多 50 条,配置最多 100 条,最大翻页轮次 5。
- 数据入库并保留原始 API 响应。
- AI 评论级结构化分析。
- 预生成内容条目级报告和热点级报告。
- 页面查看任务、热点、内容条目、报告和评论明细。
- 导出:
- CSV 评论明细;
- Markdown 热点级报告;
- Markdown 内容条目级报告。
- 基础容错:
- 单条内容条目失败不阻断整批任务;
- HTTP 429 指数退避;
- AI 输出解析失败重试。
3.2 P1 建议实现
- 任务列表自动轮询。
- 基础进度展示:
processed_items_count / total_items_countsuccessful_items_count / total_items_count
- 内容条目详情页开发调试 JSON 入口。
3.3 P2 明确不做
- 定时任务。
- 多用户 / 登录 / 权限。
- 分布式任务队列。
- 复杂任务恢复、自动补跑、单条重试按钮。
- 二级评论抓取。
- Top 50 以上热点或单内容 200 条以上评论。
- 平台级日报、跨热点深度洞察。
- Excel 导出、正式 JSON 导出。
- 任务取消 / 中断:用户主动终止正在运行的任务。
- 历史数据自动清理:基于时间策略自动删除过期任务数据。
- 搜索笔记分页:搜索接口翻页获取更多候选内容条目。
- 热点级 CSV 导出的高级格式定制。
4. 系统架构
4.1 架构形态
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 推荐目录结构
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启动事件中,执行以下逻辑:
# 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 数据量下非必需,当性能出现瓶颈时可添加:
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 小红书链路
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 抖音链路
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 评论分页策略
统一终止条件:
- 已抓取评论数达到
comment_limit_per_item。 - API 返回评论列表为空。
- 达到最大翻页轮次 5。
- 连续请求失败且超过重试次数。
分页实现要求:
- 小红书使用
cursor/index字段推进。 - 抖音使用
cursor字段推进,单次count=20。 - 若 API 未返回明确下一页游标,则停止翻页。
分页请求间隔
- 每次评论分页请求之间须等待 1-2 秒(建议默认 1.5 秒),降低触发平台限流的概率。
- 该间隔独立于 §6.1 的 429 指数退避策略;收到 429 响应后切换为退避策略,退避结束后恢复基础间隔。
7. AI 分析方案
7.1 评论级输出 Schema
AI 评论分析必须返回 JSON Array,每一项对应一条输入评论。
[
{
"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 格式:
[
{ "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 质量状态
任务完成后计算:
analysis_success_rate = analysis_success_comments / total_comments
判定:
analysis_success_rate >= 0.8:analysis_status = normalanalysis_success_rate < 0.8:analysis_status = insufficient
注意:
analysis_status不改变任务status。- 任务生命周期状态仍只有
running/success/failed。 - 页面须展示 AI 分析成功率或“分析不足”提示。
8. 报告生成方案
8.1 内容条目级报告
输入:
- 内容条目基础信息;
- 该内容条目下所有已分析评论;
- 情绪和标签结构化结果。
生成步骤:
- 统计评论样本数。
- 统计正向、负向、中性、未知数量和占比。
- 按标签字面值统计 Top 5。
- 按情绪分组选取典型评论:
- 优先按点赞数降序;
- 点赞数缺失时按抓取顺序。
- 调用 AI 生成内容条目总结。
- 生成 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 热点级报告
输入:
- 热点基础信息;
- 热点下所有内容条目;
- 热点下所有已分析评论;
- 内容条目级统计结果。
生成步骤:
- 聚合内容条目数量。
- 聚合评论样本数。
- 聚合情绪分布。
- 聚合 Top 5 标签。
- 选取典型评论。
- 调用 AI 生成热点总结。
- 生成 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 创建任务请求
{
"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 字符时截断。- 文件名中的非法字符(包括但不限于
/、\、:、*、?、"、<、>、|)统一替换为下划线_。 - 连续多个下划线合并为单个下划线。
字段:
- 平台;
- 任务 ID;
- 热点 ID;
- 热点标题;
- 内容条目 ID;
- 内容条目标题;
- 评论 ID;
- 评论内容;
- 情绪倾向;
- 方向标签;
- 点赞数;
- 评论时间。
11.2 Markdown 报告
- 直接读取
reports.markdown_content。 - 响应头设置下载文件名。
- 页面展示与 Markdown 导出必须来自同一份报告数据。
12. 配置与部署
12.1 环境变量
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 服务和一个数据卷:
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 手工验收
docker compose up --build启动。- 打开首页。
- 创建小红书默认规模任务。
- 刷新任务列表直到任务完成。
- 查看热点列表、热点报告、内容条目详情、评论明细。
- 导出 CSV 和 Markdown。
- 创建抖音默认规模任务并重复验收。
- 手动填入非法配置值,确认前后端均阻止提交。
15. 开发周期安排
若从 2026-07-01 开始,建议排期如下。
Day 1:项目骨架、数据模型、任务框架
目标:
- FastAPI 项目可启动;
- SQLite 表结构完成;
- 任务创建和状态流转可用;
- 页面能创建任务并看到任务列表。
任务:
- 初始化项目结构。
- 编写配置管理和
.env.example。 - 定义 SQLAlchemy 数据模型。
- 实现数据库初始化。
- 实现任务创建 API。
- 实现任务列表页面。
- 实现
/health。 - 编写基础单元测试。
验收:
- 本地启动后可创建一条空任务;
- 任务列表展示平台、创建时间、状态;
- Docker Compose 能启动 app。
Day 2:平台抓取链路
⚠️ 排期风险备注:Day 2 优先完成小红书完整链路(搜索 + 笔记详情 + 评论含分页 + 字段映射 + raw_data 保存)。若进度受阻,抖音链路可延至 Day 3 上午。判断标准:如果到 Day 2 下午 4 点小红书链路尚未跑通端到端测试,立即停止并将抖音推迟。
目标:
- 小红书和抖音最小链路工程化;
- 热点、内容条目、评论可入库;
- 分页、限流和字段兼容策略落地。
任务:
- 实现 TikHub HTTP client。
- 实现小红书热点、笔记、评论抓取。
- 实现抖音热点、视频、评论抓取。
- 实现评论分页与 429 退避。
- 实现 raw_data 保存。
- 实现任务进度统计字段。
- 编写字段映射和分页测试。
验收:
- 默认规模可抓取至少一个平台的真实数据;
- 小红书 / 抖音链路均可在 mock 测试中通过;
- 字段缺失不导致整批任务崩溃。
Day 3:AI 分析与报告生成
⚠️ 排期调整说明:若抖音链路从 Day 2 延入,Day 3 上午优先完成抖音链路,下午实现 AI 分析 + 报告生成。报告的 Markdown 排版以信息可读为标准,不追求视觉效果,必要时直接使用字符串拼接。
目标:
- 评论级结构化分析可用;
- AI 输出校验、重试和质量状态可用;
- 内容条目级和热点级报告预生成。
任务:
- 编写评论分析 JSON Schema。
- 实现 AI client。
- 实现批量评论分析。
- 实现 AI 解析失败重试。
- 实现
analysis_success_rate和analysis_status。 - 实现情绪 / 标签统计。
- 实现典型评论选取。
- 实现报告总结和 Markdown 生成。
- 编写 AI schema、统计和报告测试。
验收:
- 任务完成后评论有情绪和标签;
- 报告统计与评论明细一致;
- AI 成功率低于 80% 时页面可见分析不足提示。
Day 4:页面、导出、Docker 验收
目标:
- 所有页面可用;
- CSV / Markdown 导出可用;
- Docker Compose 端到端验收通过;
- 文档与环境示例补齐。
任务:
- 完成热点与内容条目列表页。
- 完成热点级报告页。
- 完成内容条目详情页和评论明细。
- 完成 CSV 导出。
- 完成 Markdown 导出。
- 完成前端手动刷新和配置校验。
- 完成 Dockerfile 和 docker-compose.yml。
- 执行端到端手工验收。
- 修复高优先级问题。
验收:
- 两个平台至少各跑通一次默认任务;
- 页面与导出内容一致;
- 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 完成时必须满足:
- 系统可通过 Docker Compose 启动,并能在浏览器访问。
- 用户可从页面选择小红书或抖音并手动触发任务。
- 用户可配置热点数、每热点内容条目数、每内容评论数,且非法值无法提交。
- 系统可获取默认 Top 5 热点。
- 系统可为每个热点拆分默认最多 5 条内容条目。
- 系统可为每条内容条目抓取默认最多 50 条一级评论。
- 任务内至少 80% 的评论成功生成情绪分类和方向标签;低于 80% 时
analysis_status标记为分析不足。 - 系统可生成热点级汇总报告。
- 系统可生成内容条目级分析报告。
- 页面可查看任务列表、热点列表、热点级报告、内容条目详情和评论明细。
- 用户可导出
UTF-8-SIG编码的 CSV 评论明细。 - 用户可导出 Markdown 热点级汇总报告。
- 用户可导出 Markdown 内容条目级报告。
- 任务失败时,页面可展示失败状态,错误原因至少包含失败阶段和错误类型。
- 报告统计数据与评论结构化结果一致。
18. 后续文档衔接
DevelopmentPlan 完成后,建议继续产出:
UIDesign.md- 页面信息结构;
- 表单布局;
- 任务状态展示;
- 报告页展示结构。
TDD.md- 单元测试、集成测试、端到端验收用例;
- mock API 响应样例;
- AI 输出解析失败用例。
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 条修订指令。 |