Como criar sugestões de escopo e correções de consulta nas buscas

blog-search-results-dark-720x420.png

Com resultados de busca relevantes, você tem a chance de manter os consumidores no seu website de comércio eletrônico. De acordo com a Harris Poll, 76% dos consumidores online abandonam um website de varejo após uma busca malsucedida.

Por isso, é essencial que sua experiência de busca seja otimizada, para que os consumidores encontrem o que querem com rapidez. Esta é a teoria por trás de uma experiência de busca moderna: hoje em dia, não basta simplesmente oferecer uma barra de busca que retorne produtos correspondentes. O seu site de comércio eletrônico precisa incluir um conjunto completo de ferramentas de busca que guiem os usuários de forma personalizada aos produtos que eles querem comprar.

Neste blog, você vai aprender como deve começar a aprimorar a busca de comércio eletrônico adicionando dois recursos comuns: sugestões de escopo e correções de consulta do tipo “Você queria dizer...?”.

Veja a seguir exemplos testados com o Elastic 8.5. 

Sugestões de escopo 

Uma das dificuldades mais comuns entre os varejistas de comércio eletrônico são os catálogos de produtos enormes, com muitas categorias diferentes. Nesse cenário, é difícil analisar uma consulta simples para entender em qual domínio ou categoria o usuário possa estar interessado. Por exemplo, se o usuário busca “tela plana” em uma loja de eletrônicos, os resultados aparecem nas categorias de TVs, suportes de TV, monitores de computador etc. Oferecer suportes de TV quando o usuário está procurando uma TV pode não ser relevante nessa etapa da jornada de compra.

As sugestões de escopo ajudam os usuários a limitar as buscas por tópicos nos quais estão mais interessados, dando sugestões de consultas dentro de categorias ou marcas específicas.

É um recurso poderoso que ajuda os usuários a receberem um conjunto de resultados refinados rapidamente. A implementação das sugestões de escopo de busca usando a Search UI e o Elastic também é simples. A próxima seção ensinará você as etapas de como criar esse recurso. 

Colete dados para as sugestões

Conforme descrito no exemplo acima, à medida que os usuários digitam, eles recebem sugestões de consultas e escopos associados. Para criar essas sugestões, é comum usar dados de analítica para reunir as consultas e os escopos mais comuns geralmente associados pelos clientes.

Por exemplo, digamos que, após analisar os dados de analítica de nosso website de comércio eletrônico, descobrimos que a consulta principal é “tv lcd” e que os produtos em que os usuários clicam com mais frequência encontram-se na categoria “TV e Home Theater”.

Com esses dados, podemos criar um documento que represente uma sugestão de escopo correspondente, como este: 

{
   "name": "lcd tv",
   "weight": 112,
   "category": {
       "name": "TV & Home Theater",
       "value": 111
   }
}

Esse documento contém uma consulta e uma categoria associada. Podemos aumentar a complexidade dele  se for necessário. Por exemplo, podemos incluir uma lista de categorias ou outro escopo, como “marcas”.

Então, criamos um índice dedicado no Elasticsearch, e no nosso caso demos o nome “suggest”, para armazenar uma lista de sugestões de escopo que criamos usando os dados de analítica. Pode-se fazer isso com as transformações de dados ou usando um código personalizado, por exemplo, um script do Python. O exemplo de mapeamento de índice para nosso índice “suggest”  pode ser encontrado aqui

Sugestões de autopreenchimento

Agora que temos uma lista de sugestões pronta para uso, precisamos adicioná-la à nossa experiência de busca.

Usando a Search UI, em apenas alguns minutos criamos uma experiência de busca fundamentada no Elastic. Podemos então usar nossa interface recém-criada como base para as sugestões de escopo.

A Search UI usa um objeto de configuração para adaptar a busca às suas necessidades. Abaixo, um snippet do objeto da configuração que corresponde à configuração da sugestão. Nesse caso, especificamos o índice e os campos da consulta, além dos campos a serem retornados como parte dos resultados:

suggestions: {
     types: {
       popularQueries: {
         search_fields: {
           "name.suggest": {} // fields used to query
         },
         result_fields: {
           name: {
             raw: {}
           },
           "category.name": {
             raw: {}
           }
         },
         index: "suggest",
         queryType: "results"
       }
     },
     size: 5
   }

Em seguida configuramos o componente SearchBox para passar a categoria como parte da consulta ao navegar para a página do resultado da busca:

