Pull to refresh

Индексирование и поиск с помощью Xapian в .NET

.NET *
Если слово Xapian вам незнакомо, рекомендую ознакомиться с небольшой статьей.
Вкратце же, Xapian — представляет собой написанный на с++ набор инструментов для индексирования текстовой информации, с возможностью поиска по базе индексированной информации. Для работы не требует установленного сервера, достаточно наличия его библиотек. Может обрабатывать огромные массивы информации(проверено до 1.5Тб), измеряемой миллионами документов. Является конкурирующим продуктом Sphinx и Apache Lucene.
Мной он был выбран из этих трех продуктов за возможность использования из .Net.



Прежде всего нужно скачать dll' ки Xapian под .Net.
Затем еще одну вспомогательную dll — Zlib1.dll, без нее будут выкидываться исключения при попытке обращения оберточной dll к скомпиленной на C++.

Собственно для работы это все что требовалось. Можно создавать проект. XapianCSharp.dll сразу добавляем в Reference. _XapianSharp.dll и zlib1.dll добавляем в проект(просто как контент), и помечаем Copy to Output Directory как Copy always.

Делаем две функции для тестирования работы:

....
using Xapian;
....
//Путь указываем куда хотим, если базы по нему не окажется
//она будет создана автоматически
string xapianBase="H:\\XapianDB\\xap.db";
....
//Индексирование файлов указанного каталога
//без рекурсии
private void IndexFolder(string path)
    {
      try
      {
        if (Directory.Exists(path))
        {
          string[] files=Directory.GetFiles(tbIndexFolder.Text);
          using (WritableDatabase database=new WritableDatabase(xapianBase, Xapian.Xapian.DB_CREATE_OR_OPEN))
          {
            using (TermGenerator indexer=new TermGenerator())
            {
              using (Stem stemmer=new Xapian.Stem("russian"))
              {
                indexer.SetStemmer(stemmer);
                foreach (string file in files)
                {
                  using (Document doc=new Document())
                  {
                    //Имя файла используется как ключ, оно и будет выдаваться при поиске
                    doc.SetData(file);
                    indexer.SetDocument(doc);
                    //Не забываем указывать кодировку ваших документов
                    indexer.IndexText(File.ReadAllText(file, Encoding.GetEncoding(1251)));
                    //Пишем в базу
                    database.AddDocument(doc);
                  }
                }
              }
            }
          }
        }
      }
      catch (Exception ex)
      {
        Write("Exception: "+ex.ToString());
      }
    }

private void Search(string searchText)
    {
      try
      {
        // Открываем базу для поиска
        using (Database database=new Database(xapianBase))
        {
          using (Enquire enquire=new Enquire(database))
          {
            using (QueryParser qp=new QueryParser())
            {
              using (Stem stemmer=new Stem("russian"))
              {
                Write(stemmer.GetDescription());
                qp.SetStemmer(stemmer);
                qp.SetDatabase(database);
                qp.SetStemmingStrategy(QueryParser.stem_strategy.STEM_SOME);
                using (Query query=qp.ParseQuery(searchText))
                {
                  Write("Parsed query is: "+query.GetDescription());
                  enquire.SetQuery(query);
                  //Тут указываем с какого по какой результат хотим получить
                  //в данном случае получим первые 100 совпадений
                  MSet matches=enquire.GetMSet(0, 100);
                  Write(String.Format("{0} results found.", matches.GetMatchesEstimated()));
                  Write(String.Format("Matches 1-{0}:", matches.Size()));
                  //Выполняем поиск
                  MSetIterator m=matches.Begin();
                  //Выводим результат поиска
                  while (m!=matches.End())
                  {
                    Write(String.Format("{0}: {1}% docid={2} [{3}]\n",
                          m.GetRank()+1,
                          m.GetPercent(),
                          m.GetDocId(),
                          m.GetDocument().GetData()));
                    ++m;
                  }
                }
              }
            }
          }
        }
      }
      catch (Exception ex)
      {
        Write("Exception: "+ex.ToString());
      }
    }


Функцию Write напишите на свой вкус.

Теперь создаем каталог с текстовыми файлами, или используем имеющийся, вызываем IndexFolder(имя_каталога), ждем пока файлы проиндексируются. И можем вызывать Search, передавая строку с ключевыми словами для поиска, разделенных пробелом.

Тестирование.

Конфигурация железа:
Intel Pentium III 996Mhz
Ram 256Mb

Количество индексируемых файлов: 641489
Объем индексируемых файлов: 2,38Gb

Время индексирования файлов: больше недели(помним о железе, на 4x Core операция займет, скорее всего, несколько часов, своп соответственно тоже снижает производительность в разы).
Нагрузка во время индексирования
image

Таблица среднего времени поиска
Количество слов Поиск Время
1 1 1883 мс.
1 2 28 мс.
1 3 31 мс.
2 1 175 мс.
2 2 36 мс.
2 3 41 мс.
3 1 1074 мс.
3 2 35 мс.
3 3 37 мс.


Показатели весьма оптимистичные, особенно для такой слабой машины.

Источники:
Статья на codeproject'е
Официальный сайт
Tags:
Hubs:
Total votes 36: ↑30 and ↓6 +24
Views 3.6K
Comments Comments 31