Introduction à Lucene

logo_luceneLucene est une librairie open source en Java (mais il existe de nombreux portages) permettant d’ajouter des fonctionnalités de recherche plein-texte à vos applications. Le projet Lucene est chapeauté par « The Apache Software Foundation ». D’autres projets très connus et de grande qualité de la fondation sont : Apache HTTP server, Tomcat, Cocoon, Ant, …

Il s’agit bien d’une librairie avec laquelle il n’est pas fourni d’outils permettant l’indexation de données en quelques clics de souris et quelques paramétrages. Il faut donc en passer par du code Java afin de mettre en place une solution sur mesure de recherche plein-texte.

Principe

Lucene indexe et retrouve des « documents ». Par document, on ne parle pas de fichiers Excel, Word, PDF ou HTML, mais d’une structure de données constituée de champs. Un champ est une donnée possédant un nom (titre, auteur, date de publication, contenu, ..) et à laquelle est associé du texte. C’est ce texte qui est indexé, recherchable et affichable. Les documents indexés sont regroupés au sein d’une collection de documents appelée « index ». Un index peut contenir plusieurs centaines, milliers ou millions de documents et il est possible de créer autant d’index différents que le nécessite votre ou vos applications. Physiquement, un index est un répertoire (que vous spécifiez) hébergeant un nombre variable de fichiers (ça c’est l’affaire de Lucene).

Si le texte qui est à indexé est contenu dans des fichiers Excel, Word, PDF ou HTML, c’est de votre ressort d’en extraire de contenu textuel qui sera indexé. Il est possible d’utiliser par exemple pdftotext pour les fichiers PDF, Antiword pour les fichiers Microsoft Word ou tout simplement la boite à outils Tika qui permet l’extraction du contenu d’un grand nombre de format de fichiers.

Obtenir et utiliser Lucene

La version actuelle de Lucene est la 2.9 et est disponible ici. La fichier lucene-x.x.x.zip est suffisant, mais le fichier lucene-x.x.x-src.zip avec les sources devient vite intéressant lorsque l’on veut étendre les possibilités de lucene et disposer d’exemples de code.

Dans la suite de cet article nous allons voir un exemple minimaliste illustrant comment indexer et rechercher des données. Cet exemple nous permet d’introduire les concepts de base de Lucene : document, field, analyzer, query, hits, …

La première chose à faire afin de pouvoir développer des classes Java utilisant Lucene, c’est de créer un projet dans votre environnement de développement et d’y inclure la librairie principale de Lucene : lucene-core-x.x.x.jar. J’utilise pour ma part Eclipse. A noter qu’un bug dans Sun Java 1.6 a posé problème avec Lucene (détails ici), ce bug a été corrigé à partir de la version 1.6.0_10-rc-b28.

Un peu de pratique

L’exemple qui suit est constitué d’une unique classe LuceneIntroduction.java dont voici le projet Eclipse complet dans un fichier zip.

Squelette de la classe

Le code suivant constitue le squelette de la classe. Il déclare les packages nécessaires et la méthode main qui exécute successivement une méthode pour l’indexation et une méthode pour la recherche.

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.*;
import org.apache.lucene.index.IndexWriter;
 
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.Hit;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.IndexSearcher;
 
import java.io.File;
import java.util.*;
 
public class LuceneIntroduction {
  static final String INDEX_DIR = "c:\\temp\\index_test";
  public static void main(String[] args) {
    if (!index())
      System.exit(1);
    if (!searchAndDisplay("titi"))
      System.exit(1);
    if (!searchAndDisplay("bla"))
      System.exit(1);
  }
  // ...
}

Indexer des données

L’indexation de données met en oeuvre 4 classes Lucene.

IndexWriter c’est la classe qui donne accès aux index en écriture (création, ajout de document, optimisation, …)
Analyzer

il s’agit d’un ensemble de classes qui ont pour but le découpage du texte en « token » (mot) et la normalisation du texte à indexer. Les principaux analyzer fournis sont :

SimpleAnalyzer SimpleAnalyzer découpe le texte en mot et le converti en minuscule.
StopAnalyzer StopAnalyzer découpe le texte en mot, le converti en minuscule et supprime les mots vides (mots sans intérêt dans le processus de recherche : le, la, de …)
StandardAnalyzer StandardAnalyzer combine les deux analyzer précédents
Document Un Document représente une unité élémentaire d’information. Par exemple, indexer tous les fichiers Word d’un répertoire va ajouter dans l’index un Document Lucene par fichier. Ce sont des Documents qui sont retournés dans la liste de résultats d’une recherche. Comme cela a déjà été dit, un document est constitué de champs « Field » (nom / valeurs).
Field Il s’agit d’un sous élément d’un document. Les champs les plus fréquents sont : titre, auteur, date de publication, url et bien sur le texte du fichier Word, PDF ou HTML.

Le code suivant créer un index et ajoute 3 documents dans cet index. La méthode createDocument est plus particulièrement dédiée à la création d’un objet Document Lucene constitué de 3 champs : id, titre et texte.

public static boolean index () {
 
  File dir = new File (INDEX_DIR);
 
  if (dir.exists()) {
    System.out.println("Impossible de créer l'index dans le répertoire '"
                       + INDEX_DIR
                       + "', veuillez le supprimer d'abord.");
    return false;
  }	
 
  try {
    // Création de l'index
    IndexWriter writer = new IndexWriter(INDEX_DIR,
                                         new StandardAnalyzer(),
                                         true);
 
    // Création et indexation d'un premier document
    Document doc = createDocument ("1", "Titre 1", "bla bla");
    writer.addDocument(doc);
 
    // Création et indexation d'un second document
    doc = createDocument ("2", "Titre 2", "titi tutu");
    writer.addDocument(doc);
 
    // Création et indexation d'un troisième document
    doc = createDocument ("3", "Titre 3", "bla bla titi tutu");
    writer.addDocument(doc);
 
    // Fermeture de l'index
    writer.close();
 
  } catch (Exception e) {
    e.printStackTrace();
    return false;
  }	    
 
  return true;
}
 
private static Document createDocument (String id, String titre,
                                        String texte) {
 
  // Créer un document vide
  Document doc = new Document();
 
  // Créer le champ id
  doc.add(new Field ("id", id,
                     Field.Store.YES, Field.Index.UN_TOKENIZED));
 
  // Créer le champ titre
  doc.add(new Field ("titre", titre,
                     Field.Store.YES, Field.Index.TOKENIZED));
 
  // Créer le champ texte
  doc.add(new Field ("texte", texte,
                     Field.Store.NO, Field.Index.TOKENIZED));
 
  return doc;
}

On remarque que l’analyzer utilisé est spécifié au constructeur de l’objet IndexWriter. Et qu’un ensemble d’attributs importants sont spécifiés au constructeur de l’objet Field.

Le premier attribut est le mode de stockage de la donnée associée au champ : Field.Store.YES (stocké) ou Field.Store.NO (non stocké). Pour être indexé, une donnée ne doit pas forcément être stockée. On stockera un titre et un auteur par exemple car il doivent pouvoir être récupérés afin d’être affichés dans une liste de résultats. La totalité du texte d’un document PDF de 100 pages ne sera pas stocké mais juste indexé.

Le second attribut est le mode d’indexation de la donnée associée au champ : Field.Index.NO (non indexé), Field.Index.TOKENIZED (indexé avec découpage en mots), Field.Index.UN_TOKENIZED (indexé sans découpage en mots).

Rechercher

La recherche met en oeuvre 6 classes Lucene.

IndexSearcher c’est la classe qui donne accès aux index en recherche
Analyzer

Tout comme pour l’indexation les analyzer font partie du processus de recherche fin de normaliser les critères de recherche :

QueryParser un parser de requête
Query un objet qui représente la requête de l’utilisateur et utilisé par un IndexSearcher.
Hits Une collection d’éléments résultats de la recherche
Hit Un élément de la collection des résultats
Document Un document retrouvé et tel qu’il était lors de son ajout dans l’index (constitué des mêmes champs)

Le code suivant recherche les documents correspondant au critère et les affiche.

public static boolean searchAndDisplay (String criteria) {
 
  try {
    IndexSearcher searcher = new IndexSearcher(INDEX_DIR);
    QueryParser parser = new QueryParser("texte", new StandardAnalyzer());
    Query query = parser.parse(criteria);
    Hits hits = searcher.search(query);
 
    System.out.println("Résultats pour '" + criteria + "': " + hits.length());
    Iterator iter = hits.iterator();
    while(iter.hasNext()) {
      Hit hit = iter.next();
      Document doc = hit.getDocument();
      System.out.println(doc.get("titre"));
    }
  }
  catch (Exception e) {
    e.printStackTrace();
    return false;
  }	    	    
 
return true;
}

Résultat de l’exécution

Résultats pour 'titi': 2
Titre 2
Titre 3
Résultats pour 'bla': 2
Titre 1
Titre 3

Et si mes applications ne sont pas écrites en Java ?

Si vous acceptez de ne pas utiliser la toute dernière version de Lucene, vous pouvez utiliser une version portée dans votre langage de programmation préféré. Il existe de nombreux portages : Perl, Python, Ruby, C, C++, .NET, Common Lisp et PHP (recherche uniquement).

Si comme moi vous préférez utiliser la version originale Java en permanente évolution grâce à sa grande communauté de développeurs, voici ma suggestion : les Services Web.

Vous avez écrit une application Web dont l’interface est en PHP et les données sont dans une base SQL. Vous n’avez pas le choix, il faut développer votre moteur de recherche avec Java. Par contre, il faut également interfacer ce moteur de recherche avec votre application PHP pour lancer les recherches et afficher les résultats.

Pour la partie indexation des données il n’est pas nécessaire de s’interfacer avec le PHP, une application Java autonome peut être développée. Par contre, pour la recherche il faut interfacer le code PHP avec le code Java. La solution consiste en la mise en place de Services Web écrits en Java et fonctionnants sous Tomcat. Il n’est pas nécessaire de se lancer dans des Services Web au standard SOAP, le standard REST est largement suffisant. En gros une requête HTTP est envoyée à une servlet qui elle même retourne des résultats au format XML. Cette méthode permet d’interfacer la recherche Java/Lucene à tous types d’applications (WEB ou non WEB) et écrites dans n’ importe quel langage.

Extensions de Lucene

L’exemple présenté est comme je l’ai déjà dit « minimaliste ». Les possibilités offertes par Lucene sont très larges et se rapprochent des moteurs de recherche les plus puissants. En effet, il existe de nombreuses extensions fournies dans la distribution : analyzers avancées, corrections orthographiques, mise en évidence des termes recherchés dans les résultats, …

En voici la présentation dans la Sandbox Lucene

Utilitaires

Voici deux utilitaires intéressants en phase de développement.

Luke – outils de monitoring et de consultation des index

Limo – outils de monitoring des index

Documentation

La documentation est disponible ici. En plus de la javadoc, on y trouve une FAQ, un Wiki et différents articles intéressants.

jGuru fournit une FAQ Lucene intéressante.

Et enfin, il existe un livre en anglais basé sur une déjà ancienne version 1.4 : Lucene in Action (2004). La second édition du livre doit être disponible prochainement.

Support et assistance

Lucene est un projet open source, il n’existe pas de support à proprement parlé, mais il existe une mailing-list et un forum très actifs qui permettent d’obtenir de l’aide et des suggestions pour les problèmes les plus pointus.

Projets utilisant Lucene

Solr

eXtensible Text Framework (XTF)

Hibernate Search


 Partager...
 
  • Print this article!
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • E-mail this story to a friend!
  • LinkedIn
  • Netvibes
  • Reddit
  • Scoopeo
  • Technorati
  • Twitter
  • Wikio FR
  • Yahoo! Bookmarks