Pesquisar

terça-feira, 1 de dezembro de 2009

Tutorial Apache Lucene 3.0.0 - motor de busca textual


Lucene um motor de busca textual

No dia 25 de novembro foi lançado a nova versão do motor de busca Lucene, a versão 3.0.0. Lucene é um framework de alta performance para busca textual feito em java. (há versões para php e .net também) Uma das características marcantes dele é a integração com Hadoop. (um framework para computação distribuida - que possui, dentre outros, um MapReduce e um sistema de arquivos distribuido HDFS)

Porque usar Lucene?

Imagine o seguinte cenário: Seu usuário deseja criar um modulo de gerenciamento de artigos e também expó-los na Internet. As informações do artigo são: título, autor e o contéudo. Normalmente cada artigo desse contém cerca de 1500 palavras e o usuário poderá buscar pelo conteudo desses artigos. Suponha que há 1 milhão e meio de artigos para serem cadastrados, o simples uso de um banco de dados e o operador LIKE '%palavra%' é inviável. E aí que entra um bom framework de busca textual, usá-lo passa ser a solução mais tranquila e viável. Além de trazer velocidade, também traz outras características nas buscas que dificilmente fariamos codificando tudo do zero.

Domain-Specific Language | Ubiquitous Language

Há vários termos usados na terminologia do framework, os principais são: Documento, Campo, Diretório, Indexador, Pesquisador, Analisador e Termo.

  • Documento - é o conjunto de dados que você deseja indexar, por ex. título, conteudo e autor formam o documento artigo, pode ser visto como um objeto no lucene é visto como Document.
  • Campo - é o dado, identificado, que pode ser analogo a um campo de uma tabela ou uma propriedade de um objeto no lucene é visto como Field.
  • Diretório - conceito abstrato que denota um local para guardar os índices no lucene é visto como uma interface Directory, que possui diversas implementações DbDirectory, FSDirectory, JEDirectory, RAMDirectory.
  • Indexador - é o responsável por indexar os documentos num diretorio no lucene é visto, geralmente, como IndexWriter.
  • Pesquisador - responsável por pesquisar uma Query no diretorio de índices, no lucene é visto como Searcher.
  • Analisador - atua como um filtro e faz uma pré-avaliação do que pode ser indexado ou não, no lucene há uma lista de vários analisadores.
  • Termo - pode ser visto como objeto que pode ser usado como parametro da pesquisa, composto pelo nome do campo e o valor a ser pesquisado, no lucene é visto como Term.

Como isso funciona?

A descrição a seguir é um resumo bem simplificado sobre o funcionamento de um motor de busca textual. A primeira fase, geralmente, é a indexação do documento, nessa fase o documento é analizado (por um Analizer que também já retira as stopwords) para posteriomente ser indexado.

Set stopWords = new HashSet();
stopWords.add("the");
stopWords.add("it");
stopWords.add("is");
IndexWriter writer = new IndexWriter(FSDirectory.open(INDEX_DIR),
new StandardAnalyzer(Version.LUCENE_CURRENT,
stopWords),
false,
IndexWriter.MaxFieldLength.LIMITED);


No Lucene você pode criar um índice usando o IndexWriter, a criação desse objeto envolve dizer onde será (ou está) o diretório dos índices, qual analizador você irá utilizar, se irá criar o indice e qual a quantidade máxima de campos.

O objetivo do indexador é indexar documentos, para tanto é necessário que haja documentos para serem indexados.
Document doc = new Document();
doc.add(new Field("nomedocampo", "valores a serem guardados", Field.Store.YES, Field.Index.NOT_ANALYZED));
doc.add(new Field("camp1","valores a serem analisados tokenizados",Field.Store.YES, Field.Index.toIndex(true, true)));
writer.addDocument(doc);
É o objetivo final de todo processo de busca textual é mesmo a busca em si.
String valueToBeSearched = "red";
String index = "indexDir"; //dirotorio base do indice
IndexReader reader = IndexReader.open(FSDirectory.open(new File(index)), true); //indexador
Searcher searcher = new IndexSearcher(reader); //pesquisador
QueryParser parser = new QueryParser(Version.LUCENE_CURRENT, field, analyzer); //transoformador do texto em uma query
Query query = parser.parse(valueToBeSearched); //a consulta (query) em si
TopScoreDocCollector collector = TopScoreDocCollector.create(hitsPerPage, false); //os melhores resultados
searcher.search(query, collector);
ScoreDoc[] hits = collector.topDocs().scoreDocs; //o conjunto de melhores documentos para a consulta

