похожие темы через sphinx

Дело, было как обычно вечером :) И лежало на мне уже давно задание — добавить «похожие темы» к проекту, но ввиду приоритетов откладывалось оно на потом постоянно. Полазил по LS, нашел уже готовый бесплатный модуль: livestreet.ru/blog/2053.html
Но незадача :( нужен fulltext для InnoDB… уже хотел похерить задание и отложить опять до лучших времен, но что-то дернуло меня все-таки покопаться чуток. А именно, вспомнил я про уже готовый fulltext поиск реализованный на sphinx'e в дефолтной сборке LS. Там как раз есть тайтл + текст, так что впринципе больше и не надо :)
В общем, пришлось чуток модифицировать имеющийся модуль + добавить ф-цию в Sphinx.class.php
К действиям:
1) берем имеющийся модуль
2) удаляем папку и ее содержимое /classes/modules/similartopics/mapper
3) заменяем полностью содержимое файла(/classes/modules/similartopics/SimilarTopics.class.php) на:
<?php
##### [hack] similar_topics_sphinx #####
class LsSimilarTopics extends Module {
	public function Init() {
	}
	public function GetSimilarTopics($oTopic){
	    $sTitleTags=$oTopic->getTags();
	    $aTopics=array();
	    $data=$this->Sphinx_FindSimilarTopics($sTitleTags,SIMILARTOPICS_COUNT+1);
	    if(is_array($data['matches']) && sizeof($data['matches'])>0){
		foreach($data['matches'] as $k=>$v) if($k!=$oTopic->getId()) $aTopics[]=$this->Topic_GetTopicById($k);
		return $aTopics;
	    }else return false;
	}
}
##### [hack] similar_topics_sphinx #####
?>

4) добавляем в класс(classes/modules/sphinx/Sphinx.class.php):
        ##### [hack] SimilarTopics #####
        public function FindSimilarTopics($sTerms,$iLimit){
                $cacheKey=SEARCH_ENTITY_PREFIX."similartopics_{$sTerms}_{$iLimit}";
                if(false===($data=$this->Cache_Get($cacheKey))){
                        $this->oSphinx->SetMatchMode(SPH_MATCH_ANY);
                        $this->oSphinx->SetLimits(0,$iLimit);

                        if(!is_array($data=$this->oSphinx->Query($sTerms,SEARCH_ENTITY_PREFIX.'topicsIndex'))) return FALSE;

                        # no results = no cache
                        if($data['total']>0) $this->Cache_Set($data,$cacheKey,array(),60*15);
                }
                return $data;
        }
        ##### [hack] SimilarTopics #####

лично я оставил поиск по тэгам, хотя — можете добавить и по тайтлу, как было первоначально в плагине
5) все, радуемся жизни :)

п.с. должен быть настроен уже sphinx и работать поиск через sphinx
п.п.с. делаем на свой страх и риск, так что не забываем о том что надо бэкапить файлы ;)
Еще со времен СССР у российских женщин устойчивым спросом пользовалась профессиональная косметика из Франции. С приходом в нашу жизнь новых технологий делать покупки стало проще. Последние коллекции легендарной французской косметики вы можете приобрести тут.

29 комментариев

avatar
Ух как полезно!
Тест сегодня неминуем.

Спасибо вам огромное!
avatar
Забыл совсем отчет написать. Все работает великолепно. Спасибо еще раз :)
avatar
обнаружил небольшой баг у себя, там приопределнных ситуациях добавлялся пустой объект на вывод и соответсвенно ошибка выдавалась
короче, если вы брали код, то просьба его обновить — файл /classes/modules/similartopics/SimilarTopics.class.php
  • sys
  • 0
avatar
А это уже не к чему что ли?)
avatar
Огромное спасибо!
И вдвойне — за безвозмездность :)

Попутный вопрос — можно ли сделать, чтобы блок «похожих тем» отображался справа, но только при выводе самого топика?
avatar
Вылезла одна проблемка.
При создании топика с несуществующими ранее данными вылезает парочка ошибок:

Notice: Undefined index: matches in /home/webmaster/www/test/classes/modules/similartopics/SimilarTopics.class.php on line 10

Warning: Invalid argument supplied for foreach() in /home/webmaster/www/test/classes/modules/similartopics/SimilarTopics.class.php on line 10
Т.е. видимо когда нечего подставлять в блок «Похожие топики», они и появляются.
avatar
спасибо, подправить можно проверяя массив на кол-во эл-ов в нем, я подправлю хак и обновлю на след. неделе, может раньше
avatar
в общем обновил, вам надо поправочку сделать в файле: classes/modules/similartopics/SimilarTopics.class.php
посмотрите выше в главном посте пункт №3
avatar
Сперва спасибо вам за апдейт!

Попробовал прикрутить с исправлениями, ошибок стало меньше :)

