Поиск по сайту (велосипед)

riff

Новичок
Здравствуйте.
Понадобился тут как-то поиск по сайту..., воот. Что для этого нужно: взять готовый инструмент и пользоваться (полнотекстовый индекс, Sphinx, яндекс, ...). Но стало интересно смогу ли самостоятельно написать "подобный инструмент" (в кавычки взято, чтобы не выглядело слишком нагло такое сравнение).

Собственно суть темы - посмотрите, если кому интересно, на мой велосипед, покритикуйте (только совсем уж в порошок не стирайте).

Класс имеет следующую структуру:
PHP:
class ISearch
{
	/**
	 * Добавить страницу к поиску
	 * @param string $category группа, к которой принадлежит страница
	 * @param int $page_id id страницы в вашем каталоге
	 * @param bool $active только активные будут участвовать в поиске
	 * @param string $title заголовок страницы в поиске
	 * @param string $path путь к странице в результатах поиска
	 * @param string $text
	 * @return bool
	 */
	public static function add($category, $page_id, $active, $title, $path, $text) {}

	/**
	 * Обновить страницу в поиске
	 * @param string $category группа, к которой принадлежит страница
	 * @param int $page_id id страницы в вашем каталоге
	 * @param bool $active только активные будут участвовать в поиске
	 * @param string|null $title заголовок страницы в поиске
	 * @param string|null $path путь к странице в результатах поиска
	 * @param string $text
	 * @return bool
	 */
	public static function update($category, $page_id, $active, $title, $path, $text) {}

	/**
	 * Удалить страницу из поиска
	 * @param string $category группа, к которой принадлежит страница
	 * @param int $page_id id страницы в вашем каталоге
	 * @return bool
	 */
	public static function delete($category, $page_id) {}

	/**
	 * заполняем таблицу словами близкими по значению
	 * пример: synonym('подгузник', 'pampers')
	 *         synonym('pampers', 'памперс')
	 * в данном примере слова 'pampers' и 'памперс' будут приравнены к 'подгузник'
	 * и в случае поиска по одному из этих трёх слов, страница будет найдена, если в ней
	 * встречается любое и этих слов.
	 *
	 * так же можно добавить слова имеющие приблизительно одинаковое значение
	 * пример: synonym('телевизор', 'sony') ..ничего другого не придумал
	 *         synonym('конструктор', 'lego')
	 *
	 * слова в написанные в транскрипции
	 * пример: synonym('mercedes', 'мерседес')
	 *
	 * слова в различных падежах
	 * пример: synonym('цветок', 'цветком')
	 *         synonym('цветок', 'цветка')
	 *         synonym('цветок', 'цветах')
	 *         ........
	 *
	 * @param string $word
	 * @param string|null $synonym
	 * @return bool
	 */
	public static function synonym($word, $synonym = null) {}

	/**
	 * Работа со стоп-словами
	 * @param string $word
	 * @param int $flag STOPWORD_CHECK - проверить принадлежит ли слово к группе стоп-слов,
	 *                  STOPWORD_ADD - добавить слово в стоп-словарь,
	 *                  STOPWORD_REMOVE - удалить из стоп-словаря
	 * @return bool
	 */
	public static function stopword($word, $flag = self::STOPWORD_ADD) {}
	
	/**
	 * @param string $text поисковый запрос
	 * @param int $page_num номер выводимой страницы поиска
	 * @param int $limit кол-во элементов на странице
	 * @param string $category группа страниц по которым осуществляется поиск
	 * @return array ['id'=>'', 'active'=>'', 'category_id'=>'', 'page_id'=>'', 'path'=>'', 'title'=>'']
	 */
	public static function search($text, $page_num = 0, $limit = 25, $category = '*') {}
}
Собственно и всё, только заранее прошу прощения за то, обращение к базе написаны не с помощью PDO.
На сайте вся работа с базой данных ведётся через обёртку, выдернуть её не возможно и не нужно, поэтому сделал простенькую заглушку для запросов (класс DB).

Структуру таблиц найдёте в конце файла. Там же есть кое какое дополнительное описание, и тестовый пример.
Управляющие символы в поиске:
"*" - усечение слова (роз* = розетка, розовый, роза, ...). звёздочка может находиться в любой части слова, только имейте ввиду, то сравнение "LIKE" будет проигрывать точному поиску по скорости.
"-" - все слова после минуса - это слова исключения, т.е. в результатах поиска они не должны встречаться.

Ещё раз попрошу прощения, если кого-то взбесит class DB, mysqli_*, не чёткое, возможно описание, и т.п., я постарался оформить, может быть не очень получилось. А ещё могут сказать, что всё это чушь и тогда тратить время на это оформление вообще не имеет смысла.

Код на Pastebin: http://pastebin.com/ZTDM4zXG
 

fixxxer

К.О.
Партнер клуба
чтобы люди хотели посмотреть, не надо над ними издеваться и требовать качать zip

положи на гитхаб, если один файл можно pastebin
 

С.

Продвинутый новичок
Понадобился тут как-то поиск по сайту..., воот.
Понятно, бзик! Бзики (найти абы что) успокаиваются поиском для сайта от Гугла или Яндекса.Поиск чего-то конкретного в базе данных делается очень просто. Все остальное -- от Лукавого.
 

fixxxer

К.О.
Партнер клуба
riff
Непонятны две вещи:
1) зачем хардкодить myisam, если ты не используешь fulltext index
2) чем, собственно, не подошел fulltext index

если просто "захотелось" - добавь стеммер и стоп-слова, тогда будет что-то, чем уже можно пользоваться
 

riff

Новичок
1) зачем хардкодить myisam, если ты не используешь fulltext index
Я, честно, не знаю что лучше использовать, а база была (есть) в isam'ах, поэтому на автомате написал myisam..

2) чем, собственно, не подошел fulltext index
есть страницы с текстами, новости, вопросы, есть каталог товаров, где у каждой ветки может быть описание, сами товары с описаниями, ... Да, для них вобщем можно сделать индекс, но выводить, как минимум, не удобно: (если это глобальный поиск по сайту, то) надо сделать запросы ко всем таблицам, надо продумать объем выводимой информации.

Если выдаётся ссылка на каталог продукции, то надо писать, например, не просто "запчасти" (название ветки каталога), а "автомобиль / мерседес / запчасти", ну чтоб визуально было понятно куда ты попадёшь, т.е. раскрутить ветку до корня. Та же проблема с формированием ссылок в каталогах/подкаталогах/..., + если ещё есть ЧПУ, надо обработать, чтоб построить путь. Лучше это сделать раз при индексировании и после(в поиске) не иметь дело с таблицами вообще.

Также, когда-то, я прочёл на хабре статью, где автор был счастлив от того, что в инструменте Sphinx имел возможность задавать слова-синонимы (у меня в первом сообщении в коде подробнее об этом), тогда мне эта мысль засела в голову, сейчас пригодилась.

Что такое стеммер, только сейчас узнал, подумаю. И за мысль о стоп-словах спасибо. Правильно я понял, это те слова, которые в поисковом запросе просто пропускаются?
 

fixxxer

К.О.
Партнер клуба
Я, честно, не знаю что лучше использовать
Оставь на усмотрение пользователя, по умолчанию - ничего не подставляй (и будет в соответствии с настройками mysql: в новых версиях, например, по умолчанию innodb).

Что такое стеммер, только сейчас узнал, подумаю
Опционально, разумеется.
можно вот так http://forum.dklab.ru/php/advises/HeuristicWithoutTheDictionaryExtractionOfARootFromRussianWord.html
или по-взрослому http://pecl.php.net/package/stem

Правильно я понял, это те слова, которые в поисковом запросе просто пропускаются?
Да.
 

riff

Новичок
Обновлённая версия: http://pastebin.com/ZTDM4zXG

Добавил работу со стоп-словами (функция ISearch::stopwords), соответственно добавилась табличка новая.
Слова из стоп-листа продолжают наравне со всеми индексироваться, но в поисковом запросе не участвуют.

Про MyIsam я написал в комментарии перед структурами таблиц.

Исправил пару ошибок.
 

riff

Новичок
Обновил поиск: http://pastebin.com/ZTDM4zXG
Добавил стеммер. Как и сказал fixxxer, подключается опционально. Не забудьте, ну если кто-то за этой темой следит, скачать файл "search.stem-ru.php" http://pastebin.com/PvJL9d7F
Вынес в отдельный файл затем, что
а) можно добавить стеммеры для других языков
б) у кого "по-взрослому" подключено php расширение php_stem, этот файл не нужен. Слова будут обрабатываться функцией этого расширения. Правда вот проверить его работу (имею ввиду с включённым php_stem) я пока не могу, на виртуальный хостинг мне его сейчас не поставить, надо ставить, проверять всё это на виртуалке... но времени сейчас нет.

Значит так, стеммер можно подключать на этапе индексирования (тогда в индекс попадают только обработанные слова и индекс становится меньше), но искать можно будет только с включённой опцией стеммера, обычный поиск уже будет не возможен.
А можно проиндексировать обычным индексом, тогда все слова попадут в индекс, но искать с включённым стеммером (обычный поиск остаётся в силе).

Как включить стеммер:

Перед поиском ( ISearch::search(...) ) выставить параметр(ы)
PHP:
ISearch::$stemmer = 'Ru'; //указываем язык стеммера или
	//константу (типа STEM_RUSSIAN_UNICODE) если у вас стоит extension php_stem
	//если оставить пустым - поиск будет проводиться обычным способом
ISearch::$stemmer_index = true; //сообщаем поисковику, что база была
	//проиндексирована стеммером Портера
	//это необязательно, но тогда искать будет, возможно, чуть медленнее
Перед индексацией:
PHP:
//для включения стеммера эти два параметра обязательны.
ISearch::$stemmer = 'Ru'; //указываем язык стеммера или
	//константу (типа STEM_RUSSIAN_UNICODE) если у вас стоит extension php_stem
	//если оставить пустым - индексация будет проводиться обычным способом
ISearch::$stemmer_index = true; //сообщаем индексатору, что база должна
	//быть проиндексирована стеммером Портера
 

fixxxer

К.О.
Партнер клуба
так

1) это же предполагается для использования совместно с другим кодом? Класс с именем Db будет очень мешать. Сделай неймспейсы (или если зачем то хочется совместимость с 5.2 - префиксы)
2) с установкой кодировки во первых достаточно set names а во вторых лучше использовать функцию http://www.php.net/manual/en/mysqli.set-charset.php
3) хорошо бы вообще давать возможность кормить библиотеке свой mysqli или pdo-mysql инстанс
4) и префиксы на таблицы не помешают
5) и хорошо бы вообще от статики избавиться. может, у меня мультисайтовый движок :)

потом приведи все к psr-0/psr-1, выложи на гитхаб и можешь смело писать в резюме :)
 

riff

Новичок
5) и хорошо бы вообще от статики избавиться. может, у меня мультисайтовый движок
других пунктов пока не касаюсь.
А этот не понятен: чем плохи вызовы функций ISearch::search(), или чем успупают $search->search() ?

4) и префиксы на таблицы не помешают
Таблицы можно назвать как угодно, только в ISearch::tbl_table_name = 'search_table_name' надо написать соответствующее название вместо "search_table_name". У меня везде в sql'ях вместо реального имени таблицы написано :tbl_words и т.п. (если только вдруг где-то не пропустил)
 

fixxxer

К.О.
Партнер клуба
ну вот допустим у меня три сайта, и я хочу написать фоновый демон, который будет грести очередь изменений. на всех трех сайтах разные настройки.

со статикой так не получится, придется запускать три разных скрипта, или устраивать говномесиво с переконфигурацией в цикле

ну три ладно, не страшно - а если у меня 1000 сайтов? :)
 

riff

Новичок
На самом деле, fixxxer, спасибо за идею стеммера, вообще-то cool. Начало находить слова в разделах, про которые забыл. До конца ещё не ясно аукнится ли в дальнейшем, но в прочем это отключаемо.
 

StalkerClasses

Новичок
Вообще - очень интересная тема...
И вот почему (как уже где-то писал).

Иногда на сайте имеет смысл делать два поиска:
1. один общий - как бы по страницам (которых обычно 100-200 штук на подавляющем большинстве проектов)
эти страницы. как правило не поддаются банальному SELECT * FROM page LIKE '%блабла%'

2. и все другие точечные по типам данных - например по каталогу продукции (где уже 10 000 записей может быть)
здесь мы уже можем сделать запросы SELECT * FROM catalog LIKE title ... param1 param=2
и т.д.

riff
Как понял что бы запустить Ваш поиск
надо вот это:
http://pastebin.com/PvJL9d7F
http://pastebin.com/ZTDM4zXG

И еще такой вопросик про алгоритм - он как сканирует все подряд (в плане ссылок) - т.е. как обходчик?
 

riff

Новичок
1. один общий - как бы по страницам (которых обычно 100-200 штук на подавляющем большинстве проектов)
эти страницы. как правило не поддаются банальному SELECT * FROM page LIKE '%блабла%'
Да. Можно искать like "пушистый%снег" и найти текст "на улице лежит пушистый, белый снег", а, ища "снег%пушистый", ты этот текст не найдёшь.
Поиск, в этой теме, ищет такой контент.

Ещё проблема в том, что при "обычном" поиске, тебе надо для каждой таблицы строить свой поиск.
Предположим, глобальный поиск по сайту:
* опросить таблицу новостей (title, text)
* опросить таблицу товаров (title, короткое_описание, полное_описание, возможно: артикул, бренд, некоторые_ключевые_параметры (типа 3D, LED, чтобы польз. мог найти "телевизоры LED"))
* опросить комментарии или форум (text)
** всё это нельзя сразу вывалить на страницу, надо разбить постранично
и всё это нагружает сервер.

2. и все другие точечные по типам данных - например по каталогу продукции (где уже 10 000 записей может быть) здесь мы уже можем сделать запросы SELECT * FROM catalog LIKE title ... param1 param=2
и т.д.
Да. только в случае моего поисковика, естественно уже пишется не LIKE (пример в самом низу кода).

И еще такой вопросик про алгоритм - он как сканирует все подряд (в плане ссылок) - т.е. как обходчик?
Нет. Я не делал никаких "обходчиков" и делать не буду. Ты сам должен передать в поиск всё то, что посчитаешь необходимым. Можешь так, например:
PHP:
//проиндексировали новости по двум полям
while (...) { ISearch::add('news', ..., $news['title'].' '.$news['text']); }

//проиндексировали продукт №25 по двум полям
ISearch::add('products', ..., 25, ..., $product['title'].' '.$product['text']);
//и ещё добавили название бренда для этого же продукта в поиск
ISearch::add('products', ..., 25, ..., $product['brand']);

//индексируем, при желании, любой текст, не обязательно из базы
ISearch::add('my_category', ..., 'по улице ходила большая крокодила');
А можешь воспользоваться своим обходчиком, и запихнуть страницы в поиск (естественно индексироваться будут не страницы целиком, а очищенные от тэгов и грязи слова).

Как понял что бы запустить Ваш поиск надо вот это:
Да.
 

StalkerClasses

Новичок
Нет. Я не делал никаких "обходчиков" и делать не буду. Ты сам должен передать в поиск всё то, что посчитаешь необходимым. Можешь так, например:
.
К сожалению - в этом и проблема...
Что на 200-300 страниц нужен обходчик в моем случае...
Это не каталоги, не форумы, не страницы с постраничной навигацией. Просто страницы с контентом.

К примеру у Вас есть древовидный сайт на основе БД -...
Вы создали раздел на сайте "где купить" - где выводиться карта городов, представили, метро, контакты, или еще что-то и на странице.
Формально у Вас в этом разделе работает 1 плагин... Вы добавили его как PHP-код. Он дергает информацию из четырех таблиц - и все показывает на одной странице...

Как я могу данный подобный раздел отобразить в поиске - не иначе как-грубо проиндексировать обходчиком?
Это 1 раздел такой... А если подобных разделов 10-20 на сайте со своей логикой вывода и построения материала, где отображение на сайте - может не на 100% - даже соответстовавать тому что в БД.
Только через обходчик...

--

И еще вот пример по поводу Wordpress.
Там Вы весь контент пишите в одном поле - text.
Если Вы разработали плагин - которые вставляете на странице - напимер
[youtube id=1] - который вставляет запись с ютуба с описанием из БД - в поиске Вы уже это описание в результатах поиска не получите!, т.к. [youtube] - плагин обращается к другой таблице. А поиск этого никак учесть не может.
 
Последнее редактирование:

riff

Новичок
К сожалению - в этом и проблема...
Что на 200-300 страниц нужен обходчик в моем случае...

К примеру у Вас есть древовидный сайт на основе БД - ... и т.д.....
Я не писал индексатор сайта, и не писал обходчик сайта (думаю обходчик гуглится не сложно)
я писал :
1. просто индексатор любого переданного текста (будь то описание товара, новости, или текст из текстового документа, или описание картинок, или... короче, не важно чего).
2. и, главное, поиск по этому тексту.

Если ты хочешь индексировать сформированные страницы своего сайта, пожалуйста, формируй и отправляй их в индекс, можешь сам создать плоский список всех url'ов твоего сайта и циклом пройти по ним, а можешь нагуглить обходчик.
 
Последнее редактирование:
Сверху