Elemar DEV

Tecnologia e desenvolvimento

Hello, Lucene

Olá pessoal. Tudo certo!?

Como você implementa busca em seus sistemas?! Se você faz isso “no braço”, provavelmente você está perdendo a oportunidade de entregar algo muito bacana para seu cliente.

Nesse post, mostro os fundamentos de implementação de buscas usando Lucene .net. Trata-se de um “port” de um poderoso engine de busca, usado em muitos sites importantes.

Este é um post introdutório. No futuro, pretendo mostrar cenários avançados considerando, inclusive, “correção de texto” (semelhante a apresentada pelo Google quando buscamos por uma palavra que não existe).

Importante: @tucaz é o grande responsável por este (e futuros posts) sobre este tema aqui no blog.

Um pouco de conceito

O que é Lucene?

Segundo a descrição do site oficial:

Apache Lucene(TM) is a high-performance, full-featured text search engine library written entirely in Java. It is a technology suitable for nearly any application that requires full-text search, especially cross-platform.

Apache Lucene is an open source project available for free download.

Há versões desse engine para Perl, Python, Ruby, C/C++, PHP, and C# (.NET).

Com Lucene, podemos entregar mecanismos de buscas avançados, em nossos sistemas, para qualquer fonte de dados.

O que é Lucene .net?

Lucene.net é um “port” do projeto original em Java, totalmente escrito em C# para .NET. Todo código-fonte está no Github.  A descrição do site oficial é:

Lucene.Net is a port of the Lucene search engine library, written in C# and targeted at .NET runtime users. The Lucene search library is based on an inverted index. Lucene.Net has three primary goals:

  1. Maintain the existing line-by-line port from Java to C#, fully automating and commoditizing the process such that the project can easily synchronize with the Java Lucene release schedule;
  2. Maintaining the high-performance requirements excepted of a first class C# search engine library;
  3. Maximize usability and power when used within the .NET runtime. To that end, it will present a highly idiomatic, carefully tailored API that takes advantage of many of the special features of the .NET runtime.

Entendendo a arquitetura de um engine de busca

A figura que segue descreve, adequadamente, a arquitetura de um engine de busca (foi extraída do “bom” Lucene in Action):

image

Como você pode ver, há dois momentos importantes na utilização da engine:

  1. geração do índice, a partir da fonte de dados;
  2. consumo do índice, através das interfaces de pesquisa.

Em palavras simples, extraímos dados de nossas fontes, gerando unidades de pesquisa (conhecidas como Documents) e, com elas, geramos um índice. Depois, utilizamos esse índice para “recuperar” informações dessas unidades de pesquisa para obter resultados. Ou seja, o engine não faz uma busca direta.

Um exemplo simples

Para dar uma idéia de como toda essa estrutura funciona, na prática, vamos implementar um cenário simples usando learning tests (veja o post onde explico esse conceito).

Referências necessárias

Para construir o exemplo de hoje, utilizarei apenas referências disponíveis no NuGet. Veja:

  • Lucene.net é a implementação core do engine;
  • NUnit será o framework de testes que utilizaremos;
  • SharpTestesEx é um helper para escrita dos asserts;
  • SharpZipLib é dependência do Lucene.net
  • SpecFlow é necessário para que eu possa escrever especificações (veja série sobre BDD).

image

Fontes de dados

Lucene.net é excelente para fazer buscas em qualquer fonte. Para fins de simplicidade, optei, nesse projeto, por fazer buscas em arquivos textos.

Utilizei textos clássicos que encontrei na Internet.

image

Importante: Lembrar de setar, em cada um dos arquivos, as propriedades Build Action para None e Copy to output directory para Copy Always.

A especificação

Penso que uma boa forma de explicar como funciona Lucene seja com uma especificação. Essa será a feature que iremos implementar hoje:

image

Considere:

  • Mantenho a criação do índice e sua utilização separadas (contexto e cenário);
  • crio o índice a partir dos arquivos que adicionei no exemplo;
  • realizo as buscas por strings e confiro o número de ocorrências e o nome do arquivo onde a primeira ocorre.

Eis o código com as etapas para essa feature:

using System.IO;

using TechTalk.SpecFlow;

using Lucene.Net.Documents;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Index;
using Lucene.Net.Store;
using Lucene.Net.Util;
using Lucene.Net.Search;
using Lucene.Net.QueryParsers;

using SharpTestsEx;

namespace Learning.Lucene
{
    [Binding]
    public class Steps
    {
        internal static readonly DirectoryInfo IndexDir =
            new DirectoryInfo(".");

        // ReSharper disable InconsistentNaming
        private IndexWriter writer;
        // ReSharper restore InconsistentNaming

        [Given(@"que abri um novo índice")]
        public void DadoQueAbriUmNovoIndice()
        {
            writer = new IndexWriter(
                FSDirectory.Open(IndexDir),
                new StandardAnalyzer(Version.LUCENE_29), true,
                IndexWriter.MaxFieldLength.LIMITED);
        }

