Site icon Eolya Consulting

Introduction à Lucene

Lucene est une librairie open source en Java 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 et Antiword pour les fichiers Microsoft Word.

Obtenir et utiliser Lucene

L’ensemble des versions de Lucene sont disponibles ici. La fichier lucene-x.x.x.tgz est suffisant, mais le fichier lucene-x.x.x-src.tgz 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.

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

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, …

Documentation

La documentation sous la forme de javadoc est disponible depuis la page officiel de Lucene. En plus de la javadoc, on y trouve une FAQ, un Wiki et différents articles intéressants.

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.

Quitter la version mobile