Создавая свой сайт Вы со временем задумаетесь о необходимости сделать на нем удобный универсальный поиск. Сходу есть простое решение: прикрутить поиск от поисковых систем, например: поиск от Яндекса или поиск от Гугла. Общий недостаток такого решения - в поисковом индексе будут только те страницы, которые поисковая система соблаговалила туда добавить. Иными словами часть Вашего сайта не будет "искаться".
Грустно, поищем другое решение. Да вот же оно: Яндекс.Сервер - это продукт для поиска по вашему сайту с учетом морфологии русского языка. Загрузка здесь. В среде Unix Яндекс.Сервер работает как демон, а на платформе MS Windows — как сервис. Т.е. работать может только при root - доступе на сервер. При работе сайта на виртуальном хостинге не подходит. Второй недостаток - никаких настроек. Только одна кнопка "Запустить/Остановить".
Начинаем "рыть" интернет. Ну как же так? У всех есть поиск. Как-то же люди его делают. Есть например, лобовое решение, давно описанное мной: контекстный поиск на сайте, который не учитывает склонение слов и не индексирует слова на страницах. Но чем дальше углубляешься в задачу тем больше понимаешь, что задача совсем нетривиальная.
Во-первых нужно просканировать весь сайт и выбрать с него все слова. Ну это я уже умею - генератор карты сайта давно с успехом справляется с этой задачей.
Во-вторых нужно получить исходную форму всех слов. Тут есть несколько вариантов, например можно использовать стример, который отрезает приставку, суффикс и окончание у слов. Или более сложную систему, использующую словари.
В третьих это все нужно загрузить в базу и проиндексировать, чтобы поиск занимал минимум времени.
Потратив две недели своего времени, перепробовав большое количество различных вариантов и алгоритмов я остановился на следующем:
1. Для сканирования я использую упрощенный парсер, который с помощью регулярного выражения вырезает
все href со страницы:
<?php
// Получаем уникальные ссылки со страницы
$html=file_get_contents($url);
if(preg_match('|<body.*?>(.*?)</body>|sei', $html, $arr)){
$body=trim($arr[1]);
if(preg_match_all('~<a [^<>]*href=['"]([^'"]+)['"][^<>]*>~si',$body, $arr))
$links=array_unique($arr[1]);
}
?>Скопировать в буфер
Теперь нужно отделить внешние ссылки от внутренних и рекурсивно обратиться к парсеру с адресом внутренней ссылки. Вот тут и начинаются "грабли"... Внутренние ссылки могут быть указаны как внешние с http://домен/адрес, они могут быть относительно текущей страницы, они могут быть относительно тега base. Далее необходимо проверить не запрещена ли индексация этой страницы в robots.txt и не была ли эта страница уже отсканирована. Для проверки можно воспользоваться примером разбора robots.txt и примером поиска по SQL
2. Далее мы должны выделить все слова на странице, для этого воспользуемся регулярным выражением:
$words=preg_split('/[^a-zA-Zа-яА-Я0-9]+/', $body, -1, PREG_SPLIT_NO_EMPTY);
убираем все короткие слова, преобразуем все слова к одному регистру и выделим основу(корень) слова. Для выделения корня слова лучше всего воспользоваться PHP - классом phpMorphy, который позволяет выделять корни слов с учетом морфологии русского, английского, украинского, эстонского или немецкого языков. Словари для каждого языка занимают 10-15 Мб. При этом не требуется устанавливать на сервер дополнительное программное обеспечение, все будет работать на самом обычном хостинге. Недостаток - низкая скрость выделения корня. Подключение библиотеки делается следующим образом:
<?php
$morphy=$path.'phpmorphy/';
require_once($morphy.'src/common.php');
$opts = array(
'storage' => PHPMORPHY_STORAGE_FILE,
'predict_by_suffix' => true,
'predict_by_db' => true,
'graminfo_as_text' => true,
);
$morphy = new phpMorphy($morphy.'dicts', 'ru_RU', $opts);
?>Скопировать в буфер
У объекта phpMorphy три параметра:
первый - путь у папке словарей;
второй - кодовая страницы 'ru_RU' - русский в utf-8, 'rus'- русский в windows-1251;
третий - опции.
В опциях используется важный параметр storage, он может принимать одно из трех значений: PHPMORPHY_STORAGE_FILE (не загружать файлы словарей в память целиком, это самый медленный вариант, но самый экономный в плане работы с ресурсами сервера), PHPMORPHY_STORAGE_SHM (загружать файл словаря целиком в shared - память, требуется расширение PHP shmop) или PHPMORPHY_STORAGE_MEM (также загружать файл в память целиком если не используется shmop, по скорости работы ничем не отличается от предыдущего). На виртуальном хостинге, скорее всего, придется использовать первый вариант, а на выделенном сервере для большей скорости лучше применять варианты с использованием памяти. Выберите вариант под свои задачи, если к модулю планируются частые обращения, то лучше, конечно, использовать вариант с разделяемой памятью.
Пример работы библиотеки phpmorphy есть здесь.
3. Теперь нужно сделать таблицы базы данных, в которых мы будем хранить все результаты сканирования и разбора:
// список страниц сайта в виде ссылки, заголовка и анонса // (первых 300 символов страницы для вывода в результатах поиска). CREATE TABLE IF NOT EXISTS page ( `id` int UNSIGNED NOT NULL PRIMARY KEY auto_increment, `url` varchar(255) not null default \"\" UNIQUE, `title` varchar(128) not null default \"\", `description` text not null default \"\" ) // все слова сайта. // word – то что осталось после стеммера (то что мы называли «корнем») // sound - результат функции soundex для данного слова. CREATE TABLE IF NOT EXISTS word ( `id` INT UNSIGNED NOT NULL PRIMARY KEY auto_increment, `word` varchar(30) not null, `sound` char(4) not null default \"A000\" ) CREATE INDEX idx_word_word ON '.$search->word.' ( word(8) ) CREATE INDEX idx_word_sound ON '.$search->word.' ( sound(4) ) // Каждая строка – это слово «word», встретившеея на странице «page» «cnt»-раз CREATE TABLE IF NOT EXISTS index ( `page` int UNSIGNED not null, `word` int UNSIGNED not null, `cnt` SMALLINT UNSIGNED NOT NULL, UNIQUE (page,word) )
Теперь нужно сделать форму запроса поискового выражения. Простейшая форма поискового запроса выглядит так: