从零打造一个电商搜索:SearchHits的那些事儿


假设你是一个电商平台的开发者,目标是让用户在搜索"手机壳"时,既能快速找到想要的东西,又能感受到结果的"贴心"。今天我们就围绕SearchHits聊聊,从一个最简单的搜索请求开始,一步步走进复杂但高效的方案,顺便看看相关性算法(比如Scores)是怎么玩转的。

最朴素的开始:一个简单的SearchRequest

我们先从最基础的玩儿法入手。想象你刚接手这个任务,领导说:"给个搜索框,用户输入'手机壳',把相关的商品吐出来就行。"你心想,这还不简单?于是写了个最直白的SearchRequest

java 复制代码
SearchRequest request = new SearchRequest("products");
QueryBuilder query = QueryBuilders.matchQuery("name", "手机壳");
request.source().query(query);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();

运行一下,SearchHits里还真出来了几个结果,比如: - "iPhone 13手机壳透明款"

  • "手机壳华为P40防摔"

  • "手机壳可爱卡通图案"

你一看,嗯,能用!hits.getHits()里每个SearchHit都有商品信息,标题、价格啥的都能拿出来展示。但很快问题就来了:用户说,"为啥第一个结果是个透明款?我想要卡通的啊!"你再一看,顺序好像有点随意,相关性完全没体现出来。

朴素策略的问题:相关性哪去了?

这时候你得琢磨了,刚刚那个matchQuery虽然简单,但它默认的排序是个啥逻辑呢?翻了翻文档,原来默认情况下,Elasticsearch会给每个结果算一个_score,但因为我们没指定啥特别的字段权重或者排序规则,这个_score基本就是"谁命中得多谁靠前",而且还不一定贴合电商场景。比如: - "手机壳"这个词在标题里出现了三次的结果,可能排在前面。 - 但用户其实更想要"卡通"这种风格的,透明款反而没啥吸引力。

再看SearchHits里的数据,假设返回了3个结果: - Hit 1: "iPhone 13手机壳透明款",_score = 1.5 - Hit 2: "手机壳华为P40防摔",_score = 1.3 - Hit 3: "手机壳可爱卡通图案",_score = 1.2

你盯着这堆数字发呆:为啥卡通的得分最低?仔细一想,matchQuery只管词频,不管用户意图,也不管商品的销量、点击率这些电商里常见的"隐藏加分项"。这时候,朴素策略的短板就暴露了:它太"机械"了,完全不理解用户的真实需求。

进化第一步:引入相关性调优

既然发现了问题,咱们就得动手优化。电商搜索里,用户输入"手机壳",通常不只是想要标题里带这个词的商品,而是希望结果既相关又"实用"。怎么做呢?先从相关性算法入手,加点权重。

比如,我们知道标题比描述更重要,品牌可能比款式更关键。于是改进了SearchRequest

java 复制代码
SearchRequest request = new SearchRequest("products");
QueryBuilder query = QueryBuilders.boolQuery()
    .should(QueryBuilders.matchQuery("name", "手机壳").boost(2.0f)) // 标题权重高
    .should(QueryBuilders.matchQuery("description", "手机壳").boost(1.0f)); // 描述权重低
request.source().query(query);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();

这回SearchHits里的_score变了: - Hit 1: "iPhone 13手机壳透明款",_score = 3.0(标题命中,权重翻倍) - Hit 2: "手机壳可爱卡通图案",_score = 2.8(标题+描述都命中) - Hit 3: "手机壳华为P40防摔",_score = 2.5

你一看,卡通的排名上来了,感觉有点靠谱了!通过boost调整字段权重,相关性算法开始"听话"了。但问题还没完,用户又反馈:"我想要销量高的卡通手机壳,为啥还是透明款排第一?"

再进一步:电商特色的排序

这时候你意识到,光靠文本匹配还不够,电商搜索得考虑更多维度,比如销量、评分这些"业务指标"。Elasticsearch支持function_score查询,咱们试试把销量加进去:

java 复制代码
SearchRequest request = new SearchRequest("products");
QueryBuilder baseQuery = QueryBuilders.matchQuery("name", "手机壳");
FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(baseQuery)
    .add(ScoreFunctionBuilders.fieldValueFactorFunction("sales").factor(0.001f).modifier(Modifier.LOG1P));
