Imported from anserIndicus/community-research-skills (
topic-extractor/SKILL.md). Install upstream withnpx skills add anserIndicus/community-research-skills --skill topic-extractor. Copyright stays with the author.
话题聚类提取器 Topic Extractor v2
从大量社区讨论中自动发现「用户在聊什么」,构建话题层级树,按热度和情感排序。
1. 核心原则
- 自底向上发现:不预设话题类别,从数据中自动涌现
- 层级结构:话题有粗粒度和细粒度之分,构建 2-3 层的话题树
- 每个话题有证据:每个话题都附带代表性帖子/评论,不是空洞的标签
- 热度 ≠ 重要性:高热度话题需要关注,但低热度的深度讨论也可能包含关键洞察
- 话题可重叠:一条内容可以同时属于多个话题
2. 话题提取流程
Step 1:内容预处理
从 voice-analyzer 的输出中提取分析所需的字段:
每条内容的可用信息:
- content(原文)
- sentiment(情感极性)
- attitude_tags(态度标签)
- features_mentioned(功能提及)
- scenarios(使用场景)
- engagement(互动数据)
Step 2:话题发现(两种策略)
策略 A:LLM 语义聚类(推荐,数据量 < 500 条)
- 将所有内容的摘要(前 100 字 + 功能提及 + 态度标签)喂给 LLM
- 要求 LLM 识别出 8-15 个话题类别
- 对每条内容标记所属话题(可多标记)
Prompt 模板:
以下是 {N} 条用户讨论 {产品名} 的内容摘要。
请识别出用户讨论的主要话题(8-15 个),并为每个话题:
1. 给出简短的话题名称(2-6 字)
2. 给出一句话描述
3. 列出属于该话题的内容编号
要求:
- 话题应该是用户视角的(如「加载太慢」而非「性能优化」)
- 话题粒度适中(不要太细如「iOS 端闪退」,也不要太粗如「体验差」)
- 一条内容可以属于多个话题
- 如果有些内容不属于任何明显话题,归入「其他」
策略 B:关键词 + 功能维度聚类(数据量 > 500 条)
- 用
sandbox_execute对所有内容做关键词提取(TF-IDF 或 jieba) - 结合
voice-analyzer已提取的features_mentioned,按功能维度初步分组 - 在每个功能维度内,用关键词相似度做细分
- 用 LLM 对聚类结果做人类可读的命名和描述
# sandbox_execute 示例
import jieba
import jieba.analyse
from collections import Counter, defaultdict
# 按功能维度初步分组
feature_groups = defaultdict(list)
for item in data:
for f in item['voice_analysis']['features_mentioned']:
feature_groups[f['category']].append(item)
# 每个组内提取关键词做细分
for category, items in feature_groups.items():
all_text = ' '.join(item['content'] for item in items)
keywords = jieba.analyse.extract_tags(all_text, topK=20, withWeight=True)
print(f"\n{category}:")
for kw, weight in keywords:
print(f" {kw}: {weight:.4f}")
Step 3:构建话题层级树
将发现的话题组织为 2-3 层的树状结构:
话题树示例:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📂 性能问题(L1,总提及 76 次)
├── 🔴 加载速度慢(L2,38 次,情感净值 -78%)
│ ├── 打开文档要等很久
│ ├── 大文档加载卡死
│ └── 移动端加载更慢
├── 🔴 编辑卡顿(L2,25 次,情感净值 -82%)
│ ├── 多人同时编辑时卡
│ └── 内容多了以后卡
└── 🟡 同步延迟(L2,13 次,情感净值 -45%)
├── 修改不能实时同步
└── 离线编辑同步冲突
📂 协同体验(L1,总提及 58 次)
├── 🔴 编辑冲突(L2,32 次,情感净值 -71%)
│ ├── 多人编辑内容被覆盖
│ └── 不知道谁改了什么
├── 🟡 权限管理(L2,18 次,情感净值 -35%)
│ └── 权限设置太复杂 / 太简单
└── 🟢 评论批注(L2,8 次,情感净值 +45%)
└── 评论功能好用
📂 功能诉求(L1,总提及 42 次)
├── 🟡 Markdown 支持(L2,12 次)
├── 🟡 更多模板(L2,10 次)
├── 🟡 导入导出(L2,10 次)
└── 🟡 AI 辅助写作(L2,10 次)
📂 竞品对比(L1,总提及 35 次)
├── vs 飞书文档(L2,20 次)
├── vs Notion(L2,10 次)
└── vs 腾讯文档(L2,5 次)
📂 正面评价(L1,总提及 30 次)
├── 🟢 免费好用(L2,15 次)
├── 🟢 与钉钉集成好(L2,10 次)
└── 🟢 模板丰富(L2,5 次)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Step 4:话题度量
对每个话题计算以下指标:
| 指标 | 计算方式 | 用途 |
|---|---|---|
| 热度(Frequency) | 属于该话题的内容条数 | 排序、筛选 |
| 情感净值(Sentiment Net) | (正面条数 - 负面条数) / 总条数 | 判断话题倾向 |
| 互动权重(Engagement Weight) | 加权互动量(likes + comments × 2) | 识别高影响力话题 |
| 平台分布 | 各平台的内容占比 | 识别平台差异 |
| 时间分布 | 按时间段的内容分布 | 识别趋势变化 |
| 用户多样性 | 独立用户数 / 总内容数 | 区分「一个人反复说」和「很多人都说」 |
用户多样性非常重要:
话题 A:提及 30 次,来自 28 个不同用户 → 广泛共识
话题 B:提及 30 次,来自 3 个用户 → 个别用户的强烈诉求
两者的处理策略完全不同。
Step 5:代表性内容提取
为每个话题提取 3-5 条最有代表性的内容:
选取标准(按优先级):
| 优先级 | 标准 | 理由 |
|---|---|---|
| 1 | 互动量最高的 | 引起最多共鸣 |
| 2 | 描述最详细的 | 信息量最大 |
| 3 | 情感最强烈的 | 最能体现用户感受 |
| 4 | 来自不同平台的 | 体现跨平台一致性 |
| 5 | 包含具体场景的 | 有使用场景支撑 |
3. 输出格式
3.1 话题清单(JSON)
{
"research_topic": "用户对钉钉文档的看法",
"total_items_analyzed": 203,
"topics_count": 12,
"topics": [
{
"topic_id": "T01",
"name": "加载速度慢",
"parent_topic": "性能问题",
"description": "用户普遍反映文档加载速度慢,尤其是大文档和移动端",
"metrics": {
"frequency": 38,
"frequency_pct": 18.7,
"sentiment_net": -0.78,
"engagement_weighted_score": 1250,
"unique_users": 35,
"user_diversity": 0.92,
"platform_distribution": {
"xiaohongshu": 15,
"zhihu": 12,
"reddit": 8,
"bilibili": 3
}
},
"representative_posts": [
{
"post_id": "xxx",
"content": "钉钉文档打开一个稍微大点的文档要等 10 秒以上...",
"platform": "zhihu",
"sentiment": -2,
"engagement": { "likes": 45 },
"why_selected": "互动量最高 + 描述具体"
}
],
"sub_topics": [
{ "name": "大文档加载卡死", "frequency": 15 },
{ "name": "移动端加载更慢", "frequency": 12 },
{ "name": "首次打开慢", "frequency": 11 }
]
}
]
}
3.2 话题热力图
用 sandbox_execute + matplotlib 生成可视化:
- X 轴:情感净值(-1 到 +1)
- Y 轴:话题热度(提及次数)
- 气泡大小:互动权重
- 颜色:红(负面)→ 黄(中性)→ 绿(正面)
# sandbox_execute
import matplotlib.pyplot as plt
import numpy as np
plt.rcParams['font.sans-serif'] = ['Noto Sans CJK SC']
plt.rcParams['axes.unicode_minus'] = False
fig, ax = plt.subplots(figsize=(12, 8))
for topic in topics:
ax.scatter(
topic['sentiment_net'],
topic['frequency'],
s=topic['engagement_score'] / 10,
c=topic['sentiment_net'],
cmap='RdYlGn',
vmin=-1, vmax=1,
alpha=0.7,
edgecolors='black'
)
ax.annotate(topic['name'], (topic['sentiment_net'], topic['frequency']),
fontsize=9, ha='center', va='bottom')
ax.set_xlabel('情感净值')
ax.set_ylabel('提及频次')
ax.set_title('话题热力图')
ax.axvline(x=0, color='gray', linestyle='--', alpha=0.5)
plt.tight_layout()
plt.savefig('topic_heatmap.png', dpi=150)
3.3 话题摘要文本
🗂️ 话题聚类报告
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📥 分析样本:203 条有效内容
📊 发现话题:12 个(归属 4 个一级类别)
🔥 热度 Top 5:
1. 🔴 加载速度慢 38 次 净值 -78% ← 最大痛点
2. 🔴 编辑冲突 32 次 净值 -71%
3. 🟡 Markdown 诉求 12 次 净值 -42%
4. 🟢 免费好用 15 次 净值 +90% ← 最大优势
5. 🟢 模板丰富 10 次 净值 +72%
📈 互动量 Top 3(高影响力话题):
1. 加载速度慢 加权互动 1,250
2. vs 飞书文档 加权互动 980
3. 编辑冲突 加权互动 870
👥 用户多样性异常:
⚠️ 「AI 辅助写作」:10 次提及,但仅来自 2 个用户
→ 可能是个别用户的强烈诉求,非广泛需求
🆕 低频但值得关注:
「离线编辑」仅 3 次提及,但 3 条都是深度体验描述
→ 可能是小众但强烈的需求
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
4. 话题质量校验
4.1 话题粒度检查
| 问题 | 判断标准 | 处理 |
|---|---|---|
| 话题太粗 | 一个话题包含 >30% 的内容 | 拆分为子话题 |
| 话题太细 | 一个话题只有 1-2 条内容 | 合并到最近的话题 |
| 话题重叠 | 两个话题的内容重叠 >50% | 合并或重新定义边界 |
| 「其他」太大 | 「其他」类包含 >20% 的内容 | 从「其他」中发现新话题 |
4.2 命名规范
| ✅ 好的话题名 | ❌ 差的话题名 | 原因 |
|---|---|---|
| 加载速度慢 | 性能 | 太笼统 |
| 多人编辑冲突 | 协同 | 太笼统 |
| 希望支持 Markdown | 功能 3 | 无描述性 |
| 比飞书差在哪 | 竞品 | 太笼统 |
| 免费是最大优势 | 正面 | 太笼统 |
命名原则:话题名应该是用户视角的、具体的、可理解的短句,看到名字就知道用户在说什么。
5. 常见坑与规避
-
不要用产品经理的视角命名话题
- ❌ 「性能优化需求」→ ✅ 「加载速度慢」
- ❌ 「协同编辑体验提升」→ ✅ 「多人编辑经常冲突」
- 话题名应该反映用户的语言,而非产品团队的语言
-
注意区分「话题」和「情感」
- 「加载速度」是话题,「加载速度慢」是话题 + 情感
- 如果同一功能既有正面又有负面,应该拆分为两个子话题
-
一条内容可以属于多个话题
- 「钉钉文档加载太慢了,比飞书差远了」→ 同时属于「加载速度慢」和「vs 飞书」
- 不要强行归入单一话题
-
低频话题不等于不重要
- 一个只有 3 条提及的话题,如果 3 条都是深度体验描述且情感强烈 → 可能是重要信号
- 用「用户多样性 × 情感强度」综合判断
6. 与上下游的接口
上游:voice-analyzer 的分析结果(情感、功能提及、态度标签)
下游:
pain-gain-detector(在话题框架内细化痛点/爽点)trend-tracker(对比不同时间段的话题变化)research-report-generator(话题分析章节)