LXF128:Sphinx

Материал из Linuxformat.

Перейти к: навигация, поиск
Sphinx Скоростная полнотекстовая поисковая система для вашего web-сайта

Содержание

Sphinx: Найдется, что настроено

Безусловно, готовый поиск по сайту от популярных поисковых систем можно добавить за несколько минут, но это не дает гибкости вашего собственного движка. Никита Шультайс представляет Sphinx!

Сравнительная скорость полнотекстового поиска (мс) по 100 000 записям объемом 456 МБ. В поиске участвуют MySQL и два поисковых движка: Luсene и Sphinx.

Для организации поиска по сайту одни web-мастеры используют сервисы от Google или Яндекса, другие делают SQL-выборку по базе данных. Мы же применим движок для полнотекстового поиска Sphinx: он одинаково легко работает и с английским, и с русским языком, распространяется под лицензией GPL2 и доступен на самых популярных ОС: Linux, Windows, FreeBSD, Mac OS X. Вот некоторые его характеристики:

  • Высокая скорость индексации (до 10 МБ/с на новых машинах).
  • Высокая скорость поиска (0,1 с по тексту объемом 2–4 ГБ)
  • Поддержка индекса до 100 ГБ.
  • Поддержка MySQL, PostgreSQL и произвольных XML-данных.
  • API доступа к индексу для большинства интерпретируемых языков: PHP, Python, Perl, Ruby, а также Java.

Заинтригованы? Тогда приступим.

Установка

Установим Sphinx из исходных текстов: скачаем их с официального сайта http://www.sphinxsearch.com и дадим стандартные команды:

$ tar xzvf sphinx-0.9.9.tar.gz
$ cd sphinx
$ ./configure
$ make
$ make install

Как обычно, configure может принимать еще и параметры:

  • --with-mysql – компиляция с поддержкой MySQL (включено по умолчанию)
  • --with-pgsql – компиляция с поддержкой PostgreSQL (выключено по умолчанию)
  • --without-mysql – компиляция без поддержки MySQL

В своих примерах я буду использовать распространенную связку – MySQL и PHP, хотя на практике работаю с PostreSQL и Python.

Начинаем работу

Sphinx состоит из трех основных частей:

  • Утилита indexer – индексирует данные по заданным правилам.
  • Демон searchd – отвечает за поиск по индексу и выдачу результатов
  • Интерфейс доступа к searchd – консольная утилита search или библиотеки API для различных языков.

Сперва создадим базу данных и добавим в неё таблицу с информацией, по которой и будем осуществлять поиск. С первым, думаю, вы разберетесь сами (SQL-код показан ниже), а тестовая таблица с данными есть на диске LXF. Онасодержит несколько записей из раздела «Легковые автомобили» сайта с объявлениями.

CREATE TABLE auto (
  id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(255) NOT NULL,
  description TEXT NOT NULL DEFAULT '',
  add_date DATE NOT NULL,
  price INT NOT NULL,
  rating INT NOT NULL DEFAULT 0
 );

Перед поиском по данным их необходимо проиндексировать утилитой indexer, входящей в комплект поставки Sphinx. Она читает файл конфигурации и индексирует данные в соответствии с его параметрами. Создадим такой файл, с именем sphinx.conf:

source test_src {
  type = mysql
  sql_host = localhost
  sql_user = user
  sql_pass = password
  sql_db = sphinx
  sql_query_pre = SET NAMES utf8
  sql_query = \
     SELECT * FROM test;
}
index test_src {
  source = test_src
  charset_type = utf-8
  path = /var/www/sphinx/data/test
  enable_star = 1
  morphology = stem_enru
  min_word_len = 3
  min_prefix_len = 0
  min_infix_len = 3
  html_strip = 1
}
indexer {
  mem_limit = 256M
  max_iops = 40
}
searchd {
  listen = localhost:3312
  pid_file = /var/log/searchd/sphinx.pid
}

Файл конфигурации состоит из ряда блоков. Source test_src описывает источник данных, его тип (у нас – mysql) и параметры. В sql_query указан запрос, поставляющий данные для индексации. Можно задавать поля таблицы или вставить * для индексации всех полей, что мы и делаем. sql_query_pre отвечает за SQL-запрос, сообщающий MySQL (до извлечения информации из базы данных), что прием и передача данных будут в кодировке UTF-8. Безэтого могут быть проблемы с поиском по русским текстам.