request.source().query(functionScoreQuery);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();

假设商品数据是这样的: - "iPhone 13手机壳透明款",销量1000,_score = 3.0 + log(1 + 1000 * 0.001) ≈ 3.69 - "手机壳可爱卡通图案",销量5000,_score = 2.8 + log(1 + 5000 * 0.001) ≈ 3.90 - "手机壳华为P40防摔",销量200,_score = 2.5 + log(1 + 200 * 0.001) ≈ 2.77

这下SearchHits的排序变成: 1. "手机壳可爱卡通图案",_score = 3.90 2. "iPhone 13手机壳透明款",_score = 3.69 3. "手机壳华为P40防摔",_score = 2.77

你拍拍手,销量高的卡通款终于排第一了!用户的需求被更好地满足,_score也开始体现"商业价值"。

解析SearchHits:给用户一个直观的展示

拿到SearchHits后,怎么呈现给用户呢?我们可以遍历结果,把关键信息掏出来:

java 复制代码
for (SearchHit hit : hits) {
    System.out.println("商品: " + hit.getSourceAsMap().get("name") + 
                      ", 得分: " + hit.getScore() + 
                      ", 销量: " + hit.getSourceAsMap().get("sales"));
}

输出可能是:

makefile 复制代码
商品: 手机壳可爱卡通图案, 得分: 3.90, 销量: 5000
商品: iPhone 13手机壳透明款, 得分: 3.69, 销量: 1000
商品: 手机壳华为P40防摔, 得分: 2.77, 销量: 200

用户一看就明白,这结果不仅相关,还考虑了热销程度,体验好多了。

还能怎么优化?

走到这一步,已经比最开始的朴素方案强了不少,但还有提升空间。看看现在的短板: 1. 个性化缺失 :不同用户可能偏好不同,比如有人爱便宜的,有人只买大牌。 2. 动态性不足 :销量是静态的,没考虑实时点击率或库存状态。 3. 语义理解不够:如果用户搜"防摔手机壳",单纯的词频匹配可能漏掉"抗摔"这种同义词。

优化方向呢?其实跟现在主流电商的复杂方案不谋而合:

  • 个性化推荐 :加个用户行为因子,比如用function_score引入用户历史点击的商品类别权重。 - 实时反馈 :结合短期的点击率或转化率,动态调整_score,可以用外部数据流实时更新。 - 语义搜索:引入向量搜索(vector search),把"防摔"和"抗摔"映射到相近的语义空间,靠 embedding 提升召回。

总结:从简单到贴心

从最开始的matchQuery,到加权字段,再到销量因子,我们一步步让搜索结果更贴近电商的真实需求。SearchHits里的_score不再是冷冰冰的数字,而是用户意图和业务价值的综合体现。未来要是再加上个性化、实时性和语义理解,妥妥能媲美大厂的搜索体验。你说,这趟旅程是不是挺有意思?

相关推荐
李长渊哦2 分钟前
Spring Boot 接口延迟响应的实现与应用场景
spring boot·后端·php
Seven9714 分钟前
【设计模式】通过访问者模式实现分离算法与对象结构
java·后端·设计模式
Seven9732 分钟前
【设计模式】遍历集合的艺术:深入探索迭代器模式的无限可能
java·后端·设计模式
小杨40437 分钟前
springboot框架项目应用实践五(websocket实践)
spring boot·后端·websocket
浪九天38 分钟前
Java直通车系列28【Spring Boot】(数据访问Spring Data JPA)
java·开发语言·spring boot·后端·spring
bobz9651 小时前
IKEv1 和 IKEv2 发展历史和演进背景
后端
大鹏dapeng2 小时前
Gone v2 goner/gin——试试用依赖注入的方式打开gin-gonic/gin
后端·go
tan180°2 小时前
版本控制器Git(1)
c++·git·后端
GoGeekBaird2 小时前
69天探索操作系统-第50天:虚拟内存管理系统
后端·操作系统
_丿丨丨_2 小时前
Django下防御Race Condition
网络·后端·python·django