308 lines
10 KiB
Markdown
308 lines
10 KiB
Markdown
# Steam 社区监控一期计划
|
||
|
||
## 目标
|
||
|
||
第一阶段先接入 Steam 两个信息源:
|
||
|
||
1. Steam 评测信息
|
||
2. Steam 讨论社区信息:`https://steamcommunity.com/app/3774440/discussions`
|
||
|
||
系统每 30 分钟刷新一次。第一轮全量抓取 Steam 评测、讨论区主题和讨论区回复;后续只做增量更新。所有新增内容调用 OpenRouter 的 `deepseek/deepseek-v4-pro` 做分类和回复必要性评估,并在 dashboard 中展示、筛选、高亮和追踪人工处理状态。
|
||
|
||
## 已确认事实
|
||
|
||
| 判断 | 类型 | 证据 | 决策影响 |
|
||
|---|---|---|---|
|
||
| AppID 为 `3774440` 的 Steam 评测 API 当前有数据 | 当前事实 | 本地请求 `https://store.steampowered.com/appreviews/3774440?...` 成功,返回 `total_reviews=130`、`review_score_desc=Very Positive` | 一期可以直接接入评测 API |
|
||
| Steam 讨论区页面当前可访问 | 当前事实 | 本地请求 `https://steamcommunity.com/app/3774440/discussions/` 返回 HTTP 200,页面包含 forum/topic 内容 | 一期可以用 HTTP + HTML 解析抓讨论区 |
|
||
| `deepseek/deepseek-v4-pro` 当前存在于 OpenRouter 模型列表 | 当前事实 | 本地请求 OpenRouter models API 返回该模型,支持 `response_format` 和 `structured_outputs` | 一期可按结构化 JSON 分类设计 |
|
||
| Steam 评测数量存在口径差异风险 | 经验事实 | 用户级经验记录:Steam `appreviews` 受缓存、语言、购买类型和索引延迟影响 | 统计口径不能只依赖单一请求 |
|
||
|
||
## 一期范围
|
||
|
||
### 做
|
||
|
||
- 每 30 分钟刷新 Steam 评测和 Steam 讨论区。
|
||
- 第一轮全量抓取;后续增量抓取新增或更新内容。
|
||
- 对 Steam 评测、讨论区主题、讨论区回复分别去重入库。
|
||
- 调用 OpenRouter 模型输出结构化分类结果。
|
||
- Dashboard 展示评论/帖子/回复列表、分类结果、原始链接、回复建议和人工处理状态。
|
||
- 支持本机运行,架构上预留服务器部署。
|
||
|
||
### 暂不做
|
||
|
||
- 暂不接入 Steam 以外社区。
|
||
- 暂不做复杂账号权限系统;服务器部署前再补认证方案。
|
||
- 暂不自动回复玩家,只做信息发现、分类和处理追踪。
|
||
- 暂不做语言筛选;所有语言统一进入采集和模型评估。
|
||
|
||
## 采集流程
|
||
|
||
### Steam 评测
|
||
|
||
使用 Steam Store Reviews API:
|
||
|
||
```text
|
||
GET https://store.steampowered.com/appreviews/3774440
|
||
```
|
||
|
||
基础参数:
|
||
|
||
- `json=1`
|
||
- `num_per_page=100`
|
||
- `language=all`
|
||
- `filter=recent`
|
||
- `purchase_type=all`
|
||
- `cursor=*` 起步,后续使用响应中的 cursor 翻页
|
||
|
||
评测去重主键:
|
||
|
||
- `steam_review:{recommendationid}`
|
||
|
||
评测建议保留字段:
|
||
|
||
- `recommendationid`
|
||
- `voted_up`
|
||
- `review`
|
||
- `language`
|
||
- `timestamp_created`
|
||
- `timestamp_updated`
|
||
- `author.steamid`
|
||
- `author.personaname`
|
||
- `author.profile_url`
|
||
- `author.playtime_forever`
|
||
- `votes_up`
|
||
- `comment_count`
|
||
- `steam_purchase`
|
||
- `received_for_free`
|
||
- `source_url`
|
||
|
||
评测链接可由 `recommendationid` 构造:
|
||
|
||
```text
|
||
https://steamcommunity.com/profiles/{steamid}/recommended/3774440/#developer_response
|
||
```
|
||
|
||
若用户 profile URL 可用,也应保留原始 `profile_url` 作为辅助追溯字段。
|
||
|
||
### Steam 讨论区
|
||
|
||
使用 HTTP 请求讨论区列表页:
|
||
|
||
```text
|
||
https://steamcommunity.com/app/3774440/discussions/
|
||
```
|
||
|
||
翻页参数:
|
||
|
||
```text
|
||
?fp=2
|
||
?fp=3
|
||
```
|
||
|
||
第一轮抓取所有可访问讨论页和所有可访问回复。后续增量刷新时,从最新列表页开始向后翻页,直到遇到本地已存在且未更新的主题为止;若 Steam 页面无法稳定判断更新时间,则以最近若干页作为增量窗口,并保留手动全量重扫入口。
|
||
|
||
讨论区去重主键:
|
||
|
||
- 主题:`steam_discussion_topic:{topic_id}`
|
||
- 回复:`steam_discussion_reply:{topic_id}:{reply_id}`,如果页面拿不到稳定 reply id,则用 `topic_id + author + timestamp + content_hash`
|
||
|
||
讨论区建议保留字段:
|
||
|
||
- `topic_id`
|
||
- `topic_url`
|
||
- `title`
|
||
- `author`
|
||
- `published_at_text`
|
||
- `content`
|
||
- `reply_count`
|
||
- `reply_author`
|
||
- `reply_time_text`
|
||
- `reply_content`
|
||
- `reply_url`
|
||
- `source_url`
|
||
|
||
## 数据模型
|
||
|
||
建议先用 SQLite 跑通本机版本;部署服务器时可迁移 PostgreSQL。
|
||
|
||
核心表可以先压成三类:
|
||
|
||
### `raw_items`
|
||
|
||
保存原始社区内容及来源信息。
|
||
|
||
关键字段:
|
||
|
||
- `id`
|
||
- `source`
|
||
- `source_item_id`
|
||
- `source_url`
|
||
- `content_type`
|
||
- `author_id`
|
||
- `author_name`
|
||
- `published_at`
|
||
- `collected_at`
|
||
- `content`
|
||
- `raw_json`
|
||
- `content_hash`
|
||
|
||
### `analysis_results`
|
||
|
||
保存模型分类结果。
|
||
|
||
关键字段:
|
||
|
||
- `raw_item_id`
|
||
- `model`
|
||
- `sentiment`
|
||
- `is_positive`
|
||
- `is_negative`
|
||
- `has_actionable_feedback`
|
||
- `feedback_types`
|
||
- `reply_recommended`
|
||
- `reply_priority`
|
||
- `reply_suggestion`
|
||
- `summary`
|
||
- `priority`
|
||
- `confidence`
|
||
- `model_json`
|
||
- `analyzed_at`
|
||
|
||
### `work_items`
|
||
|
||
保存人工处理状态。
|
||
|
||
关键字段:
|
||
|
||
- `raw_item_id`
|
||
- `status`
|
||
- `owner`
|
||
- `notes`
|
||
- `last_handled_at`
|
||
- `created_at`
|
||
- `updated_at`
|
||
|
||
状态枚举建议:
|
||
|
||
- `new`
|
||
- `read`
|
||
- `needs_reply`
|
||
- `replied`
|
||
- `needs_fix`
|
||
- `archived`
|
||
|
||
## OpenRouter 分类方案
|
||
|
||
模型:
|
||
|
||
```text
|
||
deepseek/deepseek-v4-pro
|
||
```
|
||
|
||
OpenRouter Key:
|
||
|
||
- 本机和服务器都使用 `.env` / 环境变量读取,不在项目文件中明文保存。
|
||
- 用户级 `auth.json` 只作为本机开发时迁移 key 的来源,不作为项目运行时依赖。
|
||
- 推荐变量名:`OPENROUTER_API_KEY`。
|
||
|
||
目标输出 JSON:
|
||
|
||
```json
|
||
{
|
||
"sentiment": "positive | negative | mixed | neutral",
|
||
"is_positive": true,
|
||
"is_negative": false,
|
||
"has_actionable_feedback": true,
|
||
"feedback_types": ["bug", "suggestion", "balance", "ui", "localization", "performance", "pricing", "content", "question", "other"],
|
||
"reply_recommended": true,
|
||
"reply_priority": "none | low | medium | high",
|
||
"reply_suggestion": "建议运营或开发如何回复;不需要回复时为空字符串",
|
||
"summary": "一句话摘要",
|
||
"priority": "low | medium | high",
|
||
"confidence": 0.0,
|
||
"reason": "简短分类依据"
|
||
}
|
||
```
|
||
|
||
分类规则:
|
||
|
||
- `is_positive` / `is_negative` 对应用户要求的好评、差评展示。
|
||
- `has_actionable_feedback=true` 表示包含具体建议、问题反馈、bug、平衡性、UI、翻译、本地化、性能、价格、内容量等可处理信息。
|
||
- `reply_recommended=true` 表示建议人工回复或处理,高优先级内容需要在 dashboard 高亮。
|
||
- 讨论区主题和回复都必须进入模型评估;不能只评估主题原帖。
|
||
- Steam 评测本身的 `voted_up` 作为强信号,但不要覆盖文本判断;例如推荐评测里也可能包含具体差评点。
|
||
- 每条结果必须保留 `source_url`,dashboard 中直接跳转原始评论或讨论帖。
|
||
|
||
## Dashboard 一期页面
|
||
|
||
第一版页面不追求复杂,重点是运营处理效率。
|
||
|
||
建议视图:
|
||
|
||
- 总览指标:新增数量、未处理数量、差评数量、具体反馈数量、高优先级数量、已分析数量、待补跑数量、最近更新时间。
|
||
- 内容列表:来源、内容类型、时间、作者、摘要、情绪、反馈类型、优先级、是否建议回复、处理状态、原始链接。
|
||
- 筛选:信息源、内容类型、情绪、是否具体反馈、是否建议回复、反馈类型、处理状态、时间范围。
|
||
- 高亮:`reply_recommended=true` 或 `priority=high` 的帖子/回复。
|
||
- 详情:原文、模型分类、回复建议、原始链接、备注、负责人、状态变更。
|
||
- 排序:建议回复优先;同组内按发布时间新到旧。
|
||
|
||
## 定时与失败处理
|
||
|
||
定时:
|
||
|
||
- 默认每 30 分钟执行一次采集任务。
|
||
- 第一轮执行全量抓取;全量完成后记录同步游标、已见主题、已见回复和评测 cursor/时间水位。
|
||
- 首轮全量建议支持断点续跑:每完成一页讨论列表、一个主题详情、一个评测分页后写入进度,失败后从最近进度恢复。
|
||
- 首轮全量不建议设置过小页数上限,否则会破坏“全抓”目标;建议设置安全保护,例如单次最多连续运行 2 小时或最多抓取 500 页,并允许下次继续。
|
||
- 本机先用应用内 scheduler 或命令行手动触发验证;服务器部署时再选 systemd timer、cron 或队列 worker。
|
||
|
||
失败处理:
|
||
|
||
- Steam 请求失败:记录错误,下一轮重试,不删除旧数据。
|
||
- OpenRouter 请求失败:保留 raw item,标记 `analysis_pending`,下一轮或手动补跑。
|
||
- JSON 解析失败:保存模型原始输出,进入待复核状态。
|
||
- 重复采集:通过 source item id 和 content hash 去重。
|
||
|
||
## 部署前提
|
||
|
||
本机 MVP:
|
||
|
||
- 本地数据库
|
||
- 本地 dashboard
|
||
- 从 `.env` 读取 OpenRouter API Key
|
||
- 手动或定时刷新
|
||
|
||
服务器部署前需要补充:
|
||
|
||
- 访问认证
|
||
- 持久化数据库位置和备份策略
|
||
- 后台任务运行方式
|
||
- 日志与错误告警
|
||
- OpenRouter 调用预算和速率控制
|
||
- Steam 抓取频率和 User-Agent 策略
|
||
|
||
## 已定实现决策
|
||
|
||
- 密钥配置:使用 `.env` / 环境变量,变量名 `OPENROUTER_API_KEY`。
|
||
- 首轮抓取:全量抓取,支持断点续跑;用运行时间或高页数阈值做安全保护,不用小页数上限替代全量目标。
|
||
- 负责人字段:按小团队制作人/处理人文本字段设计,暂不接用户账号系统。
|
||
|
||
## 当前实现状态
|
||
|
||
- 已实现 Python/FastAPI + SQLite MVP。
|
||
- 已实现 Steam 评测 API 抓取。
|
||
- 已实现 Steam 讨论区主题与回复抓取。
|
||
- 已实现 OpenRouter `deepseek/deepseek-v4-pro` 结构化分类。
|
||
- 已实现 dashboard、手动同步、后台 30 分钟增量同步、处理状态更新。
|
||
- 已实现局域网服务监听 `0.0.0.0:8000`。
|
||
- 已实现 Steam 讨论区中文时间解析,支持 `x 小时以前`、`3 月 7 日 下午 4:52`、`2025 年 8 月 9 日 下午 3:29`。
|
||
- 已补跑完成 2026-05-01 之后 209 条内容的 AI 分析。
|
||
|
||
## 后续平台接入约束
|
||
|
||
- 新平台不要复制 Steam 私有逻辑;应新增平台采集器,输出统一 `RawItem`。
|
||
- 新平台继续复用 `raw_items`、`analysis_results`、`work_items`。
|
||
- 每个平台必须明确稳定去重主键、原始链接、发布时间解析、首轮全量和后续增量策略。
|
||
- 需要登录态或浏览器自动化的平台,先单独做方案和当前事实验证,再接入同步链路。
|