Di recente, per un progetto universitario ho avuto necessità di leggere e indicizzare una moltitudine di tweets contenuti in una specifica colonna di un file xls. Quando si tratta di indicizzare ed effettuare query di ricerca su una moltitudine di dati, Lucene (supportata da Apache) ci viene in soccorso. Lucene non è nient’altro che un motore di ricerca testuale implementato in Java. Presente di default in molti IDE di programmazione, come Netbeans ad esempio.

Il codice proposto qui sotto, attraverso alcune librerie (anch’esse di apache), permette di leggere una moltitudine di dati da un file xls, salvarli in un indice (file-index) ed effettuare delle query sopra di esso. Può tornare utile per esperimenti di ricerca e per capire parte del funzionamento dei motori di ricerca moderni.

 
package lucene.samples;
 
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopScoreDocCollector;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.Version;
// librerie per leggere file .xls
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
 
 
import java.io.IOException;
import java.util.Iterator;
import org.apache.lucene.store.FSDirectory;
 
public class HelloLucene {
  public static void main(String[] args) throws IOException, ParseException {
    // 0. Specifica l'analyzer per tokenizzare il testo.
    //    Lo stesso analyzer dovrebbe essere usato per l'indexing e la ricerca
    StandardAnalyzer analyzer = new StandardAnalyzer(Version.LUCENE_47);
 
    // 1. creiamo l'index
    Directory index = FSDirectory.open(new File("file-index")); //new RAMDirectory(); // nel caso volessimo l'index in RAM
 
    IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_47, analyzer);
 
    IndexWriter w = new IndexWriter(index, config); // index
 
    Integer z, r = 0;
 
    try
        {
            FileInputStream file = new FileInputStream(new File("./src/lucene/samples/elenco-tweets.xls")); // controllare che il file xls sia nella cartella di lavoro del programma: System.out.println(new File(".").getAbsoluteFile());
 
 
           Workbook workbook = WorkbookFactory.create(file);
            //Creiamo un'istanza del Workbook per avere la referenza al file .xlsx
 
            //Otteniamo il primo documento dal workbook
            Sheet sheet = workbook.getSheetAt(0);
 
            //Iteriamo su ogni riga, una per una
            Iterator<Row> rowIterator = sheet.iterator();
            while (rowIterator.hasNext()) 
            { // fa l'iterazione di ogni riga
                Row row = rowIterator.next();
                //Per ogni riga, iteriamo su tutte le colonne
                Iterator<Cell> cellIterator = row.cellIterator();
 
                z = 0;
                if (r >= 1) // saltiamo la prima riga del file xls
                while (cellIterator.hasNext()) // aggiungi && r < 2  per limitarlo all'analisi di 1 sola riga ad esempio.
                { // per ogni riga fa l'iterazione di ogni colonna
                    Cell cell = cellIterator.next();
                   if (z == 2){ //scegliamo la colonna da iterare
 
                    String testo = cell.getStringCellValue().toLowerCase();
                    testo = testo.replaceAll("[^ -~]",""); // eliminiamo tutti i caratteri non-ascii
                    testo = testo.trim().replaceAll(" +", " ");
 
                    //System.out.println(testo);
                    addDoc(w, testo, String.valueOf(row.getRowNum()-1));
 
                   }else{ z++; }
 
                }
 
                r++;
            }
 
            file.close();
              }
        catch (Exception e) 
        {
            e.printStackTrace();
        }
 
 
    w.close();
 
    // 2. query
    String querystr = args.length > 0 ? args[0] : "hackers tribe";
 
    // L'argomento "title" specifica il campo di default da usare
    // when no field is explicitly specified in the query.
    Query q = new QueryParser(Version.LUCENE_47, "title", analyzer).parse(querystr);
 
    // 3. Ricerca
    int hitsPerPage = 10000;
    IndexReader reader = DirectoryReader.open(index);
    IndexSearcher searcher = new IndexSearcher(reader);
    TopScoreDocCollector collector = TopScoreDocCollector.create(hitsPerPage, true);
    searcher.search(q, collector);
    ScoreDoc[] hits = collector.topDocs().scoreDocs;
 
    // 4. Mostriamo i risultati
    System.out.println("Found " + hits.length + " hits.");
    for(int i=0;i<hits.length;++i) {
      int docId = hits[i].doc;
 
      Document d = searcher.doc(docId);
      System.out.println((i + 1) + ". " + "SCORE: "+ hits[i].score + "  ID_TWEET: " + d.get("isbn") + "  TWEET: " + d.get("title") );
    }
 
    // Il reader può essere chiuso quando non c'è più alcun accesso al documento
     reader.close();
  }
 
  private static void addDoc(IndexWriter w, String title, String isbn) throws IOException {
    Document doc = new Document();
    doc.add(new TextField("title", title, Field.Store.YES));
 
    // Usiamo un campo testuale per l'isbn perchè non lo vogliamo tokenizzato
    doc.add(new StringField("isbn", isbn, Field.Store.YES));
    w.addDocument(doc);
  }
}

Il codice è abbastanza intuitivo e commentato. Per qualsiasi dubbio o informazione aggiuntiva, commentate!