# 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 条修订指令。 |