В блоке index test_src – параметры индексации: source указывает на источник, для которого они применяются, path – на место хранения индекса. Все программы из Sphinx (indexer, searchd) должны иметь доступ к данному каталогу на чтение и запись. enable_star разрешает поиск с использованием групповых символов (wildcard). Этот параметр влияет только на поиск, и при его изменении не нужна переиндексация данных: достаточно перезапустить демон searchd. morphology определяет, морфологии каких языков учитывать при создании индекса: у нас это английский и русский. min_word_len задает минимальную длину слова при поиске, отсекая часто используемые короткие союзы, местоимения, предлоги и пр. min_prefix_len – минимальная длина префикса. Если здесь стоит 0, то префиксы не индексируются. Зачем это нужно? Если задать min_prefix_len = 3, то при разборе слова «сигнализация» в индекс будут занесены «сиг», «сигн», «сигна», «сигнал», ..., «сигнализация». Теперь, если в поиске набрать «сиг», запрос по сути будет «сиг*». Это резко увеличит индекс, время поиска и количество результатов. min_infix_len – расширенный вариант min_prefix_len. В нашем примере поисковый запрос будет иметь вид «*сиг», «сиг*» и «*сиг*». min_infix_len также увеличивает индекс и время поиска. А html_strip позволяет не учитывать при поиске HTML-тэги. Так как HTML не несет смысловой нагрузки, то и искать по нему незачем, ведь тэги в браузере не видны.

Блок indexer содержит два параметра (их может быть и больше, или вообще ни одного): mem_limit – лимит используемой оперативной памяти, по умолчанию 32 МБ, и max_iops – лимит числа операций ввода/вывода, по умолчанию 0 (не ограничено). Индексация интенсивно обращается к диску, и если диск нужен другим программам, число операций ввода/вывода следует сократить.

Блок searchd отвечает за параметры запуска поискового процесса. port – порт, который будет прослушивать демон, его номер должен быть уникальным для компьютера, чтобы не возникло конфликтов. pid_file – путь до pid-файла процесса.

Так как блоки source и index имеют свои имена, а блок index соответствует определенному источнику, то в одном файле конфигурации может быть описано несколько источников и индексов.

Создав файл конфигурации, запустим индексатор indexer:

indexer --all --config /var/www/sphinx/sphinx.conf

Параметр --all требует переиндексировать все индексы, определенные в sphinx.conf. В случае успеха вывод консоли будет:

1 Sphinx 0.9.8.1-release (r1533)
2 Copyright (c) 2001-2008, Andrew Aksyonoff
3
4 using config file '/var/www/sphinx/sphinx.conf'...
5 indexing index 'test_src'...
6 collected 20 docs, 0.0 MB
7 sorted 0.0 Mhits, 98.9% done
8 total 20 docs, 1877 bytes
9 total 0.216 sec, 8692.75 bytes/sec, 92.62 docs/sec

Из них можно узнать, сколько индексировано документов и сколько времени и места на это ушло. В каталоге /var/www/sphinx/data, указанном в параметре path блока index test_src, создано несколько файлов с именем test, но разными расширениями: test.spi, test.spd и т. д. Они отвечают за разные части индекса: заголовки, список слов, списки соответствия ID документа и ID слова и т. д.

Теперь запустим поискового демона (searchd):

searchd --config /var/www/sphinx/sphinx.conf

Учтите, что между --config и путем до файла конфигурации находится пробел, а не символ ‘=’. Наконец, попробуем поиск!

search --config /var/www/sphinx/sphinx.conf --index auto_src toyota

Если нам это удастся, вы увидите следующие строки

1 Sphinx 0.9.8.1-release (r1533)
2 Copyright (c) 2001-2008, Andrew Aksyonoff
3
4 using config file '/var/www/sphinx/sphinx.conf'...
5 index 'auto_src': query 'toyota ': returned 5 matches of 5 total in 0.000 sec
6
7 displaying matches:
8 1. document=2, weight=2
9 2. document=3, weight=1
10 3. document=4, weight=1
11 4. document=14, weight=1
12 5. document=19, weight=1
13
14 words:
15 1. ‘toyota’: 5 documents, 6 hits