Наверху ошибка — Notice: Undefined index: matches in /home/webmaster/www/test.site/classes/modules/similartopics/SimilarTopics.class.php on line 10

На месте где сам блок вставлен:
Fatal error: Call to a member function getUrl() on a non-object in /home/webmaster/www/test.site/templates/compiled/%%27^27A^27A2FB40%%topic.tpl.php on line 134

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

Доброй ночи
avatar
Fatal error: Call to a member function getUrl() on a non-object in /home/webmaster/www/test.site/templates/compiled/%%27^27A^27A2FB40%%topic.tpl.php on line 134

Такая же ошибка. Есть решение?
avatar
Пробовал, но не вышло. Криворук я видимо :)
avatar
Мне кажется проблема в том, что нет проверки на то, есть ли вообще этот похожий топик. По крайней мере с остальными топиками у меня проблем нету. Только с одним.
avatar
>можете добавить и по тайтлу, как было первоначально в плагине

А как это сделать? Что-то не догоняю никак…
avatar
Так… поразбирался немного… по сути вы просто берёте запрос «поиска» и применяете его для похожести. То бишь то же самое, что вставить набор тегов топика в поиск по сайту. Я правильно понимаю или нет?
avatar
Стоп… сорри… Заметил что тут стоит параметр «SPH_MATCH_ANY», а не «SPH_MATCH_ALL».

Ну тогда хотелось бы добавить следущее: необходимо добавить поиск только по опубликованным топикам.
avatar
Итак. Просидел почти всю ночь. Вот чего я добился:

в SimilarTopics.class.php

class LsSimilarTopics extends Module {
    public function Init() {
    }
    
	public function GetSimilarTopics($oTopic){
		$sTags = $oTopic->getTags();
		$sTitle = $oTopic->getTitle();
		$iNumTags = str_word_count($sTags);
		$iNumTitle = str_word_count($sTitle);
		($iNumTags > 1) ? 2 : $iNumTags;
        $sTitleTags='@topic_tags "'.$oTopic->getTags().'"/'.$iNumTags.' @(topic_title,topic_text) "'.$oTopic->getTitle().'"/'.round($iNumTitle*0.6);
        $aTopics=array();
        if($data=$this->Sphinx_FindSimilarTopics($sTitleTags,Config::Get('module.similartopics.count')+1,array('topic_publish' => 1))){
            foreach($data['matches'] as $k=>$v){
                $obj=$this->Topic_GetTopicById($k);
                if($k!=$oTopic->getId() && isset($obj)) $aTopics[]=$obj;
            }
        }
        return $aTopics;
    }
}


в Sphinx.class.php

public function FindSimilarTopics($sTerms,$iLimit,$aExtraFilters){
		$cacheKey=Config::Get('module.search.entity_prefix')."similartopics_{$sTerms}_{$iLimit}";
		if(false===($data=$this->Cache_Get($cacheKey))){
			$this->oSphinx->SetMatchMode(SPH_MATCH_EXTENDED2);
			$this->oSphinx->SetLimits(0,$iLimit);
			/**
			 * Устанавливаем атрибуты поиска
			 */
			if(!is_null($aExtraFilters)){
				foreach($aExtraFilters AS $sAttribName => $sAttribValue){
					$this->oSphinx->SetFilter(
						$sAttribName, 
						(is_array($sAttribValue)) ? $sAttribValue : array($sAttribValue)
					);
				}
			}
			if(!is_array($data=$this->oSphinx->Query($sTerms,Config::Get('module.search.entity_prefix').'topicsIndex'))) return FALSE;

			# no results = no cache
			if($data['total']>0) $this->Cache_Set($data,$cacheKey,array(),60*15);
		}
		return $data;
	}


Слегка поясню что же я сделал:
1) Ищется теперь только в опубликованных
2) Усложнён запрос, теперь он формулируется так:
$sTitleTags='@topic_tags "'.$oTopic->getTags().'"/'.$iNumTags.' @(topic_title,topic_text) "'.$oTopic->getTitle().'"/'.round($iNumTitle*0.6);

Теперь теги ищются среди тегов, а заголовок в заголовках и текстах, причём совпадения в тегах по двум словам, а в заголовке должны совпасть 60% слов.

P.S. Конечно в конфигурациях сфинкса (там где вы писали запросы к БД) нужно в запрос к топикам добавить поле topic_tags

Написал просто ппц как, но мне простительно, см. время коммента. Надеюсь кто нить разберётся в этом ночном бреде
avatar
Хм… str_word_count работает не так как хотелось бы… она не считает русские слова почему то… Когда я решил вывести в консоль FirePHP значение $sTitle и $sTags, то все русские слова отображались крякозябрами (но на сайте всё норм отображается)

Куда копать? Или мб кто знает аналог функции по подсчёту слов в строке?
avatar
там все в utf8, так что надо делать utf8_decode() перед тем как работать с текстом, иначе он неправильно считает символы и слова в том числе ;)
avatar
Проблему решил. Вот решение

