Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save KarelWintersky/42d379a20dd837a79e095bfb64a18025 to your computer and use it in GitHub Desktop.
Save KarelWintersky/42d379a20dd837a79e095bfb64a18025 to your computer and use it in GitHub Desktop.
Как установить и настроить сфинкс.

Как установить и настроить сфинкс.

Скачиваем сфинкс (берем версию с MySQL и со стеммингом на 15 языков Win32 binaries w/MySQL+PgSQL+libstemmer+id64 support соответствующую битности твоей ОС), распаковываем например в d:\temp\s\

На этом установка sphinx завершена. В дебиане просто делаем sudo apt-get install sphinxsearch.

Создаем таблицы:

CREATE TABLE news 
    (id INT(10) AUTO_INCREMENT PRIMARY KEY, topic INT(10) NOT NULL, header VARCHAR(200) NOT NULL, 
    body TEXT, added TIMESTAMP DEFAULT CURRENT_TIMESTAMP);

Вбиваем данные:

INSERT INTO news (topic, header, body) VALUES
(1, 'Биржевой курс евро приблизился к 46 рублям', 
'Курс евро во второй половине торгов на Московской бирже 21 января приблизился к 46 рублям. К 17:00 по московскому времени европейская валюта подорожала почти на 18 копеек до 45,9795 рубля. Курс доллара к тому же времени вырос на 21 копейку до 33,98 рубля.'),
(1, 'Найденный в ЮАР алмаз оценили в 15 миллионов долларов',
 'В Южной Африке нашли голубой алмаз, который предварительно оценен в 15-20 миллионов долларов. За эту сумму, по мнению экспертов, камень может быть продан на аукционе, сообщает Reuters.'),
(2, 'В Петербурге исчез музей-квартира Ленина', 
'Партия «Коммунисты России» потребовала, чтобы власти Санкт-Петербурга восстановили музей, действовавший в последней конспиративной квартире Владимира Ленина. Об этом 21 января сообщается на сайте партии.'),
(1, 'Hello world', 'Test news for search');

Пишем конфиг для индексера например в d:\temp\s\sphinx.conf на основе sphinx-min.conf.in, мануала и интуиции (файл sphinx.conf, приложен ниже).

В конфиге упомняуты папки log и data, так что созадем их в d:\temp\s\

Аттрибуты (added и topic) — это числа, которые можно сохранить в поисковый индекс и потом использовать их в условии поиска (для поиска в определенной теме или сортировки по дате).