int maximo = hits.length;
Document doc = searcher.doc(hits[index].doc);
String valor = doc.get("nomeDoCampo");

Recursos na busca de documentos

Quando se faz uma pesquisa por documentos no Lucene podemos utilizar de alguns operadores (+, -, AND, NOT, OR, * e etc.) juntamente com o termo pesquisado ou apenas pesquisar uma frase completa.

Exemplo : termo
Consequência: Irá pesquisar a palavra "termo" nos documentos indexados.

Exemplo : termo OR palavra ( == termo palavra)
Consequência: Irá pesquisar "termo" ou "palavra" nos documentos indexados.

Exemplo : +termo +palavra ( == termo AND palavra)
Consequência: Irá pesquisar "termo" e "palavra" nos documentos indexados.

Exemplo : campo:termo
Consequência: Irá pesquisar "termo" no campo "campo" nos documentos indexados.

Exemplo : +homer +simpsons -house
Consequência: Irá pesquisar documentos que contenham homer e simpsons e não tenha house.

Exemplo : "termo exato"
Consequência: Irá pesquisar documentos que contenham exatamente "termo exato".

Exemplo :
java*
Consequência: Irá pesquisar documentos que contenham palavras que começem com java (javadb, javanet...).

Exemplo : java~
Consequência: Irá pesquisar documentos que contenham palavras similares a java como por ex. lava, jaba...

Finalizando tive as seguintes impressões sobre essa nova versão do Lucene.

Prós:
  • Maior facilidade para uso do framework, ex: criações de Fields estão bem mais fluentes.
  • Sensível otimização nos tempos de busca e indexação.

Contras:
  • Otimas ferramentas como luke ainda não tem suporte para a versão 3.0.0. (mas já há uma solicitação aberta para tal mudança.)
  • Muitas mudanças no core do framework; o que fez livros mais antigos quase perderem seu valor.

Referências

36 comentários:

salsa disse...

Olá Leandro, será que não teria como colocar o .java do exemplo pra gente poder fazer o download?

To tentando fazer aqui mas não estou conseguindo seguindo os passos que você descreveu.

Valeu! :)

Leandro disse...

Salsa, diga qual foi o "erro" que foi lhe mostrado!

salsa disse...

Segue o link com o meu codigo: http://rafinha.pastebin.com/J7A2Tf2r

Os erros estão na linha 55 (field e analyzer), na linha 57 (hitsperpage) e na linha 62.


Valeu.

Leandro disse...

Salsa seguinte, na linha 55 você deveria ter criado uma variável chamada field.. :) (ou você apenas substitui por "nome_do_campo".

Já o analyzer, você deve instânciar um Analyzer(p.ex: new StandardAnalyzer(Version.LUCENE_CURRENT) )

Na linha 62 ... basta muda o nome da variavel doc e substituir o index por um valor numerico. ;)

Substitua hitsPerPage por um número... ;)

Outro detalhe importantissimo são os imports... tenha certeza que eles vêm do pacote da apache.

Acho que é isso, mas se não funcionar posta novamente aqui suas dúvidas.

salsa disse...

Olá Leandro, desculpe as perguntas, é que não sou programador java, e estou tentando me virar da melhor maneira possível.

Aqueles problemas resolvi, agora só estou com esse:

Document doc = searcher.doc(hits[index].doc);

sabe oq pode ser?

Leandro disse...

sim, substitua "index" por um número inteiro. (note que esse número representará o índice do documento da pesquisa, 0 será o primeiro, 1 o segundo...)

salsa disse...

Leandro não sei se estou fazendo certo:

http://rafinha.pastebin.com/2tfCKAiY

Na verdade ele não deu erro nenhum, porém não printou nada.

Eu queria que ele me retornasse tipo um array com os resultados para eu percorrer.

Valeu pela ajuda, o seu post foi o único que encontrei sobre a versão 3.0.0 do lucene.

