Главная > Программы Sphinx для НЕ полнотекстового поиска![]() Sphinx – один из самых популярных движков полнотекстового поиска, в данной статье я расскажу как
можно его использовать не по назначению… ![]() Пример такой сущности:
Теперь о самом поиске, точнее, о там как его видит заказчик – поиск должен искать по точному вхождению кейвордов, за каждое такое вхождение должен документу начисляться один бал. Остальные связи используем для фильтра – т.е. ищем документы на англиском и русском – должно найти все документы, где есть хотя бы один из этих языков. Sphinx – установка и настройкаУстановка – тут все достаточно просто – скачиваем архив с сорцами, затем собираем: # ./configure make make install Потом настраиваем, приведу пример своего sphinx.conf: ## источник данных - существующая база данных
source src1
{
# база данных - PostgreSQL
type = pgsql
# настройки соединения
sql_host = localhost
sql_user = root
sql_pass = god-sex-love-secret
sql_db = project
sql_port = 5432 # default is 3306
# основной запрос по которому будем индекс строить
sql_query = \
SELECT id, title, adult, status, blacklist, visits, rank, votes, stars, date_part('epoch', dateupdate) AS updated \
FROM documents \
WHERE (status = 1 OR status = 7)
# определяем числовые атрибуты
sql_attr_uint = status
sql_attr_uint = visits
sql_attr_uint = rank
sql_attr_uint = votes
# булеан
sql_attr_bool = adult
sql_attr_bool = blacklist
# дата и время в UNIX timestamp
sql_attr_timestamp = updated
# с плавающей запятой
sql_attr_float = stars
# multi-valued attribute (MVA) - для обеспечения связи многое ко многим
sql_attr_multi = uint keyword from query; SELECT document_id, keyword_id FROM document_keyword
sql_attr_multi = uint language from query; SELECT document_id, language_id FROM document_language
sql_attr_multi = uint country from query; SELECT document_id, country_id FROM document_country
sql_attr_multi = uint city from query; SELECT document_id, city_id FROM document_city
sql_attr_multi = uint type from query; SELECT document_id, type_id FROM document_type
# для отладки из консоли
sql_query_info = SELECT * FROM document WHERE document_id=$id
}
## определения индекса
index project
{
# берем источник описанный выше
source = src1
# путь к индексам
path = /usr/local/sphinx/var/data/project
# тип хранилища
docinfo = extern
# memory locking for cached data (.spa and .spi), to prevent swapping
mlock = 0
# нам необходимо точное соответствие - морфологию игнорируем
morphology = none
# индексируем слова даже из одной буквы
min_word_len = 1
# кодировочка
charset_type = utf-8
# и еще раз
charset_table = 0..9, A..Z->a..z, _, a..z, U+410..U+42F->U+430..U+44F, U+430..U+44F
}
## настройки индексатора
indexer
{
mem_limit = 32M
}
## настройки демона
searchd
{
listen = 127.0.0.1
listen = 3312
read_timeout = 5
client_timeout = 300
max_children = 0
pid_file = /usr/local/sphinx/var/log/searchd.pid
max_matches = 1000
}
Стартуем индексацию для всех прописанных индексов: # /usr/local/sphinx/bin/indexer --allесли демон уже запущен # /usr/local/sphinx/bin/indexer --all --rotate Поднимаем демона: # /usr/local/sphinx/bin/searchd Пробуем поиск из консоли: # /usr/local/sphinx/bin/search keyword #Sphinx 0.9.9-release (r2117) #Copyright (c) 2001-2009, Andrew Aksyonoff using config file '/usr/local/sphinx/etc/sphinx.conf'... index 'project': query 'keyword ': returned 1000 matches of 2277 total in 0.014 sec displaying matches: 1. document=5761, weight=2, adult=0, status=1, blacklist=0, visits=0, rank=0, votes=0, stars=0.000000, updated=Sun Sep 20 20:22:07 2009, keyword=(318,323,611), language=(1), country=(), city=(), aim=(), type_first=() 2. document=351943, weight=2, adult=0, status=1, blacklist=0, visits=0, rank=0, votes=0, stars=0.000000, updated=Sun Sep 20 20:22:07 2009, keyword=(10480,10490), language=(1), country=(), city=(), aim=(), type_first=() 3. document=351956, weight=2, adult=0, status=1, blacklist=0, visits=0, rank=0, votes=0, stars=0.000000, updated=Sun Sep 20 20:22:07 2009, keyword=(10480,10490), language=(1), country=(), city=(), aim=(), type_first=() ... words: 1. 'keyword': 2277 documents, 2614 hits Связка PHP+SphinxА теперь, непосредственно поиск с использованием sphinxapi.php (читайте комментарии к коду): // подключаем сфинкс
require ( "library/sphinxapi.php" );
// опущу получение данных из запроса
$sortby = "relev";
// $keywords - это массив id - т.е. до этого у нас должен быть запрос к нашей БД, который вытащит их по поисковой строке
// т.е. было "Вася, письмо, Федя" стало array(152, 345, 6342)
$keywords = array();
// тоже массивы id'шников
$languages = array();
$countries = array();
$cities = array();
$type = array();
// создаем инстанц клиента
$cl = new SphinxClient ();
// настройки
$cl->SetServer("localhost", 3312);
$cl->SetConnectTimeout(1);
$cl->SetLimits($offset, $limit); // постраничную навигацию организовываем тут
$cl->SetArrayResult (true);
$cl->SetRankingMode(SPH_RANK_PROXIMITY_BM25);
$cl->SetMatchMode(SPH_MATCH_EXTENDED);
// пишем select
// sphinx может сказать есть ли совпадение между двумя множествами, но не может сказать сколько их - приходится извращаться
// результирующий select будет иметь следующий вид:
// *, (IN(keyword, "152") + IN(keyword, "345") + IN(keyword, "6342") + ... ) AS relev
$select = "*, (IN(keyword,'.join(') + IN(keyword,',$keywords).')) AS relev";
$cl->SetSelect($select);
// накладываем фильтры
if (!empty($languages))
$cl->SetFilter('language', $languages );
if (!empty($countries))
$cl->SetFilter('country', $countries );
if (!empty($cities))
$cl->SetFilter('city', $cities );
if (!empty($type))
$cl->SetFilter('type_first', $type );
// применяем сортировку $sortby
switch ($sortby) {
case 'relev':
$cl->SetSortMode(SPH_SORT_EXTENDED, 'relev DESC');
break;
case 'visits':
$cl->SetSortMode(SPH_SORT_EXTENDED, 'visits DESC, relev DESC');
break;
case 'stars':
$cl->SetSortMode(SPH_SORT_EXTENDED, 'stars DESC, relev DESC, updated DESC');
break;
default:
break;
}
// накладываем фильтр на поле обновления - нас интересуют записи за последний год
$cl->setFilterRange('updated', (time() - 60*60*24*365), time());
// теперь запрос к демону
$res = $cl->Query("", "project");
// вывод результатов
if ( $res === false ) {
echo "Query failed: " . $cl->GetLastError() . ".\n";
} else {
if ( $cl->GetLastWarning() ) {
echo "WARNING: " . $cl->GetLastWarning() . "\n";
}
echo "total found: <strong>{$res['total_found']}</strong> at <strong>{$res['time']} sec</strong><hr/>";
if ( ! empty($res["matches"]) ) {
// это вывод того, что нашел Sphinx
// такая подробная информация будет возвращаться только, если установлен параметр $cl->SetArrayResult (true);
foreach ( $res["matches"] as $doc => $docinfo ) {
if (!isset($docinfo['attrs']['relev'])) $docinfo['attrs']['relev'] = 0;
echo "id: {$docinfo['id']}<br/>";
echo "weight: {$docinfo['weight']}<br/>";
echo "relevance: {$docinfo['attrs']['relev']}<br/>";
echo "votes: {$docinfo['attrs']['votes']}<br/>";
echo "stars: {$docinfo['attrs']['stars']}<br/>";
echo "rank: {$docinfo['attrs']['rank']}<br/>";
echo "visits: {$docinfo['attrs']['visits']}<br/>";
echo "keywords: ".join(',', $docinfo['attrs']['keyword'] )."<br/>";
echo "languages: ".join(',', $docinfo['attrs']['language'] )."<br/>";
echo "countries: ".join(',', $docinfo['attrs']['country'] )."<br/>";
echo "cities: ".join(',', $docinfo['attrs']['city'] )."<br/>";
echo "types: ".join(',', $docinfo['attrs']['type'] )."<br/>";
echo "updated: ".date("Y-m-d H:i",$docinfo['attrs']['updated'])."<br/>";
echo "<hr/>";
}
// это был вывод того, что нам вернул Sphinx, для вывода необходимой информации надо постучаться к нашей БД
// нам же надо взять ID всех документов
// для правильной работы следующего кода отключите параметр $cl->SetArrayResult (true);
$ids = array_keys($result['matches']);
// и выведем в порядке, которые нам нашептал Sphinx
// данный пример подходит для MySQL в PostgreSQL для эмуляции конструкции ORDER BY FIELD используют ORDER BY CASE
$id_list = implode(',', $ids);
$sql = sprintf('SELECT * FROM `documents` WHERE `id` IN (%s) ORDER BY FIELD(`id`, %s)', $id_list, $id_list);
}
}
Таки полнотекстовыйДальше немного о грустном, когда количество документов выросло до 4-х млн, поиск стал занимать недопустимые 4 и более секунды, пришлось пойти на небольшую хитрость – для вычисление совпадений таки использовать полнотекстовый поиск, и не заморачиваться с вычислением точных совпадений… Изменения в конфиге: source src1
{
# добавлено поле keywords - сие есть заранее подготовленное поле, обновляемое по тригеру
# содержит (array_to_string(array_agg(k.value), ', ') AS keywords
sql_query = \
SELECT id, title, keywords, adult, status, blacklist, visits, rank, votes, stars, date_part('epoch', dateupdate) AS updated \
FROM documents \
WHERE (status = 1 OR status = 7)
}
Изменения в PHP части: // дабы много не переписывать из предыдущего примера, был добавлен алиас
$select = "*, @weight AS relev";
// запрос претерпел изменения
// теперь он имеет вид @keywords ("Вася"|"письмо"|"Федя")
$query = '("'.join('"|"',$keywords).'")';
$query = '@keywords '.$query;
// теперь запрос к демону
$res = $cl->Query($query, "project");
Материал взят с сайта: Sphinx. Установка и первичная настройкаИтак, начинаю серию топиков по прикручиванию поисковой машины Sphinx к нашему любимому движку. Что есть Sphinx и с чем его едятСфинкс является системой полнотекствого поиска, распространяемой под лицензией GPL второй версии. УстановкаТопаем по НастройкаДля начала стоит сказать, что конфигурация поисковой
машины Сфинкс оперирует двумя фундаментальными параметрами: Настройка автозапуска поискового демона при старте системыДля осуществления того, что написано в заголовке раздела, необходимо в
один из стартовых скриптов системы, например, /etc/rc.local для ОС Linux вставить код: # Запускаем демона Сфинкс
/usr/local/sphinx/bin/searchd --config /usr/local/sphinx/etc/sphinx.conf
Настройка периодической индексации базы данныхСамый простой способ запустить индексацию — это выполнить или добавить в планировщик команду: /usr/local/sphinx/bin/indexer --all ,
однако данный способ является сильно неоптимальным, поскольку топики
обновляются (точнее появляются новые) значительно реже, нежели
комментарии, поэтому частоту обновления индекса комментариев должны быть больше.Для различной частоты индексации объектов разного типа, в планировщик необходимо добавить следующие инстукции: 12 */ 3 * * * /usr/local/sphinx/bin/indexer --rotate topicsIndex > /dev/null 2>&1
*/50 * * * * /usr/local/sphinx/bin/indexer --rotate commentsIndex > /dev/null 2>&1
(означает индексацию топиков каждые 3 часа с запуском процесса на 12-ой минуте часа и индексацию комментариев
каждые 50 минут) Пример конфигурационного файла## Конфигурационный файл Sphinx-а для индексации Живой улицы
#######################
#
# Описываем индексы
#
#######################
# Источник-родитель для всех остальных источников. Здесь указываются параметры доступа
# к базе данных сайта
source lsParentSource
{
type = mysql
sql_host = your_database_host
sql_user = your_database_login
sql_pass = your_database_password
sql_db = your_database_name
sql_port = 3306
# Для ускорения работы прописываем путь до MySQL-го UNIX-сокета (чтобы
# операции с БД происходили не через TCP/IP стек сервера)
sql_sock = /var/run/mysqld/mysqld.sock
mysql_connect_flags = 32 # 32- включение сжатие при обмене данными с БД
# Включам нужную кодировку соединения и выключаем кеш запросов
sql_query_pre = SET NAMES utf8
sql_query_pre = SET SESSION query_cache_type =OFF
}
# Источник топиков
source topicsSource : lsParentSource
{
# запрос на получения данных топиков
sql_query = \
SELECT t_fast.topic_id, t_fast.topic_title, UNIX_TIMESTAMP(t_fast.topic_date_add) as topic_date_add, \
tc.topic_text, t_fast.topic_publish \
FROM prefix_topic as t_fast, prefix_topic_content AS tc \
WHERE t_fast.topic_id=tc.topic_id AND t_fast.topic_id>=$start AND t_fast.topic_id<=$end
# запрос для дробления получения топиков на неколько итераций
sql_query_range = SELECT MIN(topic_id),MAX(topic_id) FROM prefix_topic
# сколько получать объектов за итерацию
sql_range_step = 1000
# Указываем булевый атрибут критерия "топик опубликован". Для возможности указания этого критерия при поиске
sql_attr_bool = topic_publish
# Атрибут даты добавления, типа "время"
sql_attr_timestamp = topic_date_add
# мульти-аттрибут "теги топика"
sql_attr_multi = uint tag from query; SELECT topic_id, topic_tag_id FROM prefix_topic_tag
sql_ranged_throttle = 0
}
# Источник комментариев
source commentsSource : lsParentSource
{
sql_query = \
SELECT comment_id, comment_text, UNIX_TIMESTAMP(comment_date) as comment_date, comment_delete \
FROM prefix_topic_comment \
WHERE comment_id>=$start AND comment_id<=$end
sql_query_range = SELECT MIN(comment_id),MAX(comment_id) FROM prefix_topic_comment
sql_range_step = 5000
sql_attr_bool = comment_delete
sql_attr_timestamp = comment_date
}
#######################
#
# Описываем индексы
#
#######################
index topicsIndex
{
# Источник, который будет хранить данный индекса
source = topicsSource
path = ПУТЬ/ДО/КАТАЛОГА/ИНДЕСА
# Тип хранения аттрибутов
docinfo = extern
mlock = 0
# Используемые морфологические движки
morphology = stem_enru, soundex, metaphone
# Кодировака данных из источника
charset_type = utf-8
# Из данных источника HTML-код нужно вырезать
html_strip = 1
}
# Индекс комментариев
index commentsIndex
{
source = commentsSource
path = ПУТЬ/ДО/КАТАЛОГА/ИНДЕСА
docinfo = extern
mlock = 0
morphology = stem_enru, soundex, metaphone
charset_type = utf-8
}
#######################
#
# Настройки индексатора
#
#######################
indexer
{
# Лимит памяти, который может использавать демон-индексатор
mem_limit = 32M
}
#######################
#
# Настройка демона-поисковика
#
#######################
searchd
{
# Адрес, на котором будет прослушиваться порт
address = 127.0.0.1
# Ну и собственно номер порта демона searchd
port = 3312
# Лог-файл демона
log = /var/log/sphinx/searchd.log
# Лог поисковых запросов. Если закомментировать,то логировать поисковые строки не будет
query_log = /var/log/sphinx/query.log
# Время в секундах, которое ждет демон при обмене данными с клиентом. По исчерпании происходит разрыв коннекта
read_timeout = 5
# Максимальное количество одновременно-обрабатываемых запросов. 0 означает дофига, а точнее без ограничения
max_children = 30
# Файл, в который сохраняется PID-процесса при запуске
pid_file = /var/log/sphinx/searchd.pid
}
Ссылки по теме:
Главная > Программы |