• 电商商品搜索设计与算法分享——自定义实现


    前言

          这个搜索方案小猿也花了比较多时间去优化和完善,同时觉得比较有意思,所以放在这里记录一下,同时也给有相关需要的朋友提供一些思路。

    说明

          当前项目中并未使用传统的ES搜索,考虑到ES对机器配置比较高,同时相对比较重。当前业务场景数据量并不算高,暂时的实现机制,使用redis + 数据库索引 + MP二级缓存,商品在20w左右可以支撑。

          1. 该算法主要基于相似搜索,并基于相似度得分进行排序,最终结果进行高亮显示。

          2.  实现商品聚合

          3.  支持多维度排序,并可动态排序

          4.  基于内存分页

     搜索设计

           1. 匹配维度

                1. 商品名 : 模糊匹配 + 精准匹配 + word分词 + mysql 正则匹配, 如果输入精准商品名,匹配唯一结果

                2. sku  : 模糊匹配 + 精准匹配, 如果输入精准sku,匹配唯一结果

                3. 关键字: 模糊匹配 + word分词  + mysql 正则匹配, 主要为了提高精准度,影响得分生成的权重

                4. 分类(去掉,与算法无关)

                5. 区域(去掉,与算法无关)

           2. 使用算法和工具

               1. 相似算法 : 相似余弦(不理想)、aerfa(不理想) 、 自定义

               2. 分词 : nlp(英文不友好,并依赖于词库), ik(英文不友好,并依赖于词库) ,word

     实现技术点

           1. 商品聚合算法

            场景:一般商品设计的时候民,同一个商品,都会有很多维度的分类,但其实在数据库中,他们都是基于唯一的sku,只不过sku会根据一定的维度生成,其实商品名

           也类似,我们在商城上看到的名字,其实是由很多部分组合而 成的一个唯一名称(可以去参考京东); 我们在搜索商品的时候,每个商品只会显示一个sku,并且如果有

           关键字,则返回符合条件一个sku返回。可能会有人说,这个设置父子表,随机取一条,其实大型电商里面,一般都只有sku的子表

           这里简单举一例:比如iphone12手机,

                  型号:有min版本,max版本, pro版本

                  颜色:黑色、银色、玫瑰金、白色

                  内存:64G,  256G、128G

                  模式: 联通、电信、移动、全网通

            虽然在界面上显示只有一个商品,其实在存储上,是有很多商品的,4(型号) * 4( 颜色) * 内存(3) * 模式(4)  ,最多可能会有132种商品,大型的电商里面,这个算法会非常复杂

            这里分分享一个比较简单的算法,实现 分组中随机取一条或者取指定条的算法,基于mysql实现

            

    #sql逻辑说明: 1. 先查出所有商品的同一个sku,使用group_concat基于material_head_id分组,并组合成 逗号分割 sku 字符串,按上架时间排序(直接使用max时间取得最新上架商品),这里默认需要找到每个商品中最新上架的sku,
    # 如果有指定条件的话,需要找到符合条件的最新上架sku 2. 将1按分组排好序的sku字符串切割,获得第一个sku 3,通过sku关联该sku对应的详细信息即可

    SELECT pf.material_id, pf.goods_id, pf.material_head_id, pf.on_shelf_time FROM put_shelf pf,( SELECT tf.material_head_id, max( tf.on_shelf_time ) AS on_shelf_time, substring_index(group_concat( tf.id ),',', 1) on_shelf_id FROM ( SELECT f.* FROM put_shelf f, material mt WHERE mt.id = f.material_id AND f.on_shelf_flag = '1' ORDER BY f.on_shelf_time DESC ) tf GROUP BY tf.material_head_id

      2. 分词实现与集成

              2.1 引入依赖 

          <dependency>
                <groupId>org.apdplat</groupId>
                <artifactId>word</artifactId>
                <version>1.3</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.slf4j</groupId>
                        <artifactId>slf4j-api</artifactId>
                    </exclusion>
                    <exclusion>
                        <groupId>ch.qos.logback</groupId>
                        <artifactId>logback-classic</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>

             2.2  初始化词库

    @Component
    public class WordInitConfig {
        static {
            WordSegmenter.segWithStopWords("初始化分词");
        }
    }
    

      2.3 使用分词,并拼接成正则格式

        /**
         * 基于word分词
         * @param keyword 搜索关键字  "不锈钢水杯"
    * @return "不锈钢|水杯|不锈钢水杯"
    */ private String handleKeywordWord(String keyword) { //新建分词器 List<Word> words2 = WordSegmenter.segWithStopWords(keyword); List<String> nameStr = Utils.map(words2, t->t.getText()); nameStr.add(keyword); log.info("基本分词:"+ StringUtils.join(nameStr,"|")); return StringUtils.join(nameStr,"|"); }

         2.4 sql 查询, 使用正则(这里补充一个知识点,很多人说正则效率很低(正则效率问题:其实是争对要匹配的原字符,如果字符是一篇文章,你需要匹配一个特定模式,那么效率确实很慢), 当然正常时候sql不推荐使用正则

         

    # regName格式为  “不锈钢|水杯|不锈钢水杯”
    select a.* from test t where t.label_name REGEXP  #{regName}

         3. 相似算法

          说明 :通过上一步搜索,其实只是找到了与我们关键字匹配的结果,但是这些结果排序是很乱的,有些匹配到的结果跟预期相关性很小,有些可能就完全没关系;而另外一些,可能由于商品名称设置不合理,本应该出现的,又没匹配到,可能会给用户带来很不好的体验,这里做一些优化。

          示例说明:比如用户在商城搜索“不锈钢水杯”, 由于之前的分词+模糊+正则匹配,可能会匹配到 诸如“不锈钢衣架”,“钢丝球”, “酒杯”, “水桶”,这些结果呢,由于商品池比较丰富,原始的结果可能会有100个,而我们要的水杯被排在了好几页之后;又或者商家把商品取名成了“保温壶”,这样的话,传统的做法,压根就没有把这个结果匹配出来。当然也有人说,这个“保温壶”搜索不到,本来就不是系统问题。但是细想一下,如果你是用户,你是不是每次都能输入一个准确的关键词去查找呢,或者说我们是不是可做一些小的优化,让系统更加智能,同时让用户有更好的体验。

          算法讲解: 这里增加关键字,一个商品可以有很多关键字,这些关键字其实就是标签,商家设置的一些更宽泛的商品分类,或者快速匹配商品的方式,并且可以根据用户的习惯设置一些用户常用的关键字,这样再加上之前的标题搜索,通过两个维度,同时设置权重,让结果更加准确,并将结果量化,生成得分,通过一个综合的得分结果排序,那这样的一个结果,应该是更能满足用户需求的。

          先上算法如下:

         

    /**
         * 基于关键字加权实现
         * @param keyword 关键字
         * @param productName 搜索内容
         * @param regex 分词,以|分割
         * @param label 标签,以,分割
         * @return
         */
        public static Double getSimilarity(String keyword, String productName, String regex, String label) {
    
            //设置分词权重表
            Map<String, Integer> rm = Maps.newHashMap();
            String[] arr = regex.split("\|");
            for(String a : arr){
                if(!rm.containsKey(a)){
                    int len = a.length();
                    rm.put(a, len * 10);
                }
            }
    
            double score = 0;
            for(String a : arr){
                int index = productName.lastIndexOf(a);
                if(index > -1){
                    score +=  rm.get(a);
                }
            }
    
            if(label == null || label == ""){
                return score;
            }
            //设置标签权重表,加分项,并设置惩罚措施
            Map<String, Integer> labelRm = Maps.newHashMap();
            String[] labelArr = label.split(",");
            for(String a : labelArr){
                if(!labelRm.containsKey(a)){
                    labelRm.put(a, 20);
                }
            }
    
            double labelScore = 0;
            for(String a : labelArr){
                int index = keyword.lastIndexOf(a);
                if(index > -1){
                    labelScore +=  labelRm.get(a);
                }else{
                    labelScore +=  -5;
                }
            }
    
            return score + labelScore;
        }

         算法讲解:

                1. 设置商品名权重表  我们为关键词分词后的分词表设置权重,一个字10分,n个字 n*10,

                2. 计算商品名匹配计分,每匹配到一个字,则加10分,如果匹配到了多个关键词,则分数累加

                3. 设置标签名权重表,一个字20分,因为这里的词匹配到,就说明其实商家更希望你输入关键字表里的信息去查找到商品;

                4. 计算标签匹配计分, 这里标签会有很多,以逗号分割的字符串存储,如果匹配到一个关键字,则加20分,2个字则40分;

                    这里有一些不一样,这里设置奖惩机制,如果匹配到,则给一个大一点的奖励,如果匹配不到,则要减分,每一个减少10

         分,这个可以根据具体去调整; 同时,如果出现负分,则将结果舍弃掉,这个也很好理解,如果商家设置20个关键词,你一个

                    都没给输对,那说明这个商品很可能不是用户要的,用户其实是不知道这个商品在系统上叫什么,商家肯定知道自己的商品叫什么

  • 相关阅读:
    BZOJ 1441: Min exgcd
    luogu 1876 开灯 约数+打表
    luogu 1414 又是毕业季II 约数相关
    BZOJ1968: [Ahoi2005]COMMON 约数研究 线性筛
    luogu 3441 [POI2006]MET-Subway 拓扑排序+思维
    Comet OJ
    CF990G GCD Counting 点分治+容斥+暴力
    CF873F Forbidden Indices 后缀自动机+水题
    CF293E Close Vertices 点分治+树状数组
    CF1101D GCD Counting 点分治+质因数分解
  • 原文地址:https://www.cnblogs.com/liuyq/p/14941510.html
Copyright © 2020-2023  润新知