Наша группа в Telegram для обмена идеями, проектами, мыслями, людьми в сфере ИТ г.Ростова-на-Дону: @it_rostov

Поиск по сайту с учетом морфологии русского языка на PHP + карта сайта

Создавая свой сайт Вы со временем задумаетесь о необходимости сделать на нем удобный универсальный поиск. Сходу есть простое решение: прикрутить поиск от поисковых систем, например: поиск от Яндекса или поиск от Гугла. Общий недостаток такого решения - в поисковом индексе будут только те страницы, которые поисковая система соблаговалила туда добавить. Иными словами часть Вашего сайта не будет "искаться".

Грустно, поищем другое решение. Да вот же оно: Яндекс.Сервер - это продукт для поиска по вашему сайту с учетом морфологии русского языка. Загрузка здесь. В среде Unix Яндекс.Сервер работает как демон, а на платформе MS Windows — как сервис. Т.е. работать может только при root - доступе на сервер. При работе сайта на виртуальном хостинге не подходит. :-( Второй недостаток - никаких настроек. Только одна кнопка "Запустить/Остановить".

Начинаем "рыть" интернет. Ну как же так? У всех есть поиск на своем сайте. Как-то же люди его делают. Есть например, лобовое решение, давно описанное мной: контекстный поиск на сайте, который не учитывает склонение слов и не индексирует слова на страницах. Но чем дальше углубляешься в задачу тем больше понимаешь, что задача совсем нетривиальная.

Во-первых нужно просканировать весь сайт и выбрать с него все слова. Ну это я уже умею - генератор карты сайта давно с успехом справляется с этой задачей.

Во-вторых нужно получить исходную форму всех слов. Тут есть несколько вариантов, например можно использовать стример, который отрезает приставку, суффикс и окончание у слов. Или более сложную систему, использующую словари.

В третьих это все нужно загрузить в базу и проиндексировать, чтобы поиск занимал минимум времени.

Потратив две недели своего времени, перепробовав большое количество различных вариантов и алгоритмов я остановился на следующем:
1. Для сканирования я использую упрощенный парсер, который с помощью регулярного выражения вырезает все href со страницы:

// Получаем уникальные ссылки со страницы
$html=file_get_contents($url);
if(preg_match('|<body.*?>(.*?)</body>|si', $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 Мб. При этом не требуется устанавливать на сервер дополнительное программное обеспечение, все будет работать на самом обычном хостинге. Недостаток - низкая скрость выделения корня. Подключение библиотеки делается следующим образом:

$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)
    )

Теперь нужно сделать форму запроса поискового выражения. Простейшая форма поискового запроса выглядит так:

Поиск:

её код так:

<form method="get" action="/search/">
   Поиск: <input type=text name="q" value="">
   <input type=submit value="Найти">
</form>

Скачать скрипт поиска по сайту

На создание этого примера у меня ушло уж очень большое количество времени, поэтому хочется конвертировать его в деньги. Если Вы хотите повторить мой подвиг - удачи. Если Вы цените свое время, я с удовольствием обменяю время на деньги. Всего за 2900рублей (~40$) Вы получите полный открытый, подробно откоментированный скрипт поиска с генератором карты сайта.

Содержимое архива:

  • phpmorphy/ - библиотека для выдления корня слов
  • stemmer/ - выделение основы слова быстрым алгоритмом
  • config.php - настроки для работы с БД и общие функции, которые возможно вы уже используете и замените их на свои
  • index.php - поисковая форма + результаты поиска
  • install.php - создание таблиц БД MySQL для поиска
  • link_bar.php - постраничная навигация
  • search.php - класс для работы с поиском. Содержит методы:
    • sound_ex($string) - русский soundex для получения звучания слова
    • update($url, $scan=0) - рекурсивно сканировать все страницы сайта, выделить заголовок страницы, тело, описание.
    • ParsingWord($url, $words) - разбор слов и добавление их в поисковую базу
    • GetWords($words) - в переданном массиве заменяет все слова на их корни
    • url_short($url,$base='',$ext=0) - разбор ссылки, отделение внешних от внутренних
    • is_robots($url) - проверка присутствия ссылки в robots.txt
    • ReadUrl($site) - чтение страницы сайта с помощью Curl, обработка 301,302 - переадресации
  • sitemap.php - построение карты сайта sitemap.xml
  • spider_http.php - сканер сайта на основании чтения и разбора страниц
  • spider_sitemap.php - сканер сайта на основании разбора sitemap.xml

Инструкция по установке:

  1. Распакуйте содержимое архива search.zip в папку search. Разрешите запись в неё из скриптов-установите права 777.
  2. Отредактируете настроки использования БД в файле config.php Запустите install.php - будут созданы необходимые базы данных
  3. Запустите "/search/spider_http.php" сканер наполняет таблицы базы:
    таблица всех страниц сайта, в неё попадают title, keywords и description.
    таблица слов, в неё попадают корни слов, найденных на страницах
    Возможно формирование базы на основании существующей карты сайта, для этого используйте "/search/spider_sitemap.php"
  4. Размещаете поисковый запрос на страницах. Форма поискового запроса:
    <form method="get" action="/search/">
    Поиск: <input type=text name="q" value=""><input type="submit" value="Найти">
    </form>
  5. Редактируете под себя формат вывода результатов на странице search.php
  6. запускаете include_once 'updater.php'; update($url);
    при добавлении, изменении или удалении каждой страницы
    $url - страница, которую нужно обновить.
    удобно вызывать после сохранения изменений страницы
    если страница возвращает 404 ошибку или она пустая - она будет удалена из базы.
  7. запускаете "/search/sitemap.php" для создания карты сайта sitemap.xml
    не забудьте прописать путь к карте сайта в robots.txt:
    sitemap: /search/sitemap.xml

Возможности скрипта поиска по сайту

  • Сканирование всех страниц сайта с учетом запрета в robots.txt и <noindex>
  • Разбор текста страниц с выделением слов, подсчет статистики слов
  • Выделение на странице title, keywords, description
  • Выделение корней слов с учетом морфологии русского языка и библиотек phpMorphy
  • Выделение основ слов быстрым алгоритмом(не рекомендуется, закоментировано в тексте скрипта)
  • Проверка русской орфографии при сканировании, основанная на отсутствии слова в словаре
  • Четыре режима сообщений: 0-работать молча, 1-выдавать только ошибки сайта, 2-выдавать ошибки и минимум информации, 3-подробное информирование при работе
  • Поиск по созвучию слов. Русский soundex.
  • Сортировка результатов поиска по релевантности. В первую очередь показываются страницы, на которых есть все поисковые слова в максимальном количестве.
  • Постраничный вывод найденных результатов
  • Скрипт подробно откомментирован на русском языке
  • Код скрипта реализован на PHP + MySQL, полностью открытый и не использует никаких дополнительных библиотек. Все необходимое идет в комплекте.
  • Генератор карты вашего сайта на основании базы, созданной сканером

    Что скрипт не может:

  • не учитываются <meta name="robots" content="noindex,nofollow">, rel=nofollow
  • не убираются из поиска общие тексты, присутствующие на всех страницах

Соглашение по использованию:

  • Вы можете использовать полученный код в любых своих разработках, вы не обязаны указывать ссылку на источник.
  • Вы НЕ имеете права перепродавать её, размещать в свободном или ограниченном доступе, а также публиковать в любом виде.
  • Все остальные права сохраняются за автором.
  • Вы можете обратиться к автору с вопросами, замечаниями, пожеланиями. Контакты здесь.

Будьте внимательны! За 2900 рублей (~40$) Вы можете выбрать один из двух вариантов скрипта, которые существенно отличаются друг от друга.
Скрипт поиска для сайта в кодировке UTF-8 использует функции работы с двухбайтными символами mb_*, разбирает страницы регулярными выражениями сделанными для кодировки UTF-8 (unicod / Юникод), создает таблицы БД в utf-8.
Скрипт поиска для сайта в кодировке Windows-1251 использует функции для работы только с однобайтными кодировками str*, разбирает страницы регулярными выражениями сделанными для однобайтных кодировок.
Вы можете войти или зарегистрироваться! Или без регистрации
При нажатии кнопки загрузить, Вы подтверждаете согласие с условиями использования скрипта, описанными на этой странице.
С Вашего баланса будет списана сумма в 2900 рублей (~40$) и начнется загрузка файла.