Поиск на php
28 июня 2001 года
95% бесплатных php-скриптов (и не только php) - полный «отстой». Оно и понятно: хороший программист бесплатно ничего писать не будет, а если и будет, то только в свободное время в качестве развлечения, и уж, конечно не всякие банальности, вроде гостевых книг. Или, как говорил Рома Воронежский: «Вот проблема с этими творческими людьми: они всегда желают быть композиторами, художниками и писателями. В результате производством труб большого диаметра занимаются бездарности».
Именно так это и происходит.
Сегодня опять ковырялся в каталогах бесплатных скриптов, главным образом из любопытства, но еще и в тайной надежде найти что-нибудь забавное. В прошлый раз из «забавных» скриптов я нашел, например, «скрипт вывода текстового файла в php». Думал - парсер. Оказалось - да: почти что парсер. Привожу скрипт целиком: «?php include ("text.txt"); ?». Или вот вижу скрипт, написано «This script will reverse the text you give it. reversed: .ti evig uoy txet eht esrever lliw tpircs siht It isn't very useful, it's just funny. Try it out :)», то есть скрипт переворачивает строку задом-наперед. Самые худшие ожидания оправдались: они делали это циклом. Наверное, не знали, что в php есть специальная, уже готовая функция для этого:
Нашел скрипт поиска по сайту: он обшаривает директории, которые вы указали, открывает все html-файлы и тупо сравнивает: У меня тоже такой же скрипт был давным-давно написан, но потом, когда я понял, что народ поиском все-таки пользуется (сюрприз!), решил сделать его по-человечески. С индексом и прочими благами цивилизации: Сделал. В результате 1.5 мегабайта заметок превращаются в 900-килобайтный индекс за 17 секунд (индексацию надо проводить раз в несколько дней, или даже реже - в зависимости от скорости обновления сайта), после чего поиск по индексу происходит меньше одной секунды.
В общем, решил я все-таки поделиться этим скриптом. Краткая информация: скрипт на php, для работы никаких mysql не надо, предполагается, что html-ные (или txt-овые, как у меня) файлы где-то лежат, а не хранятся в mysql. В общем, поисковая машина для небольшого (ну, или «среднего») сайта. Как, например, spectator.ru.
Итак, начали:
Самое первое - скрипт индексации. Для чего он нужен?.. Вот у меня 278 заметок. Если мы будем открывать каждый файл и искать совпадения, то нам надо будет открыть 278 файлов. А это ой как долго: Более того, нам надо будет 278 раз провести хитрые манипуляции с этими файлами (про манипуляции - ниже). Если же у нас есть индекс, то во-первых, поиск происходит в одном файле (индексе), во-вторых, все эти «хитрые манипуляции» уже выполнены.
Алгоритм индексирующего скрипта такой:
- Открываем очередной файл
- Убираем из него «мусор» ( зачем убирается мусор - понятно, чем мусора меньше, тем ищется быстрее: ):
- переводы строк
- html-тэги
- знаки препинания
- слова, короче трех букв (а зачем они там?)
- Делаем заглавные буквы строчными.
- Убираем повторяющиеся слова. (Действительно, зачем нам вся это тавтология?)
- Записываем все в индекс.
- Если еще есть файлы, переходим к пункту 1.
Реализуется это все на php - легко!
<?php // Spectator's Indexing Script // (C) Spectator.ru // Для работы требуется PHP 4 или выше. // Если вы будете использовать этот скрипт, ссылка на Spectator.ru // крайне желательна. Спасибо. // ставим скрипт "на счетчик" (чтобы знать, как долго он выполнялся $ttt=microtime(); $ttt=((double)strstr($ttt, ' ')+(double)substr($ttt,0,strpos($ttt,' '))); $indexdir="text"; #индексируемая директория $indexfile="indexfile.txt"; #файл, в котором будет лежать индекс // если вы хотите индексировать файлы в нескольких директориях, надо // внести несколько махоньких добавлений... // делаем так, чтобы не было таймаута из-за того, что скрипт будет долго // выполняться (на всякий случай) и из-за того, что пользователь нажмет // кнопку "стоп" в браузере $abort = ignore_user_abort(1); set_time_limit(600); // Функция, удалающая слова, короче 3х букв. Пригодится дальше. function sw (&$item1, $key) { if (strlen($item1)<3) $item1=""; } // по очереди открываем все файлы в директории и проверяем, можно ли их // индексировать у меня можно индексировать только файлы, которые имеют // вид "число.txt" то есть && (is_numeric(str_replace (".txt","", $file))) // это вам наверняка не понадобится. $handle=opendir('./'.$indexdir); while (false!==($file = readdir($handle))): if ($file!="." && $file!=".." && (is_numeric(str_replace (".txt","", $file)))): // открываем очередной файл $fd = fopen ($indexdir."/".$file, "r"); $contents = fread ($fd, filesize ($indexdir."/".$file)); Fclose ($fd); // убираем переводы строк $contents=str_replace ("n"," ", $contents); $contents=str_replace ("r","", $contents); // убираем хтмл-тэги $contents=str_replace ('<br>', ' ', $contents); $contents=str_replace ('<p>', ' ', $contents); $contents=strip_tags ($contents); // убираем знаки препинания и цифры // все эти строки работают быстрей, чем один eregi_replace! $contents=str_replace (' -', ' ', $contents); $contents=str_replace ('.', ' ', $contents); $contents=str_replace (',', ' ', $contents); $contents=str_replace ('!', ' ', $contents); $contents=str_replace ('?', ' ', $contents); $contents=str_replace (':', ' ', $contents); $contents=str_replace (';', ' ', $contents); $contents=str_replace (')', ' ', $contents); $contents=str_replace ('(', ' ', $contents); $contents=str_replace ('"', ' ', $contents); // убираем заглавные буквы $contents=strtolower ($contents); // разбиваем на слова, убираем слова, короче 3х букв $contents=explode (" ", $contents); // вот и функция пригодилась... array_walk ($contents, 'sw'); // убираем повторяющиеся слова $contents=array_unique ($contents); // соединяем слова $contents=implode (" ", $contents); // формируем соответствующую строку в индексе. $fullfile.=$file."| ".$contents." n"; // индекс-файл будет иметь вид: // имя_файла|индекс_для_данного_файла n // имя_файла|индекс_для_данного_файла n // имя_файла|индекс_для_данного_файла n echo ($file." проиндексирован<br>"); // переходим к следующему файлу endif; endwhile; closedir($handle); // убираем двойные пробелы while (stristr($fullfile, " ")) $fullfile=str_replace (" "," ",$fullfile); // индекс готов, сохраняем его $fp = fopen($indexfile, "w+"); fwrite($fp, $fullfile); fclose($fp); // считаем, как долго работал скрипт $ddd=microtime(); $ddd=((double)strstr($ddd, ' ')+(double)substr($ddd,0,strpos($ddd,' '))); echo ("<br>Время индексации: ".(number_format(($ddd-$ttt),3)). " секунд<br>"); echo ("Размер индекса: ".(number_format((round ((filesize($indexfile))/1024)) , 0, ".",".")))." Kb"; ?>
Итак, у нас есть индекс. Дальше - просто. Так ведь?.. Надо просто произвести поиск в нем. Берем функцию eregi, например:
Хотя я делал совсем по-другому:
Лирическое отступление: часто, когда надо проверить, если в строке какая-нибудь комбинация символов, пишут что-то вроде этого:
if (eregi('this must be found',$string)) echo 'found!!'; else echo 'нифига не found!';
Способ хороший, но тормозной - из-за eregi. (Функция это работает с регулярными выражениями, поэтому и тормозит). По той же причине рекомендуется использовать там, где это можно, str_replace вместо ereg_replace. Быстрее раз в 10: Поэтому крутые программеры ;), когда им надо проверить, найдено ли что-то в строке, используют функцию strstr. На самом деле, она для этого не предназначена, (верней, «предназначена не для этого»), ибо она «Find first occurrence of a string», то есть «ищет первое местонахождение строки» и выводит строку, начиная с этого самого местоположения. Запутал, верно? (смайлик).
Ок, вот пример с php.net:
$email = 'sterling@designmultimedia.com'; $domain = strstr ($email, '@'); print $domain; // выводит: @designmultimedia.com
Теперь понятно? Функция ищет, где в строке встречается подстрока «@» и выводит все после нее (включительно). Что самое главное - если ничего на найдено, то функция возвращает false. Именно поэтому ее можно использовать вот так:
if (stristr($string, 'this must be found')) echo 'found!!'; else echo 'нифига не found!';
У меня в скрипте для поиска в индексе используется stristr. Кроме того, поиск понимает простейший синтаксис: «+» (слово должно быть найдено, aka AND), «-» (слово не должно быть найдено, aka NOT) и «*» (звездочка). Но, анализируя то, что искали у меня на сайте, могу сказать только одно: Где-то в дискуссии про поисковые машины и их AI (искусственный интеллект), я нашел такую фразу, что «проще выучить последнего дебила пользоваться языком запросов, чем научить поисковую машину угадывать, что же именно этому дебилу надо/». Действительно, запрограммировать поисковую систему так, чтобы она сразу же выдавала то, что надо по идиотским запросам - сложно. Но, похоже, обучить ИХ составлять запросы правильно еще сложней:
В моем поиске всеми этими значками никто не пользуется, как бы я ни распинался. Хотя, когда мне надо найти у себя что-то конкретное, я нахожу с первого запроса (да, конечно, я ведь примерно знаю, что искать, и сам писал скрипт поиска, но все-таки:)
Скрипт простой, но работает надежно. Если правильно составить запрос - то находит все с первого раза. В принципе, можно еще сделать сортировку по релевантности, сделать так, чтобы из найденного файла показывался кусок с текстом, где искомое слово было бы выделено, и прочее: Но это вы делайте сами:
<?php // Spectator's Site Search Script // (C) Spectator.ru // Для работы требуется PHP 4 или выше. // Если вы будете использовать этот скрипт, ссылка на Spectator.ru // крайне желательна. Спасибо. // файл с индексом $indexfile="indexfile.txt"; // обрабатываем запрос $total=0; $qu2=str_replace ("+","&",$words); // убираем заглавные буквы $qu2=strtolower ($qu2); // обрубаем в конце лишние пробелы $qu2=chop($qu2); // убираем двойные пробелы while (stristr($qu2," ")) $qu2=str_replace (" "," ",$qu2); echo ('Запрос: '.$qu2); echo ('<p><p><p>'); // разбиваем запрос на слова $words = explode (' ', $qu2); // удаляем в запросе все лишнее (знаки препинания, и прочее) $qu2=eregi_replace ('[.?,!()#":;|]', '', $qu2); // проверяем длинну запроса if (strlen($qu2)>2): // открываем индекс $index=file ($indexfile); $num= (count($index)-1); // для каждой сточки из индекса (одна строчка=один файл) выполняем: for ($i=1; $i<$num+2; $i++): $contents=$index[$i-1]; $wordcount =0; $mustfound=1; // выполняем для каждого их запрошенных слов: $mustntfound=1; for ($q=0; $q<count($words); $q++): // обработка знаков *, + и - // знак * if (stristr($words[$q], "*")) {$search=str_replace ("*","",$words[$q]); } else { $search=" ".$words[$q]." ";} // если в слове есть звездочка, то убираем звездочку и добавляем в начало // и конец слова по пробелу если нет проблелов, то слово будет искаться не // целиком, а "вообще", то есть на запрос "чай" будет выводиться и слово // "случайный". // bug: скрипт не учитывает, где в слове стоит звездочка и считает, что в // любом случае она стоит в конце (!!) // знак & (или +) if (stristr($search, "&")) {$search=str_replace ("&","",$search); $mustfound++; } // если стоит знак +, то количество слов, которые _должны_ быть найдены, // увеличиваются на 1 // знак - if (stristr($search, "-")) { $search=str_replace ("-","",$search); $mustntfound=0; } // если стоит знак +, то если слово найдено, весь результат умножается на 0 // (смотри дальше). // если слово найдено, считаем его и умножаем на $mustntfound, то есть на 1, // если найдено "правильное" слово и на 0, если найдено слово, помеченное // знаком - if (stristr($contents, $search)) { $wordcount++; $wordcount=$wordcount*$mustntfound; } endfor; // проверяем, все ли слова, помеченные знаком + найдены, // либо (если таких слов нет), найдено ли вообще хоть одно слово if ($wordcount >= $mustfound): // находим имя файла, в котором это найдено $file=explode ("|",$contents); $file=$file[0]; // выводим имя файла, в котором это найдено с ссылкой // (этот кусочек вам надо будет переделать под собственные нужды). $file=str_replace (".txt", "", $file); $file=str_replace ("_", ".", $file); echo ("<a href=".$file.">".$file."</a><br>"); // считаем, колько всего файлов надено $total++; endif; // переходим к следующемуу файлу endfor; // выводим результаты if ($total!=0) echo ('<br><br>Всего найдено страниц: '.$total); else echo ("<b>Ничего не найдено!</b><p>Возможно, вы". ." просто не правильно составили запрос. Как это сделать правильно". ." - смотрите <a href=search>вот здесь</a>."); else: echo ("<br>Слишком короткий запрос!"); endif; ?> <!-- Форма для поиска: --> <form method=get action=search.php> <input type=text size=19 name=words value="" maxlength=150> <input type=submit class=frm value=Go> </form>
PS. PHP.net - очень хороший сайт. 99% того, что мне надо, я нахожу там. Все просто: если вам не понятно, как работает какая-то функция, достаточно просто ввести ее в поле поиска на том сайте и вам выдадут описание функции, но кроме того, на php.net к описанию каждой функции пользователи могут оставлять комментарии, поэтому вам выдадут и гору толковых комментариев. То, что надо.
Оставить комментарий
Комментарии
Вырезаю последнюю гласную остается "дет" после этого ищу value LIKE % дет%
в результате находит детали. Есть какой нить алгоритм поиска корня слова? я просто смотрю на этом же сайте он никогда не перепутает детей с деталями.
stristr('yo','yo');
stristr('yo0',yo);
враки
$ttt=microtime();
$ttt=((double)strstr($ttt, ' ')+(double)substr($ttt,0,strpos($ttt,' ')));
$abort = ignore_user_abort(1);
set_time_limit(600);
function sw (&$item1, $key) { if (strlen($item1)<3) $item1=""; }
setlocale(LC_ALL, 'ru_RU.utf8');
if ($handle = opendir('service/pages')){
while ($file = readdir($handle)){
if ($file != "." && $file != ".." && !is_dir($file)){
$fd = fopen ("service/pages/".$file, "r");
if(!filesize("service/pages/".$file)==0){
$contents = fread ($fd, filesize ("service/pages/".$file));
fclose ($fd);
}
$contents=str_replace ('require_once("../../class2.php")', '', $contents);
$contents=str_replace ('require_once(FOOTERF)', '', $contents);
$contents=str_replace ('require_once(HEADERF)', '', $contents);
$contents=str_replace ('class2', '', $contents);
$contents=str_replace ('$aj = new textparse', '', $contents);
$contents=str_replace ('$caption = $aj -> tpa($caption, "on")', '', $contents);
$contents=str_replace ('$text = $aj -> tpa($text, "on","admin")', '', $contents);
$contents=str_replace ('$ns -> tablerender($caption, $text)', '', $contents);
$contents=str_replace ('$caption =', '', $contents);
$contents=str_replace ('$text =', '', $contents);
$contents=str_replace ('0', '', $contents);
$contents=str_replace ('1', '', $contents);
$contents=str_replace ('2', '', $contents);
$contents=str_replace ('3', '', $contents);
$contents=str_replace ('4', '', $contents);
$contents=str_replace ('5', '', $contents);
$contents=str_replace ('6', '', $contents);
$contents=str_replace ('7', '', $contents);
$contents=str_replace ('8', '', $contents);
$contents=str_replace ('9', '', $contents);
$contents=str_replace ('/','', $contents);
$contents=str_replace ('<br>', ' ', $contents);
$contents=str_replace ('</br>', ' ', $contents);
$contents=str_replace ('<p>', ' ', $contents);
$contents=str_replace ('</p>', ' ', $contents);
$contents=str_replace ('[', '', $contents);
$contents=str_replace (']', '', $contents);
$contents=str_replace (' -', ' ', $contents);
$contents=str_replace ('.', ' ', $contents);
$contents=str_replace (',', ' ', $contents);
$contents=str_replace ('!', ' ', $contents);
$contents=str_replace ('?', ' ', $contents);
$contents=str_replace (':', ' ', $contents);
$contents=str_replace (';', ' ', $contents);
$contents=str_replace (')', ' ', $contents);
$contents=str_replace ('(', ' ', $contents);
$contents=str_replace ('"', ' ', $contents);
$contents=strip_tags ($contents);
$contents=str_replace ('"', ' ', $contents);
$contents=str_replace (' ', '', $contents);
$contents=str_replace ('justify', '', $contents);
$contents=str_replace ("link","", $contents);
$contents=str_replace ('bode', '', $contents);
$contents=str_replace ('году', '', $contents);
$contents=str_replace ('года', '', $contents);
$contents=str_replace ('php', '', $contents);
$contents=str_replace ('sevice', '', $contents);
$contents=str_replace ('pages', '', $contents);
$contents=strtolower ($contents);
$contents=explode (" ", $contents);
array_walk ($contents, 'sw');
$contents=array_unique ($contents);
$contents=implode (" ", $contents);
$fullfile.=$file."| ".$contents." \n";
echo ($file." проиндексирован<br>");
}
}
closedir($handle);
$fp = fopen("indexfile.txt", "w+");
fwrite($fp, $fullfile);
fclose($fp);
$ddd=microtime();
$ddd=((double)strstr($ddd, ' ')+(double)substr($ddd,0,strpos($ddd,' ')));
echo ("<br>Время индексации: ".(number_format(($ddd-$ttt),3))." секунд<br>");
}
?>
$contents=str_replace (' -', ' ', $contents);
$contents=str_replace ('.', ' ', $contents);
$contents=str_replace (',', ' ', $contents);
$contents=str_replace ('!', ' ', $contents);
$contents=str_replace ('?', ' ', $contents);
$contents=str_replace (':', ' ', $contents);
$contents=str_replace (';', ' ', $contents);
$contents=str_replace (')', ' ', $contents);
$contents=str_replace ('(', ' ', $contents);
$contents=str_replace ('"', ' ', $contents);
можно переделать так
$contents = preg_replace('{[-.,!?:;)("]}is', ' ', $contents)
или даже так
$contents = preg_replace('{[[:punct:]]}is', ' ', $contents)
и это не единственный пример. см. описание http://ru2.php.net/preg_replace
----------------------
Видимо, тоТ, кто писал этот скрипт, не слишком-то посвящен в особенности функции stristr (см http://ru2.php.net/stristr). Функция возвращает false, если не найдено ничего. Но она также может возвратить и 0. Если это произойдет (функция возвратит 0), то следующие строки:
if (stristr($contents, $search)) {
$wordcount++;
$wordcount=$wordcount*$mustntfound;
}
не посчитают слово найденным, в то время как оно найдено! Ведь с точки зрения php 0 == false, но 0 !== false! Обратите на это внимание!
Так что... Не все скрипты здесь будут работать хорошо... Подумайте прежде чем что-то писать или качать. А так же больше тестируйте проги. :)
С наилучшими пожеланиями, Чучундер.
string chop(string str); - Возвращает строку без повторяющихся пробелов.
возникнет warning "Warning: fread(): Length parameter must be greater than 0", предлагаю добавить проверку на то что filesize($my_dir.$file)!=0.
Так в фелом скрипт косный мне очень помог. Спасибо.
Предлагаю вместо if ($file!="." && $file!="."...) писать
if(!is_dir($file)).
Нерусский Апач настроить можно.
В скрипте вызвать:
setlocale(LC_ALL, 'ru_RU.utf8');
...
$contents=str_replace (' -', ' ', $contents);
$contents=str_replace ('.', ' ', $contents);
$contents=str_replace (',', ' ', $contents);
...
и есть функция strtr
лучше будет читатся/выполнятся
$contents=strtr($contents,array('-'=>'','.'=>'',.....));
// убираем двойные пробелы
while (stristr($fullfile, " ")) $fullfile=str_replace (" "," ",$fullfile);
Пытается заменить 1 пробел на 1 пробел. Надо писать:
// убираем двойные пробелы
while (stristr($fullfile, " ")) $fullfile=str_replace (" "," ",$fullfile);
Никакие функции не работают с регистром русских букв в "нерусском" апаче. Есть ли решения?