Whether it is news, content, or e-commerce platforms, Lenovo input has become a standard search function, is not new. Let’s open a search engine or the electric business platform, when we input pinyin or text in the input box you will see below the input box pops up meaningful search Suggestions, hints if we want to enter “under” content, help us to supplement the input or fix bugs, optimize our search experience.

Another standard configuration of search engines is popular search recommendation, which enables users to know the news, goods or gossip that people care most about at present. When users do not know what they want to search for, users can be given some choices to optimize user experience, which in fact achieves the effect of advertising.

Of course, THE author is not a product manager, so we do not analyze the meaning behind the lenovo input and popular search recommendation function, only from the technical point of view of the most interesting technical problem, that is, how to add lenovo input search and popular search recommendation function for search, this is also the author recently received a demand.

This article explains how to add associative input and popular search recommendations to your product search based on ElasticSearch.

Add top search recommendations to product search

The simplest way to achieve popular search recommendation is to record the text content entered by users when users click search, and then provide an interface for the front end to count all the text content entered by users and take out the top several records and responses to the front end. Of course, it is better to add the search time, add time range filtering statistics, only care about the most recent period of data.

Create search diary index

Suppose we create an item search diary index named search_log.

  • inKibanaCall interface in

PUT /search_log

{
    "mappings": {"properties": {"keyword": {"type":"keyword"."ignore_above":256
            },
            "search_time": {"type":"long"}}},"settings": {
        "index": {
          "number_of_shards": 5."number_of_replicas": 3}}}Copy the code

In the search_log index, we set the keyword field type to keyword.

  • TextType: word segmentation and index are supported. Fuzzy query is supported, but aggregation is not supported.
  • keywordType: query without word segmentation, direct index, support fuzzy query, support aggregation.

Query popular search terms

  • inKibanaCall interface in

GET /search_log/_search

{
  "query": {
    "bool": {
      "filter": [
        {
          "range": {
            "search_time": {
              "gte": 1000000000000,
              "lte": 10000000000000
            }
          }
        }
      ]
    }
  },
  "aggs": {
    "my_name": {
      "terms": {
        "field": "keyword",
        "size": 1
      }
    }
  }
}
Copy the code
  • JavaCode implementation, aggregation search to obtain the highest number of the first few records
@Service
public class GoodsSearchService{
    @Override
    public List<String> popularSearch(int n) {
       SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
       searchSourceBuilder.size(0);
       searchSourceBuilder.query(QueryBuilders.boolQuery()
             .filter(QueryBuilders.rangeQuery("search_time")
                    .gte(System.currentTimeMillis() - (1000L * 31 * 24 * 60 * 60))));
       searchSourceBuilder.aggregation(AggregationBuilders.terms("group_by_keyword")
                        .field("keyword").size(n));
       try {
           SearchRequest searchRequest = new SearchRequest("search_log");
           searchRequest.source(searchSourceBuilder);
           SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
           Aggregations aggregations = searchResponse.getAggregations();
           Terms trem = aggregations.get("group_by_keyword");
           List<Terms.Bucket> buckets = (List<Terms.Bucket>) trem.getBuckets();
           List<String> hotWords = new ArrayList<>();
           for (Terms.Bucket bucket : buckets) {
               String key = (String) bucket.getKey();
               hotWords.add(key);
           }
           return hotWords;
       } catch (IOException e) {
           e.printStackTrace();
           returnCollections.emptyList(); }}}Copy the code

Add associative input to product search

I also had a question when doing this feature: should this feature be implemented in the front end or the back end?

To answer this question, I looked at Google search, Baidu search, and Taobao search and found that these search engines are implemented through the request back-end interface.

Google search:Baidu search:Taobao Search:

It is well understood, take Taobao search as an example, because there is no commodity at the front, lenovo input prompt needs to know there is no commodity name in the commodity library with the prefix of the string entered by the user, always can not give the user prompts to search for non-existent goods, but also need to achieve sorting according to the matching results.

We are based on the Completion Suggest provided by ElasticSearch. Just add the following attributes to the field when creating the index:

"fields":{
   "suggest":{
   "type":"completion",
   "analyzer":"ik_max_word"
}
Copy the code

For example, add Suggest to the commodity name field of the commodity index and select Completion as the type, then configure the following:

"goodsName":{
   "search_analyzer":"ik_smart",
   "analyzer":"ik_max_word",
   "type":"text",
   "fields":{
       "suggest":{
           "type":"completion",
           "analyzer":"ik_max_word"
        }
    }
}
Copy the code

Note: This is not easy if the index already exists. ElasticSearch does not support modifying the index field, so you can only modify the index field through reindex.

ElasticSearch crashed after about 10 minutes due to lack of memory, and only 9 million records were synchronized. Reindex must be very careful to evaluate the risk based on the number of documents indexed and available memory.

Modify index field information by using reindex

  • Step 1: Create a new index

PUT/New index name

{
  "mappings": {
    "properties": {
      // Other fields are omitted
      "goodsName": {
        "search_analyzer": "ik_smart"."analyzer": "ik_max_word"."type": "text"."fields": {
          "suggest": {
            "type": "completion"."analyzer": "ik_max_word"}}}}},"settings": {"index": {"number_of_shards":5."number_of_replicas":3}}}Copy the code
  • Step 2: Executereindexoperation

Reindex takes a long time when the amount of data is very large, so the reindex request URL needs to be executed asynchronously with the wait_for_completion=false parameter.

POST _reindex? wait_for_completion=false

{
  "source": {
    "index": "Old index name"
  },
  "dest": {
    "index": "New index name"}}Copy the code

Wait until the total number of records in the two indexes is the same before performing the next operation. That is, wait until the following two requests return the same result: GET/New index name /_count GET/old index name /_count

  • Step 3: Delete the old index

DELETE/Old index name

  • Step 4: Create an alias with the same name as the old index and point to the new index

POST _aliases

{
  "actions": [{"add": {
        "index": "New index name"."alias": "Old index name"}}}]Copy the code

Use prefixes to search for associative words

  • inKibanaCall interface in

GET/index name /_search

{
  "size": 0."suggest": {
    "goodsNameSuggest": {
      "prefix":"BOBO"."completion": {
        "field": "goodsName.suggest"."size":1}}}}Copy the code
  • useJavaCode implementation
@Service
public class GoodsSearchService{
    @Override
    public List<String> lenovoSearch(String inputStr) {
        CompletionSuggestionBuilder suggestion = SuggestBuilders
                .completionSuggestion("goodsName.suggest")
                .prefix(inputStr)
                .size(10);
        SuggestBuilder suggestBuilder = new SuggestBuilder();
        suggestBuilder.addSuggestion("mySuggest", suggestion);
        SearchRequest searchRequest = new SearchRequest("Commodity Index Name");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.suggest(suggestBuilder);
        searchSourceBuilder.size(0);
        searchRequest.source(searchSourceBuilder);
        try {
            SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
            Suggest suggest = response.getSuggest();
            // There is no data
            if (suggest == null) {
                return Collections.emptyList();
            }
            Suggest suggestResult = response.getSuggest();
            Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>>
                    suggestionResult = suggestResult.getSuggestion("mySuggest");
            List<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> list
                    = suggestionResult.getEntries();
            List<String> suggestList = new ArrayList<>();
            if (list == null) {
                return null;
            } else {
                for (Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option> e : list) {
                    for(Suggest.Suggestion.Entry.Option option : e) { suggestList.add(option.getText().toString()); }}}return suggestList;
        } catch (IOException e) {
            e.printStackTrace();
            returnCollections.emptyList(); }}}Copy the code

conclusion

The popular search recommendation function is relatively simple. Even if you do not use ElasticSearch, you can choose other analytical databases to replace it. However, it is difficult to use associative input and requires high performance. But ElasticSearch has solved all the features that seemed too difficult to implement.

Here’s an article: “How does Instant Autocomplete Google Search Really” Work “? .