:)

Leandro disse...

Pelo seu código:

valueToBeSearched = "red";
Mude o valueToBeSearched = "fogo" por algo que esteja nos seus documentos como "fogo".... faça o teste.

salsa disse...

é eu já tinha mudado isso mas não consegui fazer funcionar o exemplo :(

salsa disse...

Consegui criar um indexador...
Pelo o que eu vi ele cria na pasta c:\lucene uns arquivos.

Porém quando tento efetuar uma busca, ele retorna 0 results.

Detalhe, a palavra "Brasil" que está no exemplo, esta sendo adicionada pelo indexer.

Você tem ideia do por que não retorna nada?

http://rafinha.pastebin.com/Zejm2vKS

Valeu :)

Leandro disse...

Tente mudar seu construtor de Field para receber Field.Index.toIndex(true, true) ao invés de Field.Index.ANALYZED.

Outra coisa feche o writer.close() logo depois de ter indexado tudo. (ou dá um flush)

Acho que vou escrever um EXEMPLO completo e funcional (tendo em mente que esse aqui eu fiz e testei)... se isso tudo não der certo.

salsa disse...

funcionou legal.. valeu pelo help :)

Allan Aguiar disse...

Muito bom seu artigo. Agora quando você citou a pesquisa por Java*, você disse que o Lucene iria exibir os resultados que comecem com Java, certo? Contudo, não tem funcionado. Ele dá preferência até um certo ponto para as ocorrências que comecem com Java, por exemplo. Mas não é uma regra. Outro caso quando coloco NM_PESSOA:"JOSE DA SILVA*". O solr exibe primeiro ABAAO JOSE DA SILVA, ALICE JOSE DA SILVA, e por aí vai. Já passou por isso? GOstaria que encontrasse APENAS as inicias como JOSE DA SILVA. Tentei também assim...

NM_PESSOA:"JOSE DA SILVA*" AND -NM_PESSOA:"*JOSE DA SILVA"...Mas nada.

Leandro disse...

Allan ainda não realizei nenhum teste nesse sentido... penso que ele deve priorizar (classificar sua lista de resultado) esses que começam. Penso que o modo em que o "documento" foi indexado é por vetores de palavras indicando o documento. Então quando vocÊ diz que quer algo que comece com XXX* ele vai procurar no "indice" os documentos que contém essa palavra com outro fim. (veja um doc como uma composição de palavras... um doc pode apontar para uma palavra e uma palavra pode apontar pra vários docs... e por aí vai, lista inversa... e outros conceitos da busca textual)

Porém para realizar a sua pesquisa acho que basta usar o SQL normal mesmo (o grande problema do operador % está relacionado ao uso do mesmo no inicio like '%fff') ...

Select * From Tabela where Nome like 'comeca%';

Jader disse...

Estou fazendo um sistema de stopwords que leia 10 frases e retire os stopwords de cada uma das frases, se poder me ajudar serei muito grato.

valeu!!!

Jader disse...

Estou fazendo um sistema de stopwords que leia 10 frases e retire os stopwords de cada uma das frases, se poder me ajudar serei muito grato.

valeu!!!

Leandro disse...

"sistema de stopwords que leia 10 frases e retire os stopwords de cada uma das frases"

Essa retirada é para analise / consulta ou você realmente vai editar o "arquivo" e remover essas palavras? Lembre-se que o lucene é um motor de busca textual... Dessa forma o jeitão Lucene de pensar sobre stopWords poderia ser visto assim... dado um analisador (StandardAnalyzer) e um conjunto de StopWords (um arquivo, um array de strings) ele trabalha (indexa e procura) já sabendo dessa lista de stopwords.

Gabriela disse...

Olá! Sou nova com relação a esse assunto e queria saber se o Lucene permite a escolha de qual técnica de RI utilizar, vetorial, booleano..

Leandro disse...

Sim, gabrilea você pode inclusive implementar outras técnicas.

Gabriela disse...

Obrigada por responder!

Então é preciso implementar cada modelo? Existe algum momento especifico que eu preciso implementa-los (por exemplo o booleano)? Ainda estou em fase de conhecimento do Lucene então você deve imaginar o quanto estou meio perdida hehe

