基于 Elasticsearch 8.12.0 集群热词实现
前言
在当今信息爆炸的时代,数据如同潮水般汹涌而来,海量的信息充斥着我们的生活与工作。无论是社交媒体上的热门话题,电商平台上的流行商品,还是新闻资讯中的焦点事件,热词的出现频率与传播速度都成为了衡量其重要性与影响力的关键指标。而 Elasticsearch(简称 ES)作为一款功能强大的搜索引擎与数据分析工具,以其卓越的性能、灵活的架构以及强大的文本处理能力,成为了众多企业和开发者在数据管理和检索领域的首选利器。
热词,作为数据海洋中的“灯塔”,能够快速引导我们发现用户关注的焦点、市场的趋势以及潜在的热点问题。然而,如何从海量的数据中精准地提取热词,并实时地呈现其热度变化,这不仅是一项技术挑战,更是一场对数据价值挖掘的深度探索。Elasticsearch 提供了丰富的工具和功能,从文本分析到聚合查询,从实时索引到高效的搜索性能,为实现热词的高效提取与展示提供了坚实的基础。
安装分词器
分词器
HanLP(Han Language Processing)是一个针对中文优化的自然语言处理库,提供了包括词法分析、句法分析、命名实体识别等多种NLP功能。
<dependency><groupId>com.hankcs</groupId><artifactId>hanlp</artifactId><version>portable-1.8.6</version>
</dependency>
使用
natures 定义详见附录:重点关注词性分类表
import cn.hutool.core.util.ArrayUtil;
import com.hankcs.hanlp.HanLP;
import com.hankcs.hanlp.seg.common.Term;
import lombok.extern.slf4j.Slf4j;import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;/*** 功能描述** @author jason*/
@Slf4j
public class HanlpUtil {/*** 获取分词*/public static Set<String> getWordSet(String text) {String[] natures = {"n", "nr", "nrj", "ns", "nt", "nz", "nh", "nm", "nf", "j", "i", "l", "vn", "ad", "nx", "xx"};List<Term> termList = HanLP.segment(text);for (Term term : termList) {log.info(term.word + " - " + term.nature);}Set<String> wordSet = termList.stream().filter(term -> ArrayUtil.contains(natures, term.nature.toString())).map(term -> term.word).collect(Collectors.toSet());log.info("分词:{}", wordSet);return wordSet;}public static void main(String[] args) {// 输入字符串String text = "HanLP 是一个自然语言处理工具包。是一个面向生产环境的多语种自然语言处理工具包,它基于PyTorch和TensorFlow 2.x双引擎,目标是普及落地最前沿的NLP技术\n" +"无论您是专家还是初学者,HanLP都可以让您能够轻松快速的构建、处理和“理解”大量文本的AI应用程序";Set<String> wordSet = getWordSet(text);log.info("分词:{}", wordSet);}}
输出
16:08:04.297 [main] INFO org.example.es.util.HanlpUtil -- HanLP - nx
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- - w
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- 是 - vshi
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- 一个 - mq
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- 自然语言处理 - nz
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- 工具包 - n
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- 。 - w
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- 是 - vshi
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- 一个 - mq
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- 面向 - v
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- 生产 - vn
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- 环境 - n
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- 的 - ude1
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- 多 - a
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- 语种 - n
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- 自然语言处理 - nz
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- 工具包 - n
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- , - w
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- 它 - rr
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- 基于 - p
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- PyTorch - nx
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- 和 - cc
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- TensorFlow - nx
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- - w
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- 2 - m
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- . - w
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- x - nx
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- 双引擎 - b
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- , - w
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- 目标 - n
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- 是 - vshi
16:08:04.299 [main] INFO org.example.es.util.HanlpUtil -- 普及 - v
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 落地 - vi
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 最 - d
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 前沿 - s
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 的 - ude1
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- NLP - nx
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 技术 - n
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- - w
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 无论 - c
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 您 - rr
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 是 - vshi
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 专家 - nnt
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 还是 - c
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 初学者 - n
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- , - w
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- HanLP - nx
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 都 - d
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 可以 - v
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 让 - v
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 您 - rr
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 能够 - v
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 轻松 - a
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 快速 - d
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 的 - ude1
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 构建 - v
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 、 - w
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 处理 - vn
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 和 - cc
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- “ - w
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 理解 - v
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- ” - w
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 大量 - m
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 文本 - n
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 的 - ude1
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- AI - nx
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 应用 - vn
16:08:04.300 [main] INFO org.example.es.util.HanlpUtil -- 程序 - n
16:08:04.306 [main] INFO org.example.es.util.HanlpUtil -- 分词:[技术, 初学者, 生产, AI, 处理, 文本, 程序, 语种, PyTorch, NLP, 目标, x, 工具包, 自然语言处理, 环境, HanLP, TensorFlow, 应用]
热词排行榜
@Data
@AllArgsConstructor
public class RankingEntry {/*** 热词*/private String word;/*** 出现的数量*/private int num;}
import cn.hutool.core.collection.CollectionUtil;
import org.example.es.entity.EsProduct;
import org.example.es.util.HanlpUtil;import java.util.*;/*** 自定义排行榜类-仅用于测试** 实际产线环境,需要结合 Redis 实现*/
public class RankingClient {private static final Map<String, Integer> rankMap = new HashMap<>();/*** 批量新增热词*/public synchronized static void batchAddWord(List<EsProduct> productList) {for (EsProduct esProduct : productList) {Set<String> wordSet = HanlpUtil.getWordSet(esProduct.getDescription());for (String word : wordSet) {addOrUpdateWord(word, 1);}}}/*** 新增计数或更新计数*/public synchronized static void addOrUpdateWord(String word, int num) {int rankNum = rankMap.getOrDefault(word, 0);rankMap.put(word, rankNum + num);}/*** 获取完整排行榜*/public static List<RankingEntry> getRanking() {List<RankingEntry> rankList = new ArrayList<>();rankMap.forEach((word, num) -> rankList.add(new RankingEntry(word, num)));CollectionUtil.sort(rankList, Comparator.comparingInt(RankingEntry::getNum).reversed());return rankList;}/*** 获取前 n 名*/public static List<RankingEntry> getTopN(int n) {return CollectionUtil.sub(getRanking(), 0, n);}/*** 清空排行榜*/public static void clear() {rankMap.clear();}public static void main(String[] args) {// 添加测试数据RankingClient.addOrUpdateWord("Alice", 10);RankingClient.addOrUpdateWord("Bob", 5);RankingClient.addOrUpdateWord("Charlie", 15);RankingClient.addOrUpdateWord("Alice", 3); // Alice已有记录,会增加次数// 获取完整排行榜System.out.println("完整排行榜:");List<RankingEntry> fullRanking = RankingClient.getRanking();for (RankingEntry entry : fullRanking) {System.out.println(entry);}// 获取前2名System.out.println("\nTop 2:");List<RankingEntry> top2 = RankingClient.getTopN(2);for (RankingEntry entry : top2) {System.out.println(entry);}}}
使用
@GetMapping("/searchByMatch/{index}")public List<EsProduct> searchByMatch(@PathVariable String index, @RequestParam String keyword) {BoolQuery boolQuery = new BoolQuery.Builder().should(s -> s.match(m -> m.field("name").query(keyword))).should(s -> s.match(m -> m.field("description").query(keyword))).should(s -> s.match(m -> m.field("category").query(keyword))).build();List<EsProduct> esProductList = esClient.searchByBool(index, EsProduct.class, boolQuery);// 热词RankingClient.batchAddWord(esProductList);return esProductList;}
@GetMapping("/hotWords")public List<RankingEntry> hotWords() {return RankingClient.getRanking();}
测试
http://127.0.0.1:9200/es/product/hotWords
[{"word": "防水","num": 46},{"word": "防尘","num": 46},{"word": "续航","num": 34},{"word": "设计","num": 32},{"word": "电池","num": 20},{"word": "vivo","num": 20},{"word": "容量","num": 20},{"word": "iQOO","num": 16},{"word": "OPPO","num": 14}
]
搜索多个品牌,然后多搜索几次小米
最终热词榜结果
附:热词分析 - 重点关注词性分类表
主要关注词性(核心热词)
词性标记 | 类别 | 说明 | 示例 |
---|---|---|---|
n | 普通名词 | 一般事物名词 | 手机、电脑、天气 |
nr | 人名 | 中文人名 | 张三、李四 |
nrj | 日语人名 | 日语姓名 | 山田、佐藤 |
ns | 地名 | 地理名称 | 北京、上海 |
nt | 机构团体名 | 公司、组织、机构名称 | 腾讯、联合国 |
nz | 其他专名 | 未归类专有名词 | ChatGPT、元宇宙 |
nh | 医药名词 | 药品、疾病、医学术语 | 阿司匹林、糖尿病 |
nm | 物品名词 | 具体物品名称 | 手机、汽车 |
nf | 食品名词 | 食物、饮料相关名词 | 咖啡、披萨 |
j | 简称略语 | 缩写、简称 | 5G、AI |
i | 成语 | 四字成语或固定短语 | 亡羊补牢、守株待兔 |
l | 习用语 | 常用固定搭配 | 一言难尽、不可思议 |
v | 普通动词 | 动作、行为 | 跑步、学习 |
vn | 名动词 | 可作名词的动词 | 研究、调查 |
a | 形容词 | 描述性词汇 | 漂亮、聪明 |
ad | 副形词 | 可作副词的形容词 | 快速、直接 |
nx | 外文字符 | 英文、数字、符号等 | iPhone、2024 |
xx | 非语素字 | 网络新词、特殊符号 | 绝绝子、yyds |
次要关注词性(特定场景下可能有用)
词性标记 | 类别 | 说明 | 示例 |
---|---|---|---|
nba | NBA相关词 | 篮球术语、球队、球员 | 湖人、詹姆斯 |
nbc | 生物名词 | 动植物、生物学术语 | 熊猫、DNA |
t | 时间词 | 时间相关词汇 | 今天、2025年 |
tg | 时间词性语素 | 时间相关的构词语素 | 年、月、日 |
总结建议
- 核心热词:优先关注名词类(
n
系列)、简称(j
)、成语(i
)、动词(v/vn
)、形容词(a/ad
)以及外文/网络词(nx/xx
) - 特定领域:根据分析目标选择性关注体育(
nba
)、生物(nbc
)、时间(t/tg
)等词性 - 可忽略词性:助词(
u
)、连词(c
)、标点(w
)等对热词分析价值较低
提示:实际分析时可结合词频统计和上下文关联度进一步筛选有价值的热词