        [Given(@"carreguei o arquivo (.*)")]
        public void DadoCarregueiOArquivo(string arquivo)
        {
            var document = new Document();
            var content = new StreamReader("Samples/" + arquivo);

            document.Add(
                new Field("content",
                    content)
                    );

            document.Add(
                new Field("filename",
                    arquivo,
                    Field.Store.YES,
                    Field.Index.NOT_ANALYZED)
                    );

            writer.AddDocument(document);
        }

        [Given(@"fechei o índice")]
        public void DadoFecheiOIndice()
        {
            writer.Close();
        }

        // ReSharper disable InconsistentNaming
        private IndexSearcher searcher;
        // ReSharper restore InconsistentNaming

        [When(@"carrego o searcher")]
        public void QuandoCarregoOSearcher()
        {
            searcher = new IndexSearcher(
                FSDirectory.Open(IndexDir), true
                );
        }

        // ReSharper disable InconsistentNaming
        private TopDocs hits;
        // ReSharper restore InconsistentNaming
        [When(@"procuro por (.*)")]
        public void QuandoProcuroPor(string texto)
        {
            var parser = new QueryParser(Version.LUCENE_29,
                "content",
                new StandardAnalyzer(Version.LUCENE_29)
                );

            var query = parser.Parse(texto);
            hits = searcher.Search(query, 10);
        }

        [Then(@"encontro (.*) hits")]
        public void EntaoEncontroNHits(string n)
        {
            hits.TotalHits.Should().Be(4);
        }

        [Then(@"o primeiro hit está no arquivo (.*)")]
        public void EntaoOPrimeiroHitEstaNoArquivo(string arquivo)
        {
            var document = searcher.Doc(hits.ScoreDocs[0].doc);
            document.Get("filename").Should().Be(arquivo);
        }

        [Then(@"fecho o searcher")]
        public void EntaoFechoOSearcher()
        {
            searcher.Close();
        }

    }
}

Tirando especificidades da API do Lucene, acho que o propósito de cada etapa tenha ficado bem claro:

  • As primeiras etapas criam “Documents” a partir dos diversos arquivos e as acrescentam no índice;
  • As etapas da sequência, carregam o índice; executam consultas; conferem os resultados.

Vejamos os testes rodando…

image

 

Falamos “Hello, Lucene” e .. tivemos resposta.

Era isso.

11 comentários em “Hello, Lucene

  1. alexsandro_xpt
    10/05/2012

    :D Lucene por aqui, muito legal, me conta ai Elemar, voce ja usou o Lucene em algum projeto de cliente, se sim nos conte o motivo de ter escolhido o Lucene. :)

    • elemarjr
      10/05/2012

      Não, cara! Começando a estudar a fundo agora. Por influência do @tucaz.

  2. tucaz (@tucaz)
    10/05/2012

    Opa!

    Comecei um projetinho no Github pra fazer um wrapper no Lucene que facilite o gerenciamento e querying de índices e o uso facilitado de facets e pedi ajuda ao Elemar que na hora topou.

    Aos poucos estou levando pra frente mas já tem alguma coisa lá embora pouco polido.

    https://github.com/tucaz/LuceneIndexManager

    Excelente introdução Elemar!

    • elemarjr
      10/05/2012

      obrigado, grande Tucaz

    • sricanesh
      10/05/2012

      Ao invés de criar um wrapper, por que não usar o Apache SOLR, com a vantagem de usar o Lucene do Java que esta mais atualizado?

      Claro, se o motivo de fazer é pela diversão de programar para aprender mais sobre o assunto, meu argumento é totalmente invalido :P

  3. Rodrigo S. Andrade
    10/05/2012

    A especificação não está técnica demais? coisas como abrir e fechar índice me parecem mais detalhes de implementação, no caso o importante não é a busca? Nunca usei BDD, então sou apenas um curioso tentando entender o conceito!

    • elemarjr
      10/05/2012

      Na verdade.. depende :-)

      Concordo com você, talvez a especificação tenha ficado técnica demais. Entretanto, meu objetivo, aqui, não foi escrever uma especificação de auto nível. No lugar disso, queria fornecer uma descrição textual, mais detalhada das etapas que estava cumprindo.

  4. Pingback: Operações básicas no índice do Lucene « Elemar DEV

  5. naka
    20/05/2012

    quem quiser ver o lucene funcionando de verdade entre em http://odiario.com utilize a busca de noticias ou pode entrar em imoveis.odiario.com ou veiculos.odiario.com abraços.

  6. Luciano Pimenta
    02/08/2012

    Ola,
    Estou tentando usar o Lucene.NET em uma necessidade que tenho.
    Vi exemplos e fiz alguns testes.

    Preciso comparar um texto com dados do banco e saber se o texto inserido é igual ou semelhante ao que já existe no banco.

    O Lucene consegue me dar essa característica (semelhança)?

    Att

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

Informação

Publicado às 10/05/2012 por em Post e marcado , .

Estatísticas

  • 629,849 hits
%d blogueiros gostam disto: