![]() |
![]() |
||||||
| . | ........ | . | ........ | . | Январь / 2000 | ||
| . |
|
. |
Простая поисковая система. Вернуться к началу Рэйвен М. Лернер Linux Journal, #69, Январь 2000 Перевод с английского: Игорь Грень gren@open.by Теперь, когда мы можем производить поиск по любой ключевой фразе внутри отдельного каталога, давайте объединим эту задачу с Web и будем просматривать все документы HTTP- сервера. Такая программа должна получить от пользователя только маску для поиска, так как корневой каталог Web-сайта изменяется не очень часто. Listing 6. HTML Input Form <HTML> <Head><Title>Search form</Title></Head> <Body> <Form method="POST" action="/cgi-bin/simple-cgi-find.pl"> <P>Pattern: <input type="text" name="pattern"></P> <input type="submit" value="Start search"> </Form> </Body> </HTML> В Листинге 6 приведена HTML- форма, которую можно использовать для подобного ввода. Эта HTML- форма передает свое содержимое программе simple-cgi-find.pl, CGI программе из Листинга 7. Ее параметр (pattern) содержит маску для поиска, которая будет сверяться с каждым файлом в структуре Web-сайта, а программа simple-cgi-find.pl в итоге возвратит список найденных документов. Listing 7. simple-cgi-find.pl #!/usr/bin/perl -w use strict; use diagnostics; use File::Find; use CGI; use CGI::Carp qw|fatalsToBrowser|; # Which directory should start the search? my $search_root = "/usr/local/apache/htdocs"; # Slurp up files in one fell swoop undef $/; # Create an instance of CGI my $query = new CGI; # Send a MIME header print $query->header("text/html"); # Get the text pattern for which to search my $pattern = $query->param("pattern"); # Make sure that $pattern is defined unless ($pattern) { print $query->start_html(-title => "No pattern named"); print "<P>You must enter a pattern!</P>"; print $query->end_html; exit; } # Start the HTML output print $query->start_html(-title => "Search results"); print qq{<P>The following documents matched the pattern "$pattern":</P>\n}; # Start an unordered list print "<ul>\n"; # Search for find(\&find_matches, $search_root); # End an unordered list print "</ul>\n"; print $query->end_html; #----------------------------------------- # Subroutine that searches through files for # matches sub find_matches { # Make sure that this is an HTML file return unless m/\.html?$/i; # Get the filename my $filename = $_; # Open the file, and search through its # contents if (open FILE, $filename) { # Get the file my $contents = (<FILE>); # Print the filename, with the directory print qq{<li>$File::Find::dir/$filename\n} if ($contents =~ m|\b$pattern\b|is); close FILE; } else { warn qq{Unable to open "$filename": $! }; return; } } К сожалению, версия File::Find, поставляемая с Perl, не поддерживает флаг -T, который включает режим безопасности tainting. CGI программы всегда должны запускаться с этим флагом, для того чтобы данные, полученные от внешних источников, не могли представлять потенциальной угрозы безопасности системы. В нашем случае мы не можем использовать это режим. File::Find обращается к подпрограмме fastcwd модуля Cwd, который не может нормально работать с флагом -T. В настоящее время я советую использовать эти программы без -T, но когда выйдет следующая версия Perl, я настоятельно рекомендую обновить текущую версию, чтобы CGI программы могли работать в режиме tainting. Нашу подпрограмму поиска find_matches стоит немного изменить для удобства пользователей Web. Прежде всего, поиск будем производить в HTML или текстовых файлах. Нет смысла перебирать все графические файлы: return unless (m/\.html?$/i or m/\.te?xt$/i); На некоторых Web-сайтах гипертекстовые документы имеют расширения .htm (или .HTM), а текстовые- соответственно .txt или .TXT вместо .text. Маска, приведенная выше, удовлетворяет всем вариантам, игнорирует регистр символов по ключу /i и рассматривает только расширения (хвосты имен файлов) по метасимволу $. После получения содержимого текущего файла, find_matches проверяет наличие $pattern внутри переменной $contents, которая и хранит содержимое документа. Мы окружили $pattern символами \b, для поиска $pattern в границах одного слова. Теперь поиск "foo" не совпадет со словом "food", несмотря на то, что это слово содержит нашу маску. Если совпадение найдено, find_matches генерирует URL, подменяя $search_root на $url_root и скрывая истинную иерархию хранения HTML- документов от внешних пользователей. Затем печатается имя файла вместе с гиперссылкой на этот URL: if ($contents =~ m|\b$pattern\b|ios) { my $url = "$File::Find::dir/$filename"; $url =~ s/$search_root/$url_origin/; print qq{<li><a href="$url">$filename</a>\n} } Развиваем наш Web -поиск Хотя наша программа simple-cgi-find.pl уже работает, она имеет несколько недостатков. Для начала, она не отличает тегов HTML от содержимого страницы. Поиск "IMG" не должен выдавать нам все документы, которые содержат тег <IMG>, а только те из них, которые содержат это слово вне команд HTML. Для этого заставим нашу программу "вырезать" тэги HTML из исходного файла. Начинающие программисты часто думают, что лучший способ избавиться от тэгов HTML- это удалить все, что заключено в скобки < и >, например: $contents =~ s|<.+>||g; Так как символ точки "." в Perl - это совпадение с любым символом, а плюс "+" - с одним или более предшествующих, приведенная команда, по-видимому, должна удалить все теги. К сожалению, в действительности это не так, и эта команда удалит все, что находится между самым первым символом "<" и последним ">" в файле. Это произойдет вследствие того, что стандартные маски у Perl слишком "жадные" и стараются найти максимальное число символов. Мы можем уменьшить "жадность" маски "+" и свести к минимуму количество совпадений, добавив символ "?", например: $contents =~ s|<.+?>||g; Но остается еще одно "слабое место" - случай, если $pattern содержит пробелы. Можно ли при поиске обрабатывать ключевые фразы, которые включают пробелы? Или мы должны разделять искомые слова логическими операторами "or" или "and"? Listing 8. Form with Radio Buttons <HTML> <Head><Title>Search form</Title> <Body> <Form method="POST" action="/cgi-bin/better-cgi-search.pl"> <P>Search string: <input type="text" name="pattern"></P> <P><input type="radio" name="type" value="or"> at least one word <input type="radio" name="type" value="and"> all words <input type="radio" name="type" checked value="phrase">exact phrase<P> <P><input type="submit" value="Search!"></P> </Form> </Body> </HTML> В данном частном случае мы тоже сможем испечь наш пирожок и съесть его. Добавив набор "радио"- кнопок в HTML- форму, можно предоставить пользователю выбор вида поиска: по точному совпадению всей ключевой фразы или по совпадению хотя бы одного слова, входящего в нее. Теперь мы можем усовершенствовать нашу программу, добавив возможность "поиска по всей фразе" (как мы и делали до сих пор), поиска по логическому "И" (должны быть найдены все слова ключевой фразы) и логическому "ИЛИ" (поиск хотя бы одного слова из фразы). Чтобы реализовать поиск по логическому "И", мы разделяем элементы фразы, используя оператор Perl "split". Затем подсчитываем количество слов, которое нам нужно найти, перебираем их все на наличие в переменной $contents. Если счетчик $counter достиг нулевого значения, следовательно, мы перебрали все варианты: elsif ($search_type eq "and") { my @words = split /\s+/, $pattern; my $count = scalar @words; foreach my $word (@words) { $count- if ($contents =~ m|\b$word\b|is); } unless ($count) { print qq{<li><a href="$url">$filename</a>\n}; $total_matches++; } } Поиск "ИЛИ" реализовать еще проще: мы опять разбиваем $phrase на части по количеству пробелов. Если найдено хотя бы одно из полученных слов, мы можем незамедлительно выводить имя файла и гиперссылку и возвращаться из find_matches: elsif ($search_type eq "or") { my @words = split /\s+/, $pattern; foreach my $word (@words) { if ($contents =~ m|\b$word\b|is) { print qq{<li><a href="$url">$filename</a>\n}; $total_matches++; return; } } } В конце концов, мы должны сообщить пользователю количество найденных документов. Мы сделаем это, создав новую переменную, $total_matches, которая будет увеличиваться на единицу при каждом удачном поиске (что отражено во фрагментах программы, приведенных выше для поиска по "И" и "ИЛИ"). Программа, в которую внесены все эти изменения, называется better-cgi-search.pl (Листинг 9 не приведен в этой статье, но доступен в архиве, содержащем все тексты программ по адресу: ftp://ftp.ssc.com/pub/lj/listings/issue69/3753.tgz. Исключаем каталоги и файлы Теперь у нас есть готовая поисковая программа, она может выполнять все виды поиска, которые только можно пожелать. Проблема в том, что наша программа может оказаться слишком хорошей и полезной. Многие размещают информацию в Web и не хотят, чтобы она была немедленно доступна всем. Не создают ссылок на определенные каталоги и документы и не желают предоставлять к ним общий доступ. Но наша программа никак не зависит от гиперссылок при выполнении поиска. Самый простой выход - сделать так, чтобы программа не просматривала каталоги, в которых имеется файл .nosearch. Этот файл не должен содержать никакой информации, так как уже его наличие означает, что каталог исключен из поиска. Проверить наличие файла .nosearch в каждом текущем исследуемом каталоге не сложно. Но такая проверка при каждом вызове find_matches заметно повлияет на скорость работы программы. Будет лучше, если программа, после того как обнаружит файл .nosearch, сохранит информацию об этом каталоге и будет использовать ее в дальнейшем. Другая проблема Мы можем решить эти проблемы, добавив в программу две строки. Первая, помещенная в начало find_matches, немедленно возвращает нас обратно в случае, если в текущем каталоге найден файл .nosearch: return if ($ignore_directory{$File::Find::dir}); Если мы перешли на следующую команду, значит, файл .nosearch не был найден в этом каталоге. Но файл .nosearch может быть обнаружен при различных обстоятельствах: когда мы исследуем сам файл .nosearch, когда файл .nosearch находится в текущем каталоге или когда он находится в "родительском" каталоге, на уровень выше. Если определенный каталог исключен из просмотра, значит, нужно исключить и все вложенные в него подкаталоги. Вот несколько команд, которые делают эту работу: # Mark the directory as ignorable ... $ignore_directory{$File::Find::dir} = 1 if (($_ eq ".nosearch") || (-e ".nosearch") || (-e "../.nosearch")); Версия программы better-cgi-search.pl с этими дополнениями хранится в Листинге 10 (который можно найти в архиве по ссылке, приведенной выше). Есть ли способы ускорить поиск? Если вы уже запустили эти программы, скорее всего вы уже столкнулись с проблемой, упомянутой ранее: они работают очень медленно. Если ваш Web-сайт состоит из сотни файлов, все работает прекрасно. Но если ваш сайт вырос до 1000 или 10000 файлов, пользователь прервет поиск, не дождавшись результатов его работы, так как он занимает очень много времени. По этой причине большинство серьезных поисковых систем применяют иную стратегию, которая разделяет процесс поиска на два этапа. На первом этапе индексирующая программа перебирает все файлы и сохраняет информацию об их местонахождении. Затем вторая программа запускает поискового клиента и просматривает предварительно сгенерированный индексный файл на наличие совпадений. В следующей статье я расскажу, как создавать такие индексы и как их просматривать. Возможно, наша простая поисковая программа не сможет состязаться с Glimpse и ht://Dig, но, по крайней мере, мы узнали, как подобные программы работают и как они создаются. |
Содержание ........ Трехмерные миры в Web: VRML Виталий Фридман ........ Простая поисковая система Рэйвен М. Лернер Linux Journal, #69, Январь 2000 ........ Интернет коммерция в Беларуси Дмитрий Шейко ........ Гиперссылка на любимую телепередачу Алиса Бизяева ........ Quake. Третье пришествие Денис Москаленко ........ |