class LsSimilarTopics extends Module {
public function Init() {
}
public function GetSimilarTopics($oTopic){
$iNumTitle = 0;
foreach (array_unique(preg_split('/[\s,]+/',$oTopic->getTitle())) as $var)
{
if (mb_strlen($var,'UTF-8')>1 and $var!='—') $iNumTitle++;
}
$iNumTags = 0;
foreach (array_unique(preg_split('/[\s,]+/',$oTopic->getTags())) as $var)
{
if (mb_strlen($var,'UTF-8')>1 and $var!='—') $iNumTags++;
}
$iNumTags = count(array_unique(preg_split('/[\s,]+/',$oTopic->getTags())));
$iNumTags = ($iNumTags > Config::Get('module.similartopics.num_tags')-1 )? Config::Get('module.similartopics.num_tags'): $iNumTags;
$sTitleTags='@topic_tags "'.$oTopic->getTags().'"/'.$iNumTags.' @(topic_title,topic_text) "'.$oTopic->getTitle().'"/'.round($iNumTitle*Config::Get('module.similartopics.percent_title'));
$aTopics=array();
if($data=$this->Sphinx_FindSimilarTopics($sTitleTags,Config::Get('module.similartopics.count')+1,array('topic_publish' => 1))){
foreach($data['matches'] as $k=>$v){
$obj=$this->Topic_GetTopicById($k);
if($k!=$oTopic->getId() && isset($obj)) $aTopics[]=$obj;
}
}
return $aTopics;
}
}

в конфиг similartopics внести строчки
$config['num_tags'] = 2; // какое количество тегов должно совпадать с тегами других топиков, что бы другие топики были «похожим»
$config['percent_title'] = 0.6; // процент количества всех слов из заголовка, которые должны совпасть с заголовками и текстами других топиков, чтобы именоваться похожими

Мб проверка на тире лишняя. Я повторных тестов не делал, поэтому решил оставить, время выполнения скрипта от неё не возрастёт.

Забыл уточнить… это всё работает для последних SVN. Не для 0.3.1
avatar
Сорри, ступил… забыл отметить тегами код (для удобочитаемости), повтор:
public function GetSimilarTopics($oTopic){
		$iNumTitle = 0;
		foreach (array_unique(preg_split('/[\s,]+/',$oTopic->getTitle())) as $var)
		{
			if (mb_strlen($var,'UTF-8')>1 and $var!='—') $iNumTitle++;
		}
		$iNumTags = 0;
		foreach (array_unique(preg_split('/[\s,]+/',$oTopic->getTags())) as $var)
		{
			if (mb_strlen($var,'UTF-8')>1 and $var!='—') $iNumTags++;
		}
		$iNumTags = count(array_unique(preg_split('/[\s,]+/',$oTopic->getTags())));
		$iNumTags = ($iNumTags > Config::Get('module.similartopics.num_tags')-1 ) ? Config::Get('module.similartopics.num_tags') : $iNumTags;
        $sTitleTags='@topic_tags "'.$oTopic->getTags().'"/'.$iNumTags.' @(topic_title,topic_text) "'.$oTopic->getTitle().'"/'.round($iNumTitle*Config::Get('module.similartopics.percent_title'));
        $aTopics=array();
        if($data=$this->Sphinx_FindSimilarTopics($sTitleTags,Config::Get('module.similartopics.count')+1,array('topic_publish' => 1))){
            foreach($data['matches'] as $k=>$v){
                $obj=$this->Topic_GetTopicById($k);
                if($k!=$oTopic->getId() && isset($obj)) $aTopics[]=$obj;
            }
        }
        return $aTopics;
    }
avatar
На счёт utf8_decode() не знаю, не проверял. Кто проверит — отпишитесь.
avatar
приятно видеть заинтересованного и увлеченного человека :)
спасибо за дополненения, наверное обновлю плагин до ваших дополнений
avatar
а… забыл :) это же не хак у меня :) так что обновлять нечего
avatar
В структуре Livestreet я еще чайник, но вопрос может коснуться не только меня.

После внесения изменений из комментариев от 21 ноября, возникла проблема с конструкцией Config::Get();.
Модификацию применяю на стабильный LiveStreet 0.3.1
Следует понимать, что доступ к конфигам через объект Config работает только с SVN-версией, или у меня то-то глючит? :)
Когда определил в конфиге константы и заменил Config::Get() на прямое их использование, то мод вроде как заработал, по крайней мере перестал выдавать ошибку об объекте Config.
avatar
Забыл уточнить… это всё работает для последних SVN. Не для 0.3.1


Я же вроде говорил об этом… Будет ли работать на 0.3.1 не знаю… На последних свн будет.
avatar
Понятно. Ну, вроде работает. А последний SVN почему-то не удалось поставить. Ругается на пути постоянно. Все перепроверил. Точнее, не подхватывает пути из конфига.
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.