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);
	}

Удачного поиска!

Оставить комментарий

© 2010 - 2024 Ядоблог. Ничего не защищено.
Powered by Лаборатория Яда. Написать Яду