Desde já agradeço as dicas :)

Leandro disse...

Gab,

Por usar técnica vetorial ou bolean entendo como a parte de crição da estratégia da Query.

Para bolean podemos utilizar : http://lucene.apache.org/java/3_0_1/api/core/org/apache/lucene/search/BooleanQuery.html

Já para vetorial eu acredito que seja multi phrase: http://lucene.apache.org/java/3_0_1/api/core/org/apache/lucene/search/MultiPhraseQuery.html

Na definição da Query dá pra ver todas as implementações da mesma http://lucene.apache.org/java/3_0_1/api/core/org/apache/lucene/search/Query.html

Gabriela disse...

Os sites estão certos?
Não estou conseguindo abrir aqui :(

Leandro disse...

Sim, eu os copiei do browser. Veja se não está deixando de copiar corretamente, porque alguns deles quebram a linha. (; Posta ai se conseguiu ou não.

Gabriela disse...

Não consegui, nem fazendo manualmente deu certo :( É o mesmo link para os 3 mesmo?

Gabriela disse...

No site consegui chegar até http://lucene.apache.org/java/3_0_1/api/core/index.html

Leandro disse...

Me passa seu email que tento te ajudar.

Gabriela disse...

gabrielacruz86@gmail.com :)

rodrigo disse...

leandro, boa tarde.

preciso desenvolver um trabalho onde vou realizar o(a) stemming das palavras e a remoção de stop-words, tudo na lingua portuguesa. tenho pesquisado bastante e vi que o lucene atende minhas necessidades.
existe um problema: o projeto que vou desenvolver tem que ser em JAVA, porém sou iniciante. poderia me ajudar a utilizar estas funções do lucene?
desde já agradeço.

email: rbirruga@yahoo.com.br

Leandro disse...

comece do simples, já executou algo com lucene?

rodrigo disse...

sim, executei um exemplo bem básico.

faço uma indexação na memória (pelo que vi o indice fica criado apenas enquanto o programa está executando) e inseri alguns itens classificados e fiz uma busca. tudo isso com valores estáticos atribuidos em linhas de código. eu percebi que fez o stemming das palavras, mas como era um exemplo em inglês, foi feito na lingua inglesa.
faz pouco tempo que comecei a estudar o lucene, por isso ainda estou meio perdido. o fato de eu ser iniciante em JAVA também não me ajuda muito.
to pesquisando basatante sobre o lucene, e conhecimento teorico sobre ele ja tenho algum. to esbarrando mesmo é na hora das implementações.
então, como disse, to com dificuldade para testar o stemming e remoção de stop words na lingua portuguesa.
estou a versão 3.4.0 do lucene, acho que é a ultima.
grato

Gabriela disse...

Rodrigo, no exemplo básico do Lucene vc pode trocar o StandardAnalyzer (que é o default) por BrazilianAnalyzer

BrazilianAnalyzer analyzer = new BrazilianAnalyzer(Version.LUCENE_34);

Não esqueça de importar o jar Analyzer (lucene-analyzers-3.4.0.jar)

Fabiano disse...

Ola,

Boa explicação sobre o Lucene, o que estou me batendo é que preciso aplicar isso junto com o JSF+MYSQL.


Voces tem algum exemplo ou site a respeito?

Grato

Leandro disse...

Olha aconselho a utilizar o Hibernate Lucene (http://www.hibernate.org/subprojects/search.html) ele já integra, por anotação, o Lucene o JPA e deixa isso tudo disponível para o JSF.

É independe do banco.

Leandro disse...

Olha aconselho a utilizar o Hibernate Lucene (http://www.hibernate.org/subprojects/search.html) ele já integra, por anotação, o Lucene o JPA e deixa isso tudo disponível para o JSF.

É independe do banco.

fernandofranzini disse...

Ola Leandro

Eu precisaria ter uma busca como a usada do Lucene para fazer buscas e criar links paginas html geradas dentro da uma solução JSF com XHMLT.
É possível? Vc conhece alguma coisa falando disso?
Obrigado.

Leandro disse...

Fernando, se te entendi bem dê uma olhada no solr é um motor de indexação que também funciona com um crawler (código que vai "acessar" os links que você gera e indexá-los)