Хочу поделиться методом определения «похожих» записей. Думаю, будет полезно для блогов или новостных ресурсов.
Цель данного поста показать принцип, имплементация может быть не совсем комильфо, так как автор не проф. программист, а любитель.
Итак, задача
Новости хранятся в MySQL таблице типа:
Небходимо для каждой новости при выводе на странице определить максимально похожие из той же таблицы.
Здесь нас интересует контент полей title, lead, body. Для простоты, будем считать что создаем все с нуля и не будем рассматривать необходимость обработки уже существующих записей.
Поле tags
Добавляем поле tags (на самом деле это псевдо-тэги, но нигде на сайте они показываться не будут — это поле нужно исключительно для сравнения текстов). Тип поля укажите как VARCHAR(512) и добавьте индекс типа fulltext ( FULLTEXT (tags) ).
Генерация псевдо-тэгов
Сгенерируем псевдо-тэги из полей title, lead, body перед записью новости в базу (непосредственно перед INSERT statement). Для этого загрузите phpmorphy и словари отсюда.
Для исключения малозначащих слов (стоп-слов) создадим массив $stopwords, будем использовать текстовый файл для стоп-слов (пример — сохраните как stopwords.txt ).
$stopwords=explode("n", file_get_contents("stopwords.txt"));
Далее, подключаем phpmorphy и его словари, объединяем title,lead и body и прогоняем все слова через phpmorphy.
$lowercaseLetters = array("'а'", "'б'", "'в'", "'г'", "'д'", "'е'", "'ё'", "'ж'", "'з'", "'и'", "'й'", "'к'", "'л'", "'м'", "'н'", "'о'", "'п'", "'р'", "'с'", "'т'", "'у'", "'ф'", "'х'", "'ц'", "'ч'", "'ш'", "'щ'", "'ъ'", "'ы'", "'ь'", "'э'", "'ю'", "'я'");
$uppercaseLetters = array("'А'", "'Б'", "'В'", "'Г'", "'Д'", "'Е'", "'Ё'", "'Ж'", "'З'", "'И'", "'Й'", "'К'", "'Л'", "'М'", "'Н'", "'О'", "'П'", "'Р'", "'С'", "'Т'", "'У'", "'Ф'", "'Х'", "'Ц'", "'Ч'", "'Ш'", "'Щ'", "'Ъ'", "'Ы'", "'Ь'", "'Э'", "'Ю'", "'Я'");
function cyrUpper($str)
{
global $lowercaseLetters;
global $uppercaseLetters;
return str_replace("'", "", preg_replace($lowercaseLetters, $uppercaseLetters, $str));
}
function cyrLower($str)
{
global $lowercaseLetters;
global $uppercaseLetters;
return str_replace("'", "", preg_replace( $uppercaseLetters,$lowercaseLetters, $str));
}
function cleanUP ($new_string)
{
//$new_string=nl2br($new_string);
$new_string= str_replace("-"," ",$new_string);
$new_string= str_replace("rn"," ",$new_string);
$new_string= str_replace("r"," ",$new_string);
$new_string= str_replace("n"," ",$new_string);
$new_string= str_replace("."," ",$new_string);
$new_string = ereg_replace("[^0-9 абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ]", "",$new_string );
return $new_string;
}
require_once( 'morphy/src/common.php');
$text=cleanUP($_REQUEST[title]." ".$_REQUEST[lead]." ".$_REQUEST[body]." ");
$aText = explode(' ',$text);
$aPort = array();
$aMorph = array();
foreach ($aText as $word)
$aMorph[] = cyrUpper($word);//нужно в вин1251 давать не сьедение
// set some options
$opts = array(
'storage' => PHPMORPHY_STORAGE_FILE,
// Extend graminfo for getAllFormsWithGramInfo method call
'with_gramtab' => false,
// Enable prediction by suffix
'predict_by_suffix' => true,
// Enable prediction by prefix
'predict_by_db' => true );
$dir = 'morphy/dicts';
$lang = 'ru_RU';
// Create descriptor for dictionary located in $dir directory with russian language
$dict_bundle = new phpMorphy_FilesBundle($dir, 'rus');
// Create phpMorphy instance
try {
$morphy = new phpMorphy($dict_bundle, $opts);
} catch(phpMorphy_Exception $e) {
throw new Exception('Error occured while creating stemmer instance: ' . $e->getMessage());
}
try {
if($getroot==22)
$pseudo_root = $morphy->getPseudoRoot($aMorph);//можно либо взять корни слов
else
$pseudo_root = $morphy->getBaseForm($aMorph);//либо базовую форму
//для нашей задачи $getroot=TRUE
} catch(phpMorphy_Exception $e) {
throw new Exception('Error occured while text processing: ' . $e->getMessage());
}
foreach ($pseudo_root as $roots){
$slovo=cyrLower($roots[0]);
if (strlen( $slovo)>3 && !in_array($slovo,$stopwords) && count($roots)==1 ) {
$tags.=$slovo." "; }
}
}
Полученный список тэгов в переменной $tags записываем в соотв. поле таблицы. В результате для каждой новости в этом поле будет список слов который мы и будем использовать для сравнения.
Пример
Исходный текст
Компания Samsung начала производство твердотельных жестких дисков с использованием трехмерной памяти V-NAND. Технология позволяет увеличить объем накопителей, а так же обеспечивает в 2 раза более высокую скорость передачи информации и повышает надежность устройств до 10 раз. На данный момент создаются SSD диски объемом 480 и 960 ГБ, только для корпоративных серверов. Что же касается домашних компьютеров, то конкретных сроков выпуска названо не было.
Сгенерированный список слов:
устройство увеличить только технология компания твердотельный срок создаваться скорость надежность сервер производство позволять повышать обеспечивать передача память объем начать накопитель момент корпоративный конкретный компьютер касаться использование информация жесткий диск высокий выпуск трехмерный
SQL запрос
Теперь самое интересное- вот такой SQL запрос будет использоваться для определения похожих записей:
SELECT * FROM news WHERE MATCH (tags) AGAINST ('[ список псевдо тэгов ]' ) > [значение релевантности]
Здесь, значение релевантности это и есть «похожесть» текстов — поэкспериментируйте (начните с единицы)
Автор: EminH