搜索关键词高亮

Sep 6, 2016


搜索关键词高亮

最近在研究搜索关键词高亮的问题,目前仔细看下来,主要有两个方案: 1. 使用原生的elasticsearch高亮 2. 使用javascript关键词高亮

elasticsearch

先来看看 elasticsearch的高亮显示,Elasticsearch中的高亮显示是来源于lucene的功能,他允许在一个或者多个字段上突出显示搜索内容, lucene支持三种高亮显示方式highlighter, fast-vector-highlighter, postings-highlighter,第一种是默认的标准类型。

postings-highlighter

先看一下第一种,打开sense之后,使用下面的脚本:

GET ticket/_search
{
  "query": {
    "term": {
      "districtName": "上海"
    }
  },
  "highlight": {
    "fields": {
      "productName": {},
      "districtName": {}
    }
  }
}

返回结果会有以下的一些情况:

"highlight": {
  "districtName": [
    "<em>上海</em>"
  ]
}
"highlight": {
  "districtName": [
    "<em>上海</em>"
  ]
}

通过这个上面观察,当通过一个关键词使用匹配的时候,高亮显示的词并不包括我所想展示的字段。 在我们的搜索里面,有很多种情况的搜索,比如匹配行政区的时候,输入的关键词是 上海 ,但是匹配的是 cityDestId,当我们设置高亮显示的是productName,productName无法展示高亮。而且根据官网显示,这个高亮对于复杂的查询会很影响性能。 个人还试了其他几种,模糊匹配的时候,会高亮一些本不该显示的字段。

GET ticket/_search
{
  "from": 0,
  "size": 8,
  "query": {
    "function_score": {
      "filter": {
        "bool": {
          "must": {
            "bool": {
              "should": [
                {
                  "bool": {
                    "must": {
                      "bool": {
                        "should": [
                          {
                            "wildcard": {
                              "productName": "*上海*"
                            }
                          }
                        ]
                      }
                    }
                  }
                }
              ]
            }
          }}
      }
    }
  },
  "highlight": {
    "fields": {
      "districtName": {},
      "productName": {}
    }
  }
}

返回结果:

"highlight": {
  "productName": [
    "<em>上海玛雅海滩水公园</em>"
  ]
}
"highlight": {
  "productName": [
    "<em>上海东平</em>国家森林公园"
  ]
}

fast-vector-highlighter:

lucene中自带的高亮显示,在es中,不同的场景会自动调用这种显示,下面是所描述的几个特点: 1. 快,特别是内容别大的字段,比如大于1M。 2. 可定制的boundarychars,boundarymaxscan,和fragmentoffset。 3. 需要设置termvector的值为withpositions_offsets,增加索引的大小。 4. 可以将多个字段的匹配组合成一个结果。 5. 可以分配不同的权重匹配在不同的位置上 相关的配置:

{
    "type_name" : {
        "content" : {"type":"string","term_vector" : "with_positions_offsets"}
    }
}

也就是说 他相当于在建索引的时候,增加了词向量的属性,因而在高亮查询的时候,性能更好,但是他还是存在搜的是cityDestId,但是需要显示的是productName的问题,而且我们的索引里面,productName的字段也是非常小,所以他对性能的提示并不一定会很明显。

postings-highlighter

lucene还支持postings-highlighter高亮显示,postings-highlighter高亮显示具有如下特点: 1. 快,因为它不需要重新分析文档:尤其是对大文件对性能的提高更为明显。 2. 占用更少的磁盘空间。 3. 把高亮显示和句子分开,这个更有利于人类的阅读。 4. 使用BM25算法,使搜索的时候像是整篇文档。 Elasticsearch中需要在建立索引的时候映射字段类型,才可以实现postings-highlighter高亮显示,例如对content字段采用postings高亮类型:

{
    "type_name" : {
        "content" : {"type":"string","index_options" : "offsets"}
    }
}

和fast-vector-highlighter的模式基本上差不多。

此外:es可以指定某个词的高亮属性 分别为 plain fvh postings

另:高亮内部也可以使用简单的查询,高亮的显示不适合复杂的查询。

JavaScript高亮展示

js的高亮展示比较简单,前提是搜索的关键词需要传递一个分词之后的结果,拿到这个结果之后,对指定的dom文件里面的数据进行遍历,然后进行正则替换即可。网上的demo代码如下:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>关键字高亮显示</title>
</head>
<body>
    <div class="result" id="result">
        <div>脚本之家是一个专业的收集各类脚本学习资料的网站,尽量修正错误打造精品脚本类学习网站,我们为大家游戏脚本资源,源码,软件,asp,php,javascript等编程资料,是网页制作,网络编程,网站建设人士的聚集场所。</div>
        <span>提供最新的网络编程、脚本编程、网页制作、网页设计、网页特效,为站长与网络编程从业者提供学习资料。</span>
        <div>脚本,vbscript,正则表达式,jquery,dos,bat,批处理,javascript,Photoshop,HTML,div+css,ASP,PHP,ASP.NET</div>
    </div>
    <!--iframe id="iframe1" style="width:600px; height:500px;" src="about:blank"></!--iframe-->
    <script>
        function SearchHighlight(idVal, keyword) {
            var pucl = document.getElementById(idVal);
            if ("" == keyword) return;
            var temp = pucl.innerHTML;
            var htmlReg = new RegExp("\<.*?\>", "i");
            var arrA = new Array();
            //替换HTML标签
            for (var i = 0; true; i++) {
                var m = htmlReg.exec(temp);
                if (m) {
                    arrA[i] = m;
                }
                else {
                    break;
                }
                temp = temp.replace(m, "{[(" + i + ")]}");
            }
            words = unescape(keyword.replace(/\+/g, ' ')).split(/\s+/);
            //替换关键字
            for (w = 0; w < words.length; w++) {
                var r = new RegExp("(" + words[w].replace(/[(){}.+*?^$|\\\[\]]/g, "\\$&") + ")", "ig");
                temp = temp.replace(r, "<em'>$1</b>");
            }
            //恢复HTML标签
            for (var i = 0; i < arrA.length; i++) {
                temp = temp.replace("{[(" + i + ")]}", arrA[i]);
            }
            pucl.innerHTML = temp;
            //写入iframe
            /*
            var iframe = document.getElementById('iframe1');
            iframe.contentDocument.body.innerHTML = temp;
            */
        }
        SearchHighlight("result", "脚本 学习 vbscript div");
    </script>
</body>
</html>

缺点是展示的不够智能,获取dom节点要求需要固定,如果是不同的页面,可能需要写不同的js,优点是比较简单。