Первые две строки «говорят» о версии Sphinx и его создателе. Встроке 4 указан используемый файл конфигурации. Затем, через двоеточие, идут название индекса, текст запроса («toyota») и результат: найдено 5 соответствий из 5 за 0.000 секунд (ваше время, разумеется, может быть другим). Наконец, приведены все найден ные документы и их вес (weight). Документ с номером 2 (строка 8) имеет наибольший вес: в нашей базе данных в записи с id=2 слово «toyota» встречается дважды: в названии и в описании. Со строки 14 идет отчет о частоте появления слов в документе: «toyota» встречается в 5 документах 6 раз. Выполнив команду

search --config /var/www/sphinx/sphinx.conf --index auto_src toyota vitz

мы увидим следующий вывод:

...
displaying matches:
1. document=14, weight=2
2. document=19, weight=2
words:
1. 'toyota': 5 documents, 6 hits
2. 'vitz': 2 documents, 2 hits

Поисковый движок учёл частоту появления всех слов запроса и нашел два документа, причем в одном из них (document=19) сочетание «toyota vitz» находится в заголовке. Во втором, в заголовке присутствует «toyota», а «vitz» – в поле описания. Но учтено только количество слов, а не их расположение. При добавке в команду атрибута -p будет учтена вся фраза.

search --config /var/www/sphinx/sphinx.conf -p --index auto_src toyota vitz
...
displaying matches:
1. document=19, weight=2
words:
1. 'toyota': 5 documents, 6 hits
2. 'vitz': 2 documents, 2 hits

Статистика появления слов осталась неизменной, но теперь поисковому запросу соответствует только один документ.

Ищем из PHP

Сам по себе поиск из консоли не очень полезен, но его можно привязать к нашему сайту. Для этого в Sphinx, в каталоге api дистрибутива, есть набор библиотек для разных языков Sphinx. Мы будем использовать PHP, и нам понадобится модуль sphinxapi.php.

Создадим каталог sphinx в корне нашего сервера под управлением Apache и добавим в него файл index.php следующего содержания (я предполагаю, что с HTML и PHP вы уже знакомы):

Справка о весе

Вес (weight) – это значимость документа. Чем он больше, тем более соответствует запросу найденный документ. Для вычисления веса используется ранжирование фразы и статистическое ранжирование. Ранжирование фразы основано на длине самой длинной общей подпоследовательности. Статистическое ранжирование основывается только на количестве слов в документе. В зависимости от режима поиска мы можем получить разные веса документов для одного и того же запроса.

1 <html>
 2 <head>
 3 <meta http-equiv=”Content-Type” content=”text/html; charset=utf-8” />
 4 <title>Поиск по базе</title>
 5 </head>
 6 <body>
 7 <?
 8 require(“sphinxapi.php”);
 9 if ($_GET && $_GET['text']) $text = $_GET['text']; else $text = “”;
 10
 11 print '<form action=”.” method=”get”>
 12 <input type=”text” name=”text” value=”'.$text.'” />
 13 <input type=”submit” value=”Найти”/></form>';
 14
 15 $mode = SPH_MATCH_ALL;
 16 $index = “test_src”;
 17
 18 $cl = new SphinxClient();
 19 $cl->SetServer(“localhost”, 3312);
 20 $cl->SetMatchMode($mode);
 21 $res = $cl->Query ($text, $index);
 22
 23 $ids = “”;
 24 if ($res === false) {
 25  print “Ошибка запроса: “ . $cl->GetLastError() . “<br/>”;
 26 } else {
 27  if ($res['total'] > 0) {
 28   foreach ($res[“matches”] as $key=>$docinfo) $ids .= $key.”,“;
 29  }
 30 }
 31
 32 $ids = substr($ids, 0, strrpos($ids, “, “));
 33
 34 mysql_connect(“localhost”, “user”, “password”);
 35 mysql_select_db(“sphinx”);
 36 mysql_query('SET NAMES UTF8');
 37 $res = mysql_query(“SELECT * FROM auto WHERE id IN (“.$ids.”);”);
 38
 39 if ($ids) {
 40  print “<table border='1'>”;
 41 for ($data=array(); $row=mysql_fetch_assoc($res); $data[] = $row) {
 42  print “<tr>”;
 43  print “<td>”.$row[“name”].”</td>”;
 44  print “<td>”.$row[“description”].”</td>”;
 45  print “<td>”.$row[“price”].”</td>”;
 46  print “</tr>”;
 47  }
 48 print “</table>”;
 49 }
 50 ?>
 51
 52 </body>
 53 </html>

Наша учебная программа не блещет красотой, но машину под желания виртуального покупателя подбирает споро.

Первые шесть и последние две строки относятся к простей шему HTML. Cтрока 8 подключает модуль для работы со Sphinx, а строки 9–13 – создание и обработка формы для отправки запроса. Строка 15 задаетрежим поиска SPH_MATCH_ALL – соответствие всем словам из запроса. Затем дано название индекса «test_src»: оно должно совпадать с названием индекса в файле sphinx.conf. Строка 18 создает экземпляр класса SphinxClient, для взаимодействия с поисковым сервером. Далее укажем параметры сервера (URL и порт), передаем режим поиска и в строке 21 создаем запрос.

Строка 24 проверяет, получен ли какой-либо результат, и если нет, $cl->GetLastError() выводит последнюю ошибку. А если да, мы проверяем количество найденных документов (строка 27), а в строке 28 в цикле заполняем переменную $ids их номерами.

Sphinx хранит только индекс, сами документы находятся в базе; максимум, что мы получим при поиске – номера (id) документов и некоторые целочисленные поля. Переменная $ids как раз и хранит номера найденных документов, через запятую, а так как в конце всегда будет лишняя запятая, строка 32 её удаляет.

В строках 34–36 мы соединяемся с базой данных и устанавливаем UTF-8 как рабочую кодировку, а затем выполняем SQL-запрос. Строки 39–49 – вывод данных согласно нашему запросу.

Открыв страницу в браузере, вы увидите уже не сухую статистику, а вполне конкретный текст.

Режимы поиска

Итак, ранжирование документов при выводе задается режимом поиска через метод SetMatchMode(). Мы применяли режим SPH_MATCH_ALL, с проверкой вхождения всех слов запроса (нет хоть одного – документ не попадает в результат); но есть и другие:

  • SPH_MATCH_ANY проверяет вхождение любого слова из запроса. Если хотя бы одно есть в документе, то он будет найден.
  • SPH_MATCH_PHRASE проверяет вхождение фразы целиком.
  • SPH_MATCH_BOOLEAN позволяет включать в запрос логические выражения. Например, при вводе ‘toyota & vitz’ будет проверяться вхождение обоих слов, как в SPH_MATCH_ALL; ‘toyota | vitz’– тоже, что SPH_MATCH_ANY; ‘toyota -vitz’ или ‘toyota!vitz’ отберёт все документы со словом toyota, кроме тех, чтосодержат ‘vitz’.
  • SPH_MATCH_EXTENDED позволяет использовать более сложные выражения, нежели SPH_MATCH_BOOLEAN.

Рассмотрим последний режим подробнее, на примерах.

  • Поиск слов в заданном поле. При вводе ‘toyota vitz’ выведутся два документа: ‘toyota vitz’ и ‘toyota raum’. Во втором документе слово vitz имеется в описании, и вряд ли это верный результат. Зато с конструкцией ‘@name toyota vitz’ сочетание toyota vitz будет искаться только в поле name. Можно также ограничить число слов, участвующих в поиске. Например, введя @description[5]ОТС, мы получим один документ; хотя аббревиатура ОТС встречается в двух, но во втором она не входит в первые пять слов поля description.
  • Поиск фразы. «Toyota vitz», взятой в двойные кавычки, включает режим точного соответствия фразы SPH_MATCH_PHRASE.
  • Использование кворума – минимального числа слов, которое должно встретиться. Например, «литье тонировка сигнализация»/2 означает, что для успешного поиска достаточно, чтобы в документе присутствовали любые два слова. Таких документов у нас 5, причем только в одном встречаются все три слова.
  • Близкое расположение слов. Если мы введем «защита двигателя» в двойных кавычках, то не найдем ни одного совпадения, так как в документах не встречается такая фраза. Но, введя «защита двигателя»~3, мы найдем документ, где присутствует сочетание «защита картера и двигателя», а между словами «защита» и «двигателя» будет не более трёх слов. Кстати, ввод слов в обратном порядке («двигателя защита»~3) даст такой же результат.
  • Строгое следование. Фразе «ОТС << хозяин» будут соответствовать документы, где слово «ОТС» стоит перед словом «хозяин», но не наоборот. Поэтому, введя такой запрос, мы найдем только один документ, хотя слова «ОТС» и «хозяин» встречаются в двух.

Режим поиска SPH_MATCH_EXTENDED также поддерживает логические выражения SPH_MATCH_BOOLEAN и другие, более сложные. Узнать подробности вы можете, обратившись к официальной документации http://www.sphinxsearch.com/docs/.

Для задания другого режима поиска, введите нечто вроде

$mode = SPH_MATCH_EXTENDED

в строке 15 листинга. Обратите внимание, что SPH_MATCH_EXTENDED вставляется без кавычек, потому что это импортированная константа, определенная в библиотеке sphinxapi.php.

Помимо сложных запросов, Sphinx поддерживает заменитель * (звездочка) для любого режима поиска. Ввод toy* тоже даст список всех документов, где присутствует Toyota. Важно, что нужно определить минимум 3 символа, иначе поиск не сработает. Меняя параметр min_word_len в файле sphinx.conf, это ограничение можно скорректировать.

И еще

Еще один режим поиска – SPH_MATCH_EXTENDED2 – позволяет использовать метод SetRankingMode() для ручного задания механизмов ранжирования. Как отмечено выше, при ранжировании результатов учитывается как вся поисковая фраза, так и количество ключевых слов. Но иногда требуется изменить это поведение, например, для увеличения скорости. Поэтому Sphinx поддерживает самостоятельное определение алгоритма ранжирования:

  • SPH_RANK_PROXIMITY_BM25, выбран по умолчанию. Сначала ищет соответствие фразе, а затем использует алгоритм BM25 для ранжирования по частоте появления слов.
  • SPH_RANK_BM25, используется только статистическое ранжирование BM25. Работает быстрее, но может привести к ухудшению результата при запросе, содержащем более одного слова.
  • SPH_RANK_NONE, отключает режим ранжирования и задает для всех документов, в которых найдено совпадение, вес, равный единице. Самый быстрый метод.
  • SPH_RANK_WORDCOUNT, ранжирует по числу вхождений ключевых слов.
  • SPH_RANK_PROXIMITY, вычисляет насколько результат близок к поисковой фразе.

Помимо таблиц, Sphinx может индексировать данные из особым образом оформленного XML-файла. Такой подход позволяет объединять в одном месте информацию из разных источников, и искать уже не в конкретном разделе, а по всему сайту. А вкупе с большим количеством параметров индексирования и поиска (не рассмотренных в этой статье) ваш интернет-ресурс будет снабжен поисковой системой мощнее, чем в состоянии предло жить Яндекс или Google. LXF

SphinxClient: другие методы

Помимо рассмотренных нами возможностей, класс SphinxClient предлагает и другие методы, как то:

  • SetRetries – устанавливает число попыток получения результатов от сервера и задержку между попытками в миллисекундах. Сам API не выполняет повторы, он только указывает об этом searchd. Несколько попыток для получения результатов может использоваться, если произошел сбой в соединении или сервер слишком занят обработкой других запросов.

Применительно к нашему коду, пример мо жет выглядеть так:

$cl->SetRetries(3,500);

Мы выполняем три попытки с задержкой в 500 мс.

  • SetLimits – устанавливает смещение и число выводимых документов. С помощью этого метода можно организовывать постраничный просмотр результатов поиска. SetLimits() принимает четыре параметра, обязательными из которых являются только первые два, идентичные аргументам конструкции LIMIT в MySQL$offset задает смещение, а $limit – число документов. Дополнительными являются:
  1. $max_matches – определяет, сколько документов searchd будет держать в памяти во время поиска. Хранение документов в оперативной памяти позволяет ускорить поиск, но слишком большее значение может оставить другие процессы без ресурсов.
  2. $cutoff – предназначен для контроля производительности. Параметр указывает searchd, после скольких найденных документов поиск следует прекратить.
$cl->SetLimits(0,20);

возвращает первые 20 совпадений.

Личные инструменты
  • Купить электронную версию
  • Подписаться на бумажную версию