Skip to content


Highlighting query terms in Lucene

Você pode estar se perguntando, o que diabos faz esse Lucene? Se já o conhece, pode estar se perguntando, o que é Highlighting? Lucene é uma “search engine” escrito totalmente em Java. É um projeto open source da Apache. O objetivo deste artigo não é falar exatamente sobre o Lucene, isso Sérgio Veloso e Alexandre Barroso explicaram muito bem na edição 49 da Java Magazine.

Este artigo irá te ajudar a fazer um destaque (highlighting) nos termos da sua pesquisa. O destaque ocorre sobre o texto da pesquisa, ou seja, você busca por uma palavra em um texto e, quando encontrado, um fragmento do texto é exibido e o termo de busca é destacado.

Acredito que este tipo de funcionalidade em um sistema, agrega um diferencial para a aplicação, dá um toque especial ao seu aplicativo (tá bom… ficou muito afeminado este “toque especial” :D ). Falando sério, o seu cliente e até o seu chefe ficarão contentes em ver isso funcionando. Acredite, aconteceu comigo!

Como ilustração, vamos criar um Servlet simples que recebe o termo de busca e apresenta os fragmentos encontrados deste texto com o termo destacado. Baixe aqui o arquivo WAR e faça deploy em seu servidor de aplicações.

Primeiramente recebemos no método doPost do Servlet a palavra (expressão) de pesquisa:

String expressao = request.getParameter("expressao");

Nosso objetivo é construir um objeto Highlighter. Um Highlighter é baseado em três partes: “Fragmenter” – Fragmentador, “Scorer” – Marcador e “Formatter” – Formatador.

Em seguida montamos o termo de pesquisa:

Query query = <strong>new</strong> TermQuery(<strong>new</strong> Term("f", expressao));

Para então criarmos o Scorer para o Highlighter. Repare que o Lucene fornece várias formas de pesquisa. Estude, pesquise e ajuste a sua necessidade:

Scorer scorer = <strong>new</strong> QueryScorer(query);

O mais interessante vem agora. Como vimos, o Highlighter precisa de um formatador. Este objeto é responsável por “decorar” o termo localizado de alguma maneira. Existem três formatadores na API. Nós vamos utilizar neste exemplo o SimpleHTMLFormatter, que insere uma tag antes e depois do termo localizado.

Neste exemplo, iremos colocar o texto encontrado entre as tags “span” para alterar a fonte para cor vermelha e negrito.

Formatter formatter =   <strong>new</strong> SimpleHTMLFormatter("&lt;span style=\"color: rgb(255,0,0); font-weight: bold;\"&gt;", "&lt;/span&gt;");

Agora podemos criar o objeto Highlighter:

Highlighter highlighter = <strong>new</strong> Highlighter(formatter, scorer);

Ainda está faltando definir o fragmentador do Highlighter. Na API existem dois fragmentadores. Iremos utilizar o SimpleFragmenter que quebra o texto em fragmentos de tamanhos fixos. No construtor passamos o tamanho em bytes do fragmento:

Fragmenter fragmenter = <strong>new</strong> SimpleFragmenter(30);
highlighter.setTextFragmenter(fragmenter);

O Highlighter também precisa de um TokenStream. Um TokenStream enumera uma sequência de tokens. Você pode criar um TokenStream a partir de um analisador ou você pode criar seu próprio analisador de acordo com as suas necessidades. O papel de um analisador é analisar (óbvio) a expressão de pesquisa e retirar palavras comuns que sempre ocorrerão em um texto, por exemplo a letra ‘a’. Criamos o TokenStream a partir do analisador padrão do Lucene:

TokenStream tokenStream = <strong>new</strong> StandardAnalyzer().tokenStream("f", <strong>new</strong> StringReader(<em>text</em>));

Finalmente podemos recuperar os fragmentos com o termo destacado. Para isso usamos o método getBestFragments que recebe o TokenStream, o texto da pesquisa, quantas ocorrências queremos mostrar e qual deve ser a string utilizada como delimitador (usado nas extremidades dos fragmentos).

Atenção a neste método, pois o mesmo texto que utilizamos para fazer a tokenização deve ser usado para retornar os fragmentos. Lembre-se que quando você “tokenizou” o texto, ele obteve as posições das palavras.

String result =   highlighter.getBestFragments(tokenStream, <em>text</em>, 5, "...");

Neste momento a String “result” já tem o resultado da pesquisa com as tags incluídas. Como utilizamos tags HTML, basta exibir o texto no browser que a “mágica” já está pronta.

Para testar, digite http://localhost:8080/highlight/pesquisa no browser. Entre com a expressão de busca “java”. Você terá o resultado como mostra a figura abaixo:

Resultado

Espero ter ajudado com este mini-artigo. Qualquer dúvida estou a disposição.

Abraço a todos e até a próxima.

Posted in Java, Lucene.

Tagged with , , , , .


8 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

  1. Marcelo Lopes says

    Não consegui realizar este exemplo pesquisando direto no índice. Você utilizou um texto fixo, mas como ficaria para pesquisar no índice?
    Por exemplo, no índice tenho um campo conteúdo que foi indexado utilizando doc.add(new Field(”conteudo”, conteudo). “conteudo” é um Reader.
    Passo o campo “conteudo” para o TokenStream mas ele retorna nulo.

  2. Normandes Junior says

    Marcelo Lopes,

    Não consegui identificar bem qual seu problema, mas segue umas dias:

    1º) A primeira coisa a se fazer é indexar! rsrs (Tenho quase certeza que você está fazendo isso). Mas lembre-se que dependendo da forma que você indexar, você pode não armazenar o conteúdo, ou seja, às vezes pode ser um simples índice para então referenciar algo maior. Atente-se a como criar um Field (http://lucene.zones.apache.org:8080/hudson/job/Lucene-Nightly/javadoc/org/apache/lucene/document/Field.html)

    2º) Depois de indexado é necessário fazer a pesquisa, também tenho quase certeza de que você está fazendo corretamente. Só lembrando, teste para ver se você já tem o objeto Hits com o resultado da sua pesquisa. Se já tiver ok, ótimo agora você pode passar para o próximo passo e implementar o Highlight, senão, é melhor você estudar mais a respeito do Lucene pois o seu problema ainda não é no Highlight.

    3º) Supondo que você já tenha o objeto Hits “populado” você pode ir iterando por ele, recuperando os índices ou mesmo as “colunas” que você armazenou no índice e então ir concatenando em um StringBuffer ou StringBuilder (em java 5). Lembre-se do que eu escrevi no artigo, é muito importante que o mesmo texto (mesma String) passado para o método tokenStream seja passado para o método getBestFragments.

    Se tiver algum problema mande um novo post com seu email que eu entro em contado com você para tentar te ajudar vendo o seu projeto.

    Abraços e obrigado pela visita.

  3. Junior says

    Campeão, gostei mto do artigo. Fiz alguns testes aqui e funcionou show de bola. Agora tenho algumas dúvidas, vamos ver se vc pode me ajudar. Eu preciso fazer o seguinte com o lucene, preciso que o usuário entre com uma frase, tipo um nome completo: “joao da silva” e o sistema faça o hightlight para o mesmo…neste seu exemplo só funciona com uma palavra…com duas não funciona…o que eu tenho q fazer…Ah tem um outro detalhe, em arquivos .txt maiores, eu notei que algumas palavras ele não me retorna os fragments…o q pode ser?

  4. Normandes Junior says

    Olá Junior, que bom que você gostou do artigo.

    Quanto a pesquisa de mais de uma palavra é fácil de resolver. Você precisa trocar a forma de pesquisa. Comente as linhas:
    //Query query = new TermQuery(new Term(”f”, expressao));
    //Scorer scorer = new QueryScorer(query);

    E adicione as seguintes:
    MultiFieldQueryParser multiFieldQueryParser = new MultiFieldQueryParser(new String[]{”f”}, new StandardAnalyzer());
    QueryScorer scorer = new QueryScorer(multiFieldQueryParser.parse(expressao));

    Pronto. Agora a pesquisa irá destacar mais de uma palavra. Por exemplo, “Java” e “projeto”.

    Quanto ao seu problema de não retornar os fragments, tenta verificar se realmente é o mesmo texto que você esta passando para o analizador é o mesmo que você está passando para o highlighter.

    Abraços.

  5. Junior says

    Campeão, valeu pela dica. Quanto a não vir os fragments já resolvi, editei a classe highlighter e aumentei a variavel:

    public static final int DEFAULT_MAX_DOC_BYTES_TO_ANALYZE=50*1024*1024;

    Agora quanto a pesquisa, existe um problema, eu não quero procurar várias palavras, quero procurar apenas uma frase inteira. Imagine que eu tenha 10 arquivos dentro de um diretorio, ok. Dentro de 2 arquivos existe o nome completo: “FARNETANI JUNIOR” e nos outros 8 existe outros nomes com o final “JUNIOR”, eu quero que o sistema me resulte apenas a pesquisa da expressão inteira: “FARNETANI JUNIOR”, porém ele está me mostrando que existem nos 10 arquivos por causa apenas do JUNIOR!

    Na verdade o que eu tenho que fazer é assim, primeiro encontrar os nomes dos arquivos que contem o nome todo e depois abrir arquivo por arquivo e dar o highligh na expressao inteira!

    Segue o codigo abaixo reduzido:

    public Hits performSearch(String queryString) throws IOException,
    ParseException {

    Analyzer analyzer = new BrazilianAnalyzer();
    IndexSearcher is = new IndexSearcher(”c:/temp/indices”);

    QueryParser parser = new QueryParser(”conteudo”, analyzer);
    Query query = parser.parse(queryString);

    Hits hits = is.search(query);
    return hits;
    }

    Me responda uma coisa, na expressao: {”f”} isto significa o meu conteudo é isto?

    Qualquer dica será bem vinda!

    Valew!

  6. Emerson says

    Tenho uma duvida.

    Estou passando a seguinte query:
    (Creio AND deve) OR (faculdade AND Pilhas)

    As palavras “deve” e “faculdade” nao existem no texto, o que deixaria esta expressao Falsa. Porem o texto retorna com as palavras “Creio” e “Pilhas” destacados.

    Eu gostaria que o highlight tivesse o comportamento de nao destacar estas palavras. Sabe se tem como?

    Desde ja obrigado,
    Emerson

  7. Murilo says

    Olá, Normandes! Parabéns pelo artigo!
    Uma dúvida: esse processo de obter os fragmentos (para cada arquivo encontrado) afeta muito o tempo de busca?
    Obrigado,
    Murilo

  8. Normandes Junior says

    Olá Murilo, que bom que gostou do artigo. Infelizmente não fiz nenhum teste de performance para descobrir se o tempo de busca será afetado. Acredito que deve ser prejudicado sim, mas em números não sei te dizer.
    Abraços,
    Normandes Junior



Some HTML is OK

or, reply to this post via trackback.