Уф. Тяжело потрудились. Попробуем теперь запустить индексер, открываем консоль (гайд по использованию консоли https://gist.github.com/codedokode/586dabb540415e0cc3d3 ), переходим в папку сфинкса и печатаем:

.\bin\indexer --config sphinx.conf --all

(под линуксом печатаем просто indexer так как там он прописан в системную папку). Если все ОК, выведется текст, и там мы увидим:

indexing index 'index_news'...

WARNING: collect_hits: mem_limit=20480 kb too low, increasing to 25856 kb

Мало памяти, надо минимум 25 Мб (и зачем ему столько?)

total 4 docs, 1418 bytes

total 0.030 sec, 45854 bytes/sec, 129.34 docs/sec

4 документа из базы проиндексировались

Ок, индекс создан в папочке data, проверим, работает ли поиск? Набираем

.\bin\search --config sphinx.conf "курс"

Вот так печалька, ничего не найдено. Еще бы, виндовая консоль не умеет в utf-8 и коверкает наши буковки. Попробуем английский:

.\bin\search --config sphinx.conf "hello"

Сфинкс вернул нам id новости, отлично. Теперь запустим поисковый демон и перейдем к PHP (и к поддержке utf-8). Запускаем демон:

.\bin\searchd --config sphinx.conf --console

(--console чтобы он не пытался уйти в фоновый режим и его можно было остановить через Ctrl + C). Появятся надписи что все хорошо. Из PHP к сфинксу можно подсоединиться 2 способами:

  • через mysql-совместимый протокол подсоединиться к демону как к БД и искать с помощью SQL-запросов
  • использовать sphinx.api.php и бинарный протокол

Начнем с варианта 1. Пишем test-sql.php (код в приложении).

Запускаем его, из консоли или через браузер и сервер (демон поиска должен быть запущен естественно). Если все верно, в ответ вернется массив вроде такого, с id документа и его аттрибутами:

array(1) {
  [0] =>
  array(6) {
    'id' =>
    string(1) "1"
    [0] =>
    string(1) "1"
    'topic' =>
    string(1) "1"
    [1] =>
    string(1) "1"
    'added' =>
    string(10) "1390318498"
    [2] =>
    string(10) "1390318498"
  }
}

Перейдем к sphinx.api.php (мануал по апи: http://sphinxsearch.com/docs/manual-2.2.1.html#api-reference ). Берем пример кода из папки api и пишем на его основе код api-test.php (приложен ниже).

Запускаем, тоже видим массив с результатами. Хорошо, когда все работает.

Стемминг

Если мы сейчас попробуем поискать слово с учетом склонения, например «курсом» то ничего не найдем, так как в тексте нет таких слов. Нехорошо. Давай исправлять это при помощи встроенных возможностей сфинкса.

В сфинксе есть такие возможности преобразования слов при индексировании: lemmatizer (приводит слово в нормальную форму: running -> run), stemmer (отрезает окончания слов, не особо заботясь о логике, business -> busi но зато простой и не требует словаря), фонетические алгоритмы (soundex, metaphone заменяют похожие звуки в слове для поиска с неправильным написанием слова но только в английских словах). Мануал: http://sphinxsearch.com/docs/manual-2.2.1.html#conf-morphology

Подключим русский и англ. стеммер, прописав в конфиге в секции index_news:

morphology = stem_ru, stem_en

Индекс сам себя не обновит, так что останавливаем демон (Ctrl + C), запускаем переиндексацию и перезапускаем демон (когда демон работает в фоновом режиме, можно дописать ключ --rotate и индексер сам попросит демона перезагрузить индексы, при этом работа поиска не прервется):

.\bin\indexer --config sphinx.conf --all

Теперь попробуем поискать по слову «курса» через PHP-скрипт. Все должно работать.

Лемматайзер конечно работает качественнее, так что лучше скачать нужные словари и подключить его.

Другие возможности

Сфинкс умеет еще вырезать HTML-теги и декодировать HTML-сущности (параметр html_strip), задавать диапазон символов (charset_table), исключения и синонимы (wordforms и exceptions).

Можно задать, какие символы являются и не являются частью слова (например чтобы слова разделенные подчеркиванием считались как разные).

Также, сфинкс может строить сниппеты в результатах поиска (вырезать куски текста рядом с подсвеченным ключевым словом), с помощью функции BuildExcerpts() в API.

Autocomplete

Sphinx можно использовать для реализации автокомплита в поиске. т.е. дополнения по началу слова а также исправления опечаток (мануал http://sphinxsearch.com/blog/2013/05/21/simple-autocomplete-and-correction-suggestion/ ).

Realtime-индексы

Ок, у нас есть индекс для поиска. Но как обновлять его при добавлении новых данных или удалении старых? Ну с удалением просто, если sphinx вернул нам id несуществующей в таблице записи — значит, она удалена. А как добавлять данные в индекс?

Для этого используются realtime-индексы. Rt индексы можно обновлять с задержкой в несколько миллисекунд (по крайней мере так говорят авторы), но они не очень эффективно хранят данные и много данных в них лучше не помещать. То, есть мы делаем так:

  • основной индекс, содержит все записи (или все записи старше определенной даты), полностью перестраивается раз в час/в сутки в заивисимости от объема данных
  • RT-индекс, который хранит только новые данные, которых нет в основном индексе, который обновляется в реальном времени и который очищается при переиндексации

Добавим RT-индекс в конфиг:

index rt_news 
{
    type = rt
    path = d:/temp/s/data/rt_news

    # Надо описывать все возможные поля для индексирования
    rt_field = header
    rt_field = body

    rt_attr_uint = added
    rt_attr_uint = topic

    # Сколько памяти отведем под индекс (что не поместится, будет храниться на диске)
    rt_mem_limit = 25M
}

Ок, теперь перезапускаем индексер и демон. Добавление в Rt-индекс делается SQL-запросами, так что возьмем sql-test.php и чуть-чуть переделаем:

$st = $pdo->prepare("INSERT INTO rt_news (id, topic, header, body, added) VALUES (?, ?, ?, ?, ?)");
$st->execute(array(100, 1, 'Проверим realtime index', 'Это текст проверки rt индекса', time()));

Запустим наш файл. Теперь поиском убедимся, что поиск по слову «проверим» и «realtime» выдает нам результаты. Если ты ищешь через SQL, а не через api, не забудь указать в запросе оба индекса (SELECT ( FROM index_news, rt_news ... )).

Индекс можно обновить через вставку новой записи с таким же id помощью REPLACE, проверим:

$st = $pdo->prepare("REPLACE INTO rt_news (id, topic, header, body, added) VALUES (?, ?, ?, ?, ?)");
$st->execute(array(100, 1, 'Черный кот в черном доме', 'здесь ничего нет', time()));

Убедимся, что теперь ищется слово кот, а по слову realtime резальтотов нет.

Ну и можно еще удалять записи из RT-индекса, с помощью DELETE FROM rt_news WHERE id IN(1, 2, 3)

Поисковые запросы

Sphinx понимает хитрый синтаксис в поисковых запросах ( http://sphinxsearch.com/docs/manual-2.2.1.html#searching ):

  • оператор ИЛИ: слово1 | слово2
  • оператор НЕ: слово1 -слово2
  • скобки для группировки
  • и мнгого других опций, которые есть в мануале

Можно сортировать и группировать результаты по разным аттрибутам.

Доплнительное чтение

Вот неплохая статья на русском про то, как sphinx обрабатывает слова в тексте и некоторые настройки для этого: http://chakrygin.ru/2013/07/sphinx-search.html

<?php
error_reporting(-1);
require __DIR__.'/api/sphinxapi.php';
$client = new SphinxClient();
// данные берем из конфига
$client->SetServer('localhost', 9312);
$client->SetConnectTimeout(1);
$client->SetArrayResult(true);
$result = $client->Query("черный");
var_dump($result);
# Источник данных — MySQL
# В конфиге источники могут наследоваться. создадим базовый конфиг для любых MySQL-источников
# чтобы не копипастить данные для подключения
source base
{
type = mysql
sql_host = localhost
sql_user = tester
sql_pass = password
sql_db = tester
sql_port = 3306
# Ставим кодировку при соединении
sql_query_pre = SET NAMES utf8
}
# Теперь настроим источник данных для выборки новостей наследующийся от базового
source src_news: base
{
# Включаем range (выборку больших таблиц по частям)
sql_range_step = 1000
# запрос на выборку диапазона id
sql_query_range = SELECT MIN(id), MAX(id) FROM news
# запрос на выборку самих новостей для индексации
# сфинкс понимает даты только в виде числа, так что преобразуем дату в timestamp
sql_query = \
SELECT id, topic, header, body, UNIX_TIMESTAMP(added) AS added \
FROM news WHERE id BETWEEN $start AND $end
# Сохраняем для каждой новости ее topic и дату в аттрибуты
sql_attr_uint = topic
sql_attr_timestamp = added
}
# Теперь создаем индекс из данных взятых из источника
index index_news
{
source = src_news
# где хранить данные
# не знаю, как писать относительный путь, потому пишу абсолютный
path = d:/temp/s/data/news
# где хранить аттрибуты — в индексе (inline) или отдельном файле (extern)
docinfo = extern
# Либо sbcs (1-байтовая кодировка) либо utf-8
charset_type = utf-8
}
# Говорим сколько памяти можно использовать при индексации (если недодать то будет ошибка)
# объем памяти зависит от размера таблицы и опредеояется опытным путем
indexer
{
mem_limit = 20M
}
# настройки поискового демона
searchd
{
# на каких портах слушать с бинарным проткоолом
listen = 9312
# и с mysql-протоколом
listen = 9306:mysql41
# Куда класть логи
log = d:/temp/s/log/searchd.log
query_log = d:/temp/s/log/query.log
read_timeout = 5
max_children = 30
pid_file = d:/temp/s/log/searchd.pid
max_matches = 1000
seamless_rotate = 1
preopen_indexes = 1
unlink_old = 1
workers = threads # for RT to work
binlog_path = d:/temp/s/data
}
<?php
error_reporting(-1);
// номер порта берется из конфига сфикса а не наугад
// Если вместо 127.0.0.1 написать localhost, то под линуксом PDO может приконнектиться к MySQL
// вместо сфинкса через юникс-сокет, игнорируя указанный номер порта (MySQL использует
// порт 3306 в отличие от сфинкса)
// Мануал: http://php.net/manual/ru/ref.pdo-mysql.connection.php#refsect1-ref.pdo-mysql.connection-notes
$pdo = new PDO('mysql:host=127.0.0.1;port=9306');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Ищем слово «курс» в индексе новостей
// мануал: http://sphinxsearch.com/docs/manual-2.2.1.html#sphinxql-select
$stmt = $pdo->query("SELECT * FROM index_news WHERE MATCH('курс')");
$results = $stmt->fetchAll();
var_dump($results);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment