Рецепты программирования на PHP или практические советы по программированию → Обрабатываем фразу для поиска или "примитивный поиск для сайта"
В форму поиска пользователь часто вводит фразу, со знаками препинания, какими-либо символами, а плохой пользователь - с тегами.
Чтобы сформировать более точный запрос требуется разбить вводимую фразу на отдельные слова, а лишние символы убрать.
Для этого я использую несколько функций, приведенных ниже.
| 1 | //разбиваем строку на слова и формируем чистый выходной массив для поиска |
| 2 | //в качестве аргументов передаем строку поиска, минимальное кол-во букв в слове (min) и максимальное (max) |
| 3 | function clearWords($str,$min=2,$max=100) { |
| 4 | $out = array(); $out1 = array(); |
| 5 | $str = normalize($str); |
| 6 | $words = preg_split('/([^A-zА-я]+)/',$str,-1,PREG_SPLIT_NO_EMPTY); |
| 7 | foreach($words as $word) |
| 8 | if (strpos($word,"_")===false && strlen($word)<=self::MAX_WORD_LENGTH && strlen($word)>self::MIN_WORD_LENGTH) |
| 9 | $out[] = $word; |
| 10 | //убираем одинаковые слова |
| 11 | array_unique($out); |
| 12 | //переводим все слова в верхний регистр |
| 13 | foreach ($out as $k=>$v) $out1[]=rstrtoupper($v); |
| 14 | return $out1; |
| 15 | } |
| 16 | |
| 17 | //дополнительные функции |
| 18 | //эта функция убирает знаки препинания в строке |
| 19 | function normalize($str) { |
| 20 | $str = preg_replace('/[.;,-:!?\[\]@\(\)]/',' ',$str); //убиваем знаки препинания |
| 21 | //пропускаем только символы русского и латинского алфавита, все остальное заменяем на _ |
| 22 | $str = preg_replace('/[^\x7F-\xFFA-z0-9 -]/','_',$str); |
| 23 | return $str; |
| 24 | } |
| 25 | //эта функция корректно переводит русские слова в нижний регистр |
| 26 | //строки в верхний и нижний регистр (корректно обрабатывает cp1251) |
| 27 | function rstrtoupper($str) { |
| 28 | return strtr($str, |
| 29 | "abcdefghijklmnopqrstuvwxyz". |
| 30 | "\xE0\xE1\xE2\xE3\xE4\xE5". |
| 31 | "\xb8\xe6\xe7\xe8\xe9\xea". |
| 32 | "\xeb\xeC\xeD\xeE\xeF\xf0". |
| 33 | "\xf1\xf2\xf3\xf4\xf5\xf6". |
| 34 | "\xf7\xf8\xf9\xfA\xfB\xfC". |
| 35 | "\xfD\xfE\xfF", |
| 36 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ". |
| 37 | "\xC0\xC1\xC2\xC3\xC4\xC5". |
| 38 | "\xA8\xC6\xC7\xC8\xC9\xCA". |
| 39 | "\xCB\xCC\xCD\xCE\xCF\xD0". |
| 40 | "\xD1\xD2\xD3\xD4\xD5\xD6". |
| 41 | "\xD7\xD8\xD9\xDA\xDB\xDC". |
| 42 | "\xDD\xDE\xDF"); |
| 43 | } |
что примечательно, более наглядная и короткая функция перевода русских слов в верхний регистр работает в несколько раз медленее.
Эта функция работает на 10000 циклов 0.1сек (предыдущий код работает за 0.04 сек.)
| 1 | function rstrtoupper($str) { |
| 2 | return strtr(strtoupper($str), '[абвгдеёжзиклмнопрстуфхцчшщьъэюя]', '[АБВГДЕЁЖЗИКЛМНОПРСТУФХЦШЩЬЪЭЮЯ]'); |
| 3 | } |
Теперь осталось считать запрос из формы поиска и состряпать запрос к базе данных
| 1 | //считываем строку, введенную в поисковой форме |
| 2 | $search = isset($_REQUEST["search"]) && trim($_REQUEST["search"]) ? trim($_REQUEST["search"]) : FALSE; |
| 3 | if($search) { |
| 4 | //получаем массов чистых слов |
| 5 | $words = clearWords($search); |
| 6 | //добавляем ограничители для формирования условия WHERE к каждому слову |
| 7 | foreach($words as $k=>&$v) $v="'".$v."%'"; |
| 8 | //формируем условие WHERE |
| 9 | $where = implode(" OR header like ".clearWords($search); |
| 10 | //отрезаем первое " OR" из условия |
| 11 | $where = preg_replace("/^\sOR/","",$where); |
| 12 | //формируем окончательный запрос к БД |
| 13 | $query = "SELECT * FROM table WHERE ".$where; |
| 14 | } |
В результате выполнения этого запроса мы получим массив всех строк таблицы table, у которых заголовок хоть одним словом похож на запрос посетителя.
Например, если пользователь ввел в форму поиска словосочетание "Ищу большой дом на Канарах", то запрос будет вида:
| 1 | SELECT * FROM table WHERE header like 'ИЩУ%' OR header like 'БОЛЬШОЙ%' OR header like 'ДОМ%' OR header like 'КАНАРАХ%' |
В реальной жизни "хороший" поиск устроен немного по другому. Если вкратце, то все материалы сайта проиндексированы заранее и каждое слово в базовой словоформе "уложено" в отдельную индексную табличку, в которой указан идентификатор статьи (товара, новости и т.п.) сайта, идентификатор базовой словоформы, смещение слова относительно начала статьи и еще несколько параметров.
Все словоформы, которые встречаются на сайте, также уложены в отдельную табличку - словарик сайта. Эта таблица содержит все слова, которые встречаются на сайте.
Далее, очищенные слова, которые ввел пользователь, приводятся к основным словоформам, для каждого слова ищется его идентификатор с словарике сайта и по идентификаторам ищутся связи в таблице индексов.
В этом случае поиск уже можно сделать релевантным и с учетом морфологии русского языка.
Также, в результатах поиска не сложно выделить слова, которые задал пользователь.
"Хороший" поиск с учетом морфологии я реализовал в нескольких своих проектах. Например, на сайтах: .
Это отдельная и довольно сложная тема, которую постараюсь написать в следующих материалах.
