Xpoint
   [напомнить пароль]

Поиск по сайту

Более-менее серьезный сайт без поиска немыслим. Но реализация поиска может представлять собой серьезную проблему для начинающего программиста.

В MySQL версии 3.23.23 появился "полнотекстовой поиск" (FULLTEXT). Это тип индексов, специально ориентированный на "человеческий" поиск по тексту.

Вот пример MySQL-декларации:

CREATE TABLE mytable (
  content mediumtext NOT NULL,
  FULLTEXT KEY (content)
) TYPE=MyISAM;

Выборка делается с помощью конструкции MATCH...AGAINST:

SELECT * FROM mytable WHERE MATCH (content) AGAINST ('что ищем')

Подробнее см. в руководстве по MySQL

В принципе, поиск вроде как уже можно делать – делаем поиск по таблице, где у нас хранятся страницы – и все будет... Но! Во-первых, кроме нужного текста у нас в базе также записаны перемежающие его теги html-разметки, которые нам совершенно не надо находить. Во-вторых, наши страницы могут формироваться довольно сложным образом на основе информации из нескольких таблиц и даже текстовых файлов. Или наоборот - просто быть текстовыми файлами.

Есть предложение написать свой простенький мини-"яндекс" — паука, обходящего сайт по ссылкам, выбирающего контент из страниц и записывающего его в отдельную таблицу, по которой мы уже и будем делать поиск, как описано выше.

При написании я принял следующие ограничения: сайт построен на человекопонятных URL, все адреса оканчиваются на /.

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

Таблица для поискового индекса:

CREATE TABLE search_index (
  uri varchar(255) NOT NULL default '',
  title text NOT NULL,
  description text NOT NULL,
  text mediumtext NOT NULL,
  UNIQUE KEY (uri),
  FULLTEXT KEY (text)
) TYPE=MyISAM;

Паук:

<?php
$indexed=array(); // массив, где мы отмечаем уже проиндексированные страницы
// $uri - стартовый адрес
function spider($uri='/', $depth=0){
  global $indexed;
  // URI уже проиндексирован, пропускаем
  if($indexed[$uri]) return;
  // удаляем из кэша старую версию рассматриваемого URI
  mysql_query("delete from search_index where uri='".mysql_real_escape_string($uri)."'");
  // формируем URL для вызова страницы сайта по HTTP
  $url='http://'.$_SERVER["HTTP_HOST"].$uri;
  // берем мета-теги. Для упрощения работы у нас сайт заголовок записывает не только в <title>, то и <meta name="title">
  $meta=@get_meta_tags($url);
  // либо ошибка, либо не документ. Пропускаем
  if(!$meta) return;
  // тут еще один момент для оптимизации. Мы 2 раза запрашиваем страницу, т. к. get_meta_tags не может работать со строкой, только с файлом. Можно один раз вызвать страницу по HTTP и сохранить в локальный файл.
  $content=file_get_contents($url); 
  // чистим контент от тегов и html-entity (и заодно от повторяющихся пробелов)
  $text=preg_replace('/(\s+)|(&[a-zA-Z0-9#]+;)/', ' ', strip_tags($content));
  // записываем данные по URI в индекс
  mysql_query("insert into search_index set
       uri='".mysql_real_escape_string($uri)."',
       title='".mysql_real_escape_string($meta['title'])."',
       description='".mysql_real_escape_string($meta['description'])."',
       text='".mysql_real_escape_string($meta['title']."\n".$page['description']."\n".$text)."'");
  // отмечаем в массиве, что этот URI мы проиндексировали
  $indexed[$uri]=true;
  // теперь делаем обход ссылок
  // тут опять поле для оптимизаторства: мы здесь удаляем все теги кроме <A> и выбираем все, что идет после href= и заключено в двойные или одинарные кавычки
  if($depth<3){
  // если мы не слишком глубоко углубляемся в рекурсию
    $links=split("href=",strip_tags($content,'<a>'));
    foreach($links as $link){
      $f=substr($link,0,1);
      $link=substr($link,1);
      if($f=='"' || $f=="'"){
        $link=substr($l,0,strpos($link,$f));
        // пропускаем ссылки, начинающиеся на http://, javascript: и пр.
        if(strpos($link,'http://')===0 || strpos($link,'https://')===0 || strpos($link,'javascript:')===0 || strpos($link,'mailto:')===0 || strpos($link,'#')===0) continue;
      }else{
        //пропускаем ссылки без кавычек
        continue;
      }
      // приводим ссылку к абсолютному виду
      // тут тоже можно пооптимизировать
      $diez=strpos($link,'#');
      if($diez!==null){
        $link=substr($link,0,$diez);
      }
      if(substr($link,0,1)!='/'){
        $link=$uri.$link;
      }
      // рекурсивный вызов паука на очередную ссылку
      spider($link, $depth+1);
    }
  }
}
// начальный вызов паука на корень сайта. Можно начинать его работу, например, и с карты сайта.
spider('/');
// оптимизируем таблицу индекса, а то она знатно раздувается.
db_query('optimize table search_index'); 
?>

Этот скрипт-паук можно запускать либо самостоятельно после обновления сайта, либо поставить в cron.

Powered by POEM™ Engine Copyright © 2002-2005