// Search bar component
<SearchBox
   onSelectAutocomplete={(suggestion, config, defaultHandler) => {
       // User selects a scoped suggestion - Category
       if (suggestion.name && suggestion.category) {
           const params = { q: suggestion.name.raw, category: suggestion.category.raw.name };
           // Navigate to search result page with category passed as parameters
           navigate({
               pathname: '/search',
               search: `?${createSearchParams(params)}`,
           });
       // User selects normal suggestion
       } else if (suggestion) {
           // Navigate to search result page
           window.location.href = "/search?q=" + suggestion.suggestion;
       }
       defaultHandler(suggestion);
   }}
   autocompleteSuggestions={{
       popularQueries: {
           sectionTitle: "Popular queries",
           queryType: "results",
           displayField: "name",
           categoryField: "category"
       }
   }}
   autocompleteView={AutocompleteView}
/>

Observe que passamos uma função de AutocompleteView para personalizar a visualização de autocompletar, de modo a exibir a categoria em paralelo à consulta sugerida. 

Abaixo, temos um snippet de código da função AutocompleteView que mostra como exibir uma consulta com o escopo associado: 

{suggestions.slice(0,1).map((suggestion) => {
   index++;
   const suggestionValue = getDisplayField(suggestion)
   const suggestionScope = getCategoryField(suggestion)
   return (
     <li
       {...getItemProps({
         key: suggestionValue,
         index: index - 1,
         item: {
           suggestion: suggestionValue,
           ...suggestion.result
         }
       })}
     >
       <span>{suggestionValue}</span>
       <ul><span style={{marginLeft: "20px"}}>in {suggestionScope}</span></ul>
     </li>
   );
})}

Em seguida, na página de resultado da busca, só precisamos processar os parâmetros da busca e filtrar o conjunto de resultados: 

 const [searchParams] = useSearchParams();
   useEffect(() => {
       if (searchParams.get('category')) addFilter("department", [searchParams.get('category')], "all")
   }, [searchParams]);

Depois que tudo estiver reunido, o resultado será este:

Video thumbnail

Correções de consulta conhecidas como “Você queria dizer...?”

Os consumidores podem ficar frustrados quando não veem resultados de busca relevantes devido a uma escolha inadequada dos termos de consulta ou a erros de digitação. Para evitar que haja poucos resultados ou nenhum na busca, é recomendável sugerir uma consulta melhor, o que chamamos de “Você queria dizer...?”.

Há muitas maneiras diferentes de implementar esse recurso, mas o que vamos mostrar é o modo mais objetivo de criá-lo, usando a Search UI e o Elastic. 

Análise de dados

O conjunto de dados que vamos usar para criar o recursoVocê queria dizer...?” é semelhante àquele usado para as sugestões de escopo, para que você possa sugerir uma consulta comum para os usuários, caso a consulta deles não retorne resultados.  

Use uma estrutura de documento similar para o “Você queria dizer...?”.

{
   "name": "lcd tv",
   "weight": 112,
   "category": {
       "name": "TV & Home Theater",
       "value": 111
   }
}

O mais importante nesta etapa é a maneira como os dados são indexados no Elasticsearch. Para oferecer sugestões relevantes, você precisa usar recursos específicos oferecidos pelo Elasticsearch, como os analisadores personalizados.

Abaixo, os mapeamentos e as configurações de índice usados em nosso exemplo: 

{
    "settings":
    {
        "index":
        {
            "number_of_shards": 1,
            "analysis":
            {
                "analyzer":
                {
                    "trigram":
                    {
                        "type": "custom",
                        "tokenizer": "standard",
                        "filter":
                        [
                            "lowercase",
                            "shingle"
                        ]
                    },
                    "reverse":
                    {
                        "type": "custom",
                        "tokenizer": "standard",
                        "filter":
                        [
                            "lowercase",
                            "reverse"
                        ]
                    }
                },
                "filter":
                {
                    "shingle":
                    {
                        "type": "shingle",
                        "min_shingle_size": 2,
                        "max_shingle_size": 3
                    }
                }
            }
        }
    },
    "mappings":
    {
        "properties":
        {
            "category":
            {
                "properties":
                {
                    "name":
                    {
                        "type": "text",
                        "fields":
                        {
                            "keyword":
                            {
                                "type": "keyword",
                                "ignore_above": 256
                            }
                        }
                    },
                    "value":
                    {
                        "type": "long"
                    }
                }
            },
            "name":
            {
                "type": "search_as_you_type",
                "doc_values": "false",
                "max_shingle_size": 3,
                "fields":
                {
                    "reverse":
                    {
                        "type": "text",
                        "analyzer": "reverse"
                    },
                    "suggest":
                    {
                        "type": "text",
                        "analyzer": "trigram"
                    }
                }
            },
            "weight":
            {
                "type": "rank_feature",
                "fields":
                {
                    "numeric":
                    {
                        "type": "integer"
                    }
                }
            }
        }
    }
}

Consulte a documentação dos sugeridores para saber mais detalhes sobre por que você deve usar mapeamentos e configurações específicas para receber sugestões relevantes. 

Como preparar a busca

Agora que o seu índice está pronto, você pode trabalhar na experiência de busca. Para receber sugestões com base em uma consulta, aproveite a API do Elasticsearch. Se você quiser menos complexidade para o cliente, crie um modelo de busca no Elasticsearch para incluir a consulta. Assim, basta executar o modelo de busca no cliente:

PUT _scripts/did-you-mean-template
{
   "script": {
       "lang": "mustache",
       "source": {
           "suggest": {
               "text": "{{query_string}}",
               "simple_phrase": {
                   "phrase": {
                       "field": "name.suggest",
                       "size": 1,
                       "direct_generator": [
                           {
                               "field": "name.suggest",
                               "suggest_mode": "always"
                           },
                           {
                               "field": "name.reverse",
                               "suggest_mode": "always",
                               "pre_filter": "reverse",
                               "post_filter": "reverse"
                           }
                       ]
                   }
               }
           }
       }
   }
}

Como receber sugestões da aplicação

Para receber sugestões do Elasticsearch, adicionamos uma API de backend que o cliente pode consumir em /api/suggest. Essa nova API chama o Elasticsearch, executa o modelo de busca e passa adiante a consulta para a qual você deseja sugestões e, em seguida, retorna uma sugestão para a consulta. A API de backend permite reduzir a complexidade na parte do frontend.

client.searchTemplate({
       index: "suggest",
       id: 'did-you-mean-template',
       params: {
           query_string: req.body.query
       }
   })

Por exemplo, caso a consulta seja “auto-falantes”, a API retorna a sugestão “alto-falantes”. Esse recurso se baseia em dados anteriormente ingeridos contendo as consultas mais comuns. A API de suggest retornará um termo que seja o mais próximo sintaticamente à consulta. 

Como adicionar à página de resultado o “Você queria dizer...?”

Agora podemos incluir o recurso “Você queria dizer...?” na aplicação de frontend. Para isso, continuaremos trabalhando na mesma aplicação React usada na parte de sugestões de escopo. 

A nova API que adicionamos pode ser consumida na aplicação React e exibe uma sugestão caso não haja resultados para a consulta atual. 

A ideia aqui é obter uma sugestão para cada consulta que o usuário inserir. Se não houver resultados, o usuário verá uma sugestão. Também é possível ter implementações alternativas: você poderá exibir sugestões se houver poucos resultados ou poderá executar a sugestão automaticamente em vez da consulta do usuário.

Na sua aplicação, temos um componente React chamado SearchResults que exibe nossos resultados de busca. Nele, podemos adicionar uma função que pega a sugestão em nossa API /pt/api/suggest de backend.

const fetchDidYouMeanSuggestion = async (query) => {
   const response = await fetch('/api/did_you_mean', {
       method: 'POST',
       headers: {
           Accept: 'application/json, text/plain, */*',
           'Content-Type': 'application/json',
       },
       body: JSON.stringify(query)
   })
   const body = await response.json();
   if (response.status !== 200) throw Error(body.message);
   return body;
}

Quando a consulta muda de acordo com o refinamento do usuário, você pode atualizar a sugestão chamando a API:

   // Get search params from dom router
   const [searchParams] = useSearchParams();
 
   useEffect(() => {
       // When the searchParams contains a query
       if (searchParams.get('q')) {
           // Set query for Search UI - Run the search
           setSearchTerm(searchParams.get('q'))
           // Fetch suggestion from backend API
           fetchDidYouMeanSuggestion({ query: searchParams.get('q') }).then(res => {
               setSuggestion(res.body?.suggest?.simple_phrase[0]?.options[0]?.text)
           })
               .catch(err => console.log(err));
       }
  }, [searchParams]);

Finalmente, exiba a sugestão no caso de ausência de resultados para a consulta do usuário:

{wasSearched && totalResults == 0 && <span>No results to show{suggestion ? <>, did you mean <span style={{ cursor: "pointer", color: "blue" }} onClick={() => navigateSuggest(suggestion)}>{suggestion}</span>?</> : "."}</span>}

O resultado final fica assim:

Video thumbnail

Conclusão

Neste post do blog, você aprendeu como criar facilmente os recursos “Você queria dizer...?” e as sugestões de escopo usando oElastic Enterprise Search. Esses recursos podem ser integrados de forma simples à sua experiência de busca existente, para que os usuários encontrem o que precisam com rapidez. Se você deseja revisar o código usado para criar os exemplos usados aqui, acesse o GitHub repo.

Oferecer uma experiência de busca moderna é essencial para os websites de comércio eletrônico e para muitos outros casos de uso, como suporte ao cliente, busca em website, busca interna no local de trabalho e aplicações de busca personalizada. As etapas apresentadas neste post do blog podem ser seguidas para acelerar uma experiência de busca melhor para todos os usuários finais.