ВыходВход

Меню сайта

Категории статей
Nested Sets [10]
Работа с деревьями Nested Sets
Программирование Perl [5]
Статьи по теоретическому и практическому применению Perl, при проектировании интернет приложений
HTML + JavaScript [1]
Некоторые примеры HTML верстки с использованием JavaScript
Устаревшие статьи [4]
Данные статьи были написаны довольно давно, поэтому могут быть не актуальны в данное время

Меню пользователя

Поиск по статьям

Друзья сайта

Поиск по сайту - статичный контент (часть вторая)
» Каталог статей » Программирование Perl
Поиск по сайту - статичный контент (часть вторая)

Поиск по сайту - статичный контент (часть вторая)

2. Способ второй: "Изобретаем велосипед" или "Пляски с бубнами"

... А в PostrgeSQL FULLTEXT нету :(... (Цитата из ЖЖ)

Достаем из тумбочки старый любимый бубен, разжигаем костер и начинаем готовится к пляске.

Сразу хочу сказать, что данное решение мне нравится больше:

  • во-первых - используются стандартные инструменты, что позволяет сделать поисковую систему максимально кроссплатформенной;
  • во-вторых - возможность более "тонкой" настройки поисковой системы в целом и в частности.

По каким критериям производится поиск по сайту:

  • совпадение слова - это само собой;
  • "вес" слова на страницы, то есть количество повторов слова на странице.

При этом я совершенно не учитываю расположение слова на странице и то, находятся ли поисковые слова рядом, или же в разных частях документа. С одной стороны - это плохо, но с другой - мы же не пишем поисковую систему Google, нам нужно найти что-либо в пределах одного сайта, поэтому излишние критерии релевантности - ни к чему, только лишняя головная боль и бесполезная трата ресурсов.

Для нашей поисковой системы нужно будет создать три таблицы:

  • слова (search_main) - таблица в которой хранятся (раздельно!) все поисковые слова  сайта, страница к которой они относятся и их вес;
  • страницы (search_page) - URL, заголовки и описания страницы. Хотя возможно эти данные хранить применительно к каждому поисковому слову, но это тоже лишняя трата ресурсов;
  • фильтр (search_filter) - список слов не включаемых в поисковые - это имена стилей, некоторые теги, операторы JavaScript; в общем, те слова, которые не требуются для поиска.

2.1. Организация таблиц

Структура таблиц и связей выглядит так:

Команды на создание таблиц:

CREATE TABLE `search_filter` (              `word` varchar(100) NOT NULL,              `note` varchar(100) NULL,         PRIMARY KEY (`word`) ) TYPE=MyISAM;

CREATE TABLE `search_main` (              `word`       varchar(100) NOT NULL   default '',              `page`       int(11)       NOT NULL   default '0',              `relevance`  int(11)       NOT NULL   default '0',         KEY `word` (`word`,`page`) ) TYPE=MyISAM;

CREATE TABLE `search_page` (              `id`           int(11)        NOT NULL,              `url`          varchar(200)   NOT NULL   default '',              `title`        varchar(200)   NOT NULL   default '',              `description`  text           NOT NULL,         PRIMARY KEY (`id`) ) TYPE=MyISAM;

2.2. Предварительное формирование данных или просто формирование данных

Не будем возвращаться к рекурсии и обработке файла, так как они идентичны (о чем было сказано выше).

Итак, что мы должны сделать в этой процедуре. Контент практически подготовлен, нужно сформировать 2 блока (файла) данных. Для этого в самом начале скрипта откроем для последовательной записи (если они не были заранее очищены, то их очищаем) и выберем слова исключения (search_filter). Так же в начале скрипта мы определяем глобальную переменную $i =1 которая будет у нас идентификатором страницы, вот почему мы не указали при создании таблиц автоматических счетчиков. Объясняю почему:

  • во-первых, данные вставляются в базу данных не сразу, а после обработки всей информации, а нам нужно будет сразу определять связь слово->страница;
  • во-вторых, даже при последовательном внесении информации в базу данных, прийдется делать дополнительный запрос для определения последнего идентификатора страницы;
  • в-третьих, таблица базы данных пустая, и за уникальность идентификаторов можно не волноваться.
#!/usr/bin/perl # Подключаем основные модули use strict; use warnings; use DBI; use locale; use POSIX qw (locale_h);     setlocale(LC_CTYPE, 'ru_RU.CP1251');     setlocale(LC_ALL, 'ru_RU.CP1251'); # Обозначаем глобальные переменные use vars '$dbh', '$url_start', '$dir_start', '@dir_filter', '@file_type', '$i', '%filter'; # Инициализируем идентификатор страниц $i = 1; # Директория DocumentsRoot сайта $dir_start = '/var/www/sites/alfakmv/html'; # Домен сайта $url_start = 'http://www.alfakmv.ru'; # Фильтр директорий (директории, которые исключаются из индексации) @dir_filter = (                 'cgi-bin',                 'images',                 'temp',                ); # Фильтр файлов (какие расширения файлов индексировать) @file_type = (                 'shtml',                 'html',                 'htm',               ); # Коннектимся $dbh = 'DBI'->connect('DBI:mysql:database=search;host=localhost;port=3306', 'user', 'pass')             || die $DBI::errstr; # Выбираем слова - исключения my $sql = 'SELECT word FROM search_filter'; my $sth = $dbh->prepare($sql);     $sth->execute() || die $DBI::errstr;     while (my $row = $sth->fetchrow_hashref()) {$filter{$$row{'word'}} = 1}     $sth->finish(); # Очищаем таблицы базы данных $dbh->do('DELETE FROM search_main'); $dbh->do('DELETE FROM search_page'); # Сразу отправляем заголовок браузеру print "Content-type: text/html; charset=windows-1251\n\n"; open (WORDS, '>>', '/var/www/my_sites/cgi-bin/search/words.txt');     flock WORDS, 2;    open (PAGES, '>>', '/var/www/my_sites/cgi-bin/search/words.txt');     flock PAGES, 2;    # Передаем управление процедуре рекурсии     &recursion(); close PAGES; close WORDS;    &update_db;    exit;

2.3. Обновление блока данных

Определим основные действия процедуры:

  • сформировать строку для блока данных страниц, и записать её в файл;
  • обработать контент страницы, подсчитать вес слов и сформировать список;
  • дописать список в блок данных (файл) слов;
sub update_data { # Получаем данные     my ($content, $title, $description, $file) = @_; # Формируем строку блока данных страниц и записываем её в файл     my $line = $i."\t".$url_start.$file."\t".$title."\t".$description;     print PAGES $line, "\n"; # Переводим текст, контент страницы, в нижний регистр     $$content =~tr /A-Z\xA8\xC0-\xDF/a-z\xB8\xE0-\xFF/; # Определяем хеш для подстчета веса слов     my %words;     foreach my $word (split(' ', $$content)) { # Фильтрация слов         next if length $word < 3; # Примечание*         next if exists $filter{$word}; # Формируем хеш слов и их вес         if (exists $words{$word}) {$words{$word}++} else {$words{$word} = 1}     } # Формируем строки блока данных слов     foreach my $word (keys %words) {         my $line = $word."\t".$i."\t".$words{$word};         print WORDS $line, "\n";     } # Обновляем идентификатор страницы     $i++;     return 1; }

*ПРИМЕЧАНИЕ: Цифра 3 как раз и отвечает за размер слова, которые разрешены для индексации

2.4. Обновление базы данных

Данная процедура просто выгружает в базу данных наши два файла, после чего их удаляет

sub update_db { # Загружаем данные     $dbh->do("LOAD DATA INFILE \"/var/www/sites/alfakmv/cgi-bin/search2/words.txt\" INTO TABLE search_main;")               || die "ERROR!!! $DBI::errstr <br>";     $dbh->do("LOAD DATA INFILE \"/var/www/sites/alfakmv/cgi-bin/search2/pages.txt\" INTO TABLE search_page;")               || die "ERROR!!! $DBI::errstr <br>"; # Удаляем временные файлы     unlink '/var/www/sites/alfakmv/cgi-bin/search2/words.txt';     unlink '/var/www/sites/alfakmv/cgi-bin/search2/pages.txt';     return 1; }

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

На этом, с индексацией все. Я даже не рассматриваю варианты обновления данных с помощью команд LOAD DATA и INSERT так как, в таблицу слов вставляется записей не на один порядок больше чем в первом варианте с FULLTEXT (~3000 против ~2000000), а таблицу страниц - ровно такое же количество, правда в гораздо меньшем объеме.

2.5. Скрипт вывода результатов поиска

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

#!/usr/bin/perl # Подключаем основные модули use strict; use warnings; use DBI; use CGI qw(param); use locale; use POSIX qw(locale_h);     setlocale(LC_CTYPE, 'ru_RU.CP1251');     setlocale(LC_ALL, "ru_RU.CP1251"); # Получаем поисковый запрос     my $search = param('search') || undef; # Сразу отправляем заголовок браузеру     print "Content-type: text/html; charset=windows-1251\n\n"; # Форма запроса     print '<form action='' method=get>';     print '<input type=text name=search value="'.($search || '').'">';     print '<input type=submit value=search>';     print '</form>'; # Если запрос пустой, то останавливаем скрипт     unless ($search) {print 'Результатов запроса - 0'; exit} # На всякий случай "чистим" полученные данные     $search =~s /[^\w\s\-]/ /g; # "Сжимаем" пробельные символы     $search =~s /\s+/ /g; # Подключаемся к базе данных     my $dbh = 'DBI'->connect('DBI:mysql:database=search;host=localhost;port=3306', 'root', 'dfkmrbhbz')                     || die $DBI::errstr; # Формируем запрос     my @search = split(' ', $search);     my $sql = "SELECT                     t2.url, t2.title, t2.description, SUM(t1.relevance) AS score                FROM search_main AS t1, search_page AS t2                WHERE t1.word IN ('".join("','",@search)."') AND t1.page = t2.id                GROUP BY t1.id                ORDER BY score DESC                LIMIT 50";     my $sth = $dbh->prepare($sql);     $sth->execute() || die $DBI::errstr; # Устанавливаем счетчик     my $i = 1;     while (my $row = $sth->fetchrow_hashref()) { # Печатаем строку результата         print $i, ' - <a href="', $$row{'url'}, '">', $$row{'title'}, '<a><br>',$$row{'description'}, ' - ', $$row{'score'},'<br><br>';         $i++     }     $sth->finish(); # Отключаемся от базы данных $dbh->disconnect();    if ($i == 1) {print 'Результатов запроса - 0'} else {print 'Результатов запроса - ', $i - 1}    exit;

Вот и все, совсем все, осталось сравнить эти 2 способа.

3. Сравнение

Сравнение проводилось на одном и том же сервере, индексировался один и тот же сайт.

С использованием FULLTEXT Ручная обработка
Объем занимаемых данных (относительно друг друга) 1 0,43
Скорость индексации (относительно друг друга, средняя величина, индексация с помощью команды FULLTEXT) 1 0,97
Скорость поискового запроса к базе данных 0,02 сек (~3 300 записей) 0,31 сек (~2 300 000 записей)

В итоге мы видим, что несмотря на то что объем данных во втором способе гораздо меньше (индекс FULLTEXT довольно объемный), скорость индексации отличается незначительно (если совсем не отличается), а вот запрос для выборки результатов гораздо медленнее. Это связано с гораздо большим количеством записей, и более сложным запросом из двух таблиц. Можно, конечно, во втором способе данные о странице хранить в основной таблице, но при этом объем данных увеличивается в 5-6 раз, а скорость запроса убыстряется всего на 10-15 %, что, впрочем, не актуально. Впрочем, для небольших сайтов оба варианта будут одинаково приемлемы, так как все таки тестирование проводилось на сайте, имеющем более 3000 статичных страниц.

При этом результаты выполнения запроса практически идентичны в обоих случаях, различие было только в порядке вывода (релевантности), что никак не сказывалось на правильности поиска.

Заключение

Итак мы рассмотрели 2 способа организации поиска по сайту. Следует иметь в ввиду, что поиск осуществляется по статичным страницам сайта и никак не предназначен для динамичных сайтов. Естественно были рассмотрены практически идеальные варианты построения сайта:

  • не учтена возможность существования символьных ссылок;
  • не учтены вариации метатегов title и description;
  • не учтены вариации использования SSI;
  • не учтена возможность создания фильтров для определенных файлов (кроме как по расширению);
  • не учтена возможность создания фильтров для определенных вложенных папок (кроме как корневых);
  • может что-то еще не учтено :)...

но, впрочем, скелет дан, нарастить мясо - на совести программиста...



Другие статьи по теме
Категория: Программирование Perl | Добавил: phoinix (2005-11-03) | Автор: Сергей Томулевич (aka Phoinix)
Просмотров: 1955 | Рейтинг: 0.0 |

Комментарии

 

Бесплатный конструктор сайтов - uCoz