sphinx, xmlpipe2, PHP и MongoDB или загибаем трубу из MongoDB в сфинкс, используя PHP
22 мая, 2012
ТЗ: организовать релевантный поиск.
ТУ: данные хранятся в MongoDB, бэкенд на PHP.
Можно искать через аля ‘%like%’, можно через fulltext индекс, но там проблемка с морфологией =)
Для поиска, я использую Sphinx. Отличнейший поисковый движок, работающий быстро и выдающий качественный результат с учётом морфологии русского языка!
Обычно сфинкс натравливаем на реляционную субд, создаём индекс, в котором указываем SQL запрос для получения данных. Это стандартный способ, в интернетах масса описаний.
Проблема: сфинкс не умеет хавать MongoDB, т.к. требует числовой ID. В MongoDB у документов ID не числовой, да и документ не плоская структура, а JSON деревцо.
Решение проблемы:
Читаем про xmlpipe2, понимаем как собрать XML (реализуем на PHP формирователь этой ленты).
Пример:
<?php public function showXmlPipe2_for_spxinx() { $data=array(); $coupons=$this->MCoupon->getAll_for_sphinx(); echo '<?xml version="1.0" encoding="utf-8"?>'; echo ' <sphinx:docset> <sphinx:schema> <sphinx:field name="title"/> <sphinx:field name="description"/> <sphinx:attr name="location" type="int"/> <sphinx:field name="supplier_name"/> </sphinx:schema> '; foreach ($coupons as $coupon) { $id=$coupon['sphinx_id']; // if ($id>10) // { // break; // } $title=''; $description=''; $location=''; $supplier_name=''; // if ($id<10) {var_dump($coupon);} if (isset($coupon['item']['title'])) {$title=$coupon['item']['title'];} if (isset($coupon['item']['description'])) { if (is_array($coupon['item']['description'])) { $description=''; foreach ($coupon['item']['description'] as $line) { $description.=$line.". "; } } } if (isset($coupon['item']['location'])) {$location=crc32(strtolower($coupon['item']['location']));} if (isset($coupon['item']['supplier']['name'])) {$supplier_name=$coupon['item']['supplier']['name'];} echo ' <sphinx:document id="'.$id.'"> <title><![CDATA[['.$title.']]></title> <description><![CDATA[['.$description.']]></description> <location>'.$location.'</location> <supplier_name><![CDATA[['.$supplier_name.']]></supplier_name> </sphinx:document> '; } echo ' </sphinx:docset>'; } |
Первый момент: придётся таки создать числовое уникальное поле в документе MongoDB. Ходящие в интернетах примеры с передачей MongoDB _id в качестве атрибута не работают, как нам нужно(они не вернутся в результатах запроса).
Я делал sphinx_id в каждом MongoDB документе.
Второй момент: придётся атрибуты(название города в нашем случае) передавать числом. Я использовал crc32(strtolower($coupon[‘item’][‘location’])) =)
Затем создаём в sphinx индекс, описывающий получаемую структуру данных:
source kupon { type = xmlpipe # shell command to invoke xmlpipe stream producer # mandatory # xmlpipe_command = /usr/bin/wget -O - -q -t 1 http://_секретный_урл_xmlpipe2_трубы_/get_xml_pipe2_to_sphinx # xmlpipe2 field declaration # multi-value, optional, default is empty # xmlpipe_field = title xmlpipe_field = description # xmlpipe_field = location xmlpipe_field = supplier_name # xmlpipe2 attribute declaration # multi-value, optional, default is empty # all xmlpipe_attr_XXX options are fully similar to sql_attr_XXX # # xmlpipe_attr_timestamp = published xmlpipe_attr_uint = location # perform UTF-8 validation, and filter out incorrect codes # avoids XML parser choking on non-UTF-8 documents # optional, default is 0 # # xmlpipe_fixup_utf8 = 1 } index kupon { # Использовать соответствующий source-блок настроек при индексации source = kupon # Путь до файлов индекса path = /var/lib/sphinxsearch/data/kupon # Способ хранения индекса (none, inline, extern) # Подробнее http://www.sphinxsearch.com/docs/manual-0.9.8.html#conf-docinfo docinfo = extern # Memory lock (http://www.sphinxsearch.com/docs/manual-0.9.8.html#conf-mlock) mlock = 0 # Использование английского и русского стемминга morphology = stem_ru,stem_en # Минимальная длина индексируемого слова min_word_len = 2 # Установка используемой кодировки charset_type = utf-8 # Таблица символов (http://www.sphinxsearch.com/docs/manual-0.9.8.html#conf-charset-table) charset_table = 0..9, A..Z->a..z, _, a..z, U+410..U+42F->U+430..U+44F, U+430..U+44F # Минимальная длина инфикса (префикс в том числе) min_infix_len = 2 # Использовать оператор усечения "*" (http://www.sphinxsearch.com/docs/manual-0.9.8.html#conf-enable-star) enable_star = 3 } |
Почти всё, пробуем в консоле:
/usr/bin/indexer --config /etc/sphinxsearch/sphinx.conf --rotate kupon |
если всё ок, начал качать и строить индекс, прописываем в крон:
#sphinx indexer 0 */1 * * * /usr/bin/indexer --config /etc/sphinxsearch/sphinx.conf --rotate kupon |
За результатами обращаться в сфинкс как обычно.
public function search($text) { $this->load->library('sphinxclient'); $query=$text; $this->sphinxclient->SetMatchMode(SPH_MATCH_ANY); $this->sphinxclient->SetSortMode(SPH_SORT_RELEVANCE); $result=$this->sphinxclient->Query($query); return ($result); } |
Удачного поиска!