БлогNot. Делаем "живой" поиск по заголовкам сайта

Делаем "живой" поиск по заголовкам сайта

Пусть заголовки статей нашего сайта хранятся в некоторой таблице mySQLi, достаточно, чтобы для каждой статьи в записи базы имелось целочисленное поле id с идентификатором записи, по которому потом можно сформировать ссылку для доступа к статье и строковое поле name с заголовком записи.

Мы хотим, чтобы в текстовое поле можно было вводить произвольные фрагменты текста, а скрипт показывал кликабельный список статей, подходящих под запрос (см. скрин, фрагмент экрана):

живой поиск в работе, скриншот, фрагмент экрана
живой поиск в работе, скриншот, фрагмент экрана

Для решения задачи нам понадобится немного PHP и немного Javascript, подгружать JQuery для уменьшения объёма трафика от скрипта не будем. Предполагается, что база и все файлы сохранены в кодировке Юникода UTF-8, а поиск должен работать для латиницы и кириллицы независимо от регистра. В коде использован тот же подход, что и при создании Sitemap.

Напишем файл index.php, куда включим форму и яваскрипт. На самом деле, содержимое скрипта и формы может быть вставлено куда-то в нужный модуль сайта.

Файл index.php
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Живой поиск по заголовкам</title>
</head>
<body>

<script>
function showResult (str) {
 str = str.replace(/[\&\+]/g,'');
 if (str.length==0) {
  document.getElementById("livesearch").innerHTML='';
  document.getElementById("livesearch").style.border="0px";
  return;
 }
 var xmlhttp=new XMLHttpRequest();
 xmlhttp.onreadystatechange=function() {
  if (this.readyState==4 && this.status==200) {
   document.getElementById("livesearch").innerHTML=this.responseText;
   document.getElementById("livesearch").style.border="1px solid #ccc";
  }
 }
 xmlhttp.open("GET","livesearch.php?q="+str,true);
 xmlhttp.send();
}
</script>

<form>
 <input type="text" size="30" onkeyup="showResult(this.value)">
 <div id="livesearch"></div>
</form>

<p><a href="updatelinks.php">Переиндексировать поиск</a></p>

</body>
</html>

Легко увидеть, что этот файл вызывает модуль livesearch.php, передавая ему наш запрос. Конечно же, этот модуль не будет каждый раз осуществлять реальный поиск по сайту, а подгрузит ранее сгенерированный файл links.xml, содержащий нужную информацию.

Файл livesearch.php
<?php
 $xmlDoc=new DOMDocument();
 $xmlDoc->load("./links.xml");
 $x=$xmlDoc->getElementsByTagName('link');
 $q=$_GET["q"];
 if (mb_strlen($q.'UTF-8') > 0) {
  $hint=''; //подсказка
  for($i=0; $i<($x->length); $i++) {
   $y=$x->item($i)->getElementsByTagName('title');
   $z=$x->item($i)->getElementsByTagName('url');
   if ($y->item(0)->nodeType==1) {
    $url = $z->item(0)->childNodes->item(0)->nodeValue;
    $anchor = $y->item(0)->childNodes->item(0)->nodeValue;
    if (!empty($anchor) and !empty($q) and mb_stristr($anchor,$q,false,'UTF-8')) { //найти вхождения
     if (empty($hint)) {
      $hint = '<a href="'.$url.'" target="_blank">'.$anchor.'</a>';
     }  
     else {
      $hint .= '<br><a href="'.$url.'" target="_blank">'.$anchor.'</a>';
     }
    }
   }
  }
 }
 if (empty($hint)) {
  $response = 'не найдено';
 } 
 else {
  $response = $hint;
 }
 echo $response;
?>

Сам файл links.xml будет иметь простую структуру:

<pages>
<link>
 <title>Заголовок страницы 1</title>
 <url>URL_страницы_1</url>
</link>
...
<link>
 <title>Заголовок страницы N</title>
 <url>URL_страницы_N</url>
</link>
</pages>

Он может генерироваться отдельным скриптом, который назовём updatelinks.php. В этом скрипте должно быть правильно прописано подключение к базе, основной URL Вашего сайта и, вероятнее всего, будет изменён блок кода "Формируем одну непустую ссылку". Тем не менее, исходник несложен для восприятия и поддаётся вашему переписыванию.

Файл updatelinks.php
<!DOCTYPE html>
<html lang="ru">
 <head>
  <meta charset="utf-8">
  <title>Update links.xml</title>
 </head>
 <body>

<?php
 error_reporting(E_ALL);
 //Контроль времени
 $time = 0;
 if (file_exists('./time.dat') === false) {
  file_put_contents ('./time.dat',time()) or die ("Не могу создать метку времени, проверьте права!");
 }
 else {
  $time = file_get_contents ('./time.dat');
  if ($time === false) die ("Не могу прочитать метку времени, проверьте правильность кода!");
 }
 $delay = time() - intval($time);
 if ($delay <= 60*60) { //Можно переиндексировать не чаще раза в час
  echo '<p>Извините, переиндексировать базу можно через '.(floor((60*60 - $delay)/60)+1).' мин.</p>'."\n".
   '<p><a href="index.php">Вернуться к поиску</a></p></body></html>';
  exit;
 }
 else {
  file_put_contents ('./time.dat',time()) or die ("Не могу создать метку времени, проверьте права!");
 }
 //Данные для работы с MySQLi
 define ('DB_HOST','localhost'); //хост
 define ('DB_LOGIN','root'); //логин 
 define ('DB_PASS',''); //пароль 
 define ('DB_NAME','database'); //имя БД
 define ('DB_CODEPAGE','utf8'); //кодовая страница БД
 define ('TB_NAME','datatable'); //имя таблицы
 //Данные для генерации XML
 define ('FILE_NAME','links.xml'); //имя создаваемого файла .xml с данными
 define ('MAP_URL','http://blog.kislenko.net/'); //общий URL, с '/' В конце!
 define ('MAP_HEAD','<pages>'."\n");  //header файла .xml
 define ('MAP_FOOT','</pages>'); //footer файла .xml
 //Выполняемый код:
 $link=mysqli_connect(DB_HOST, DB_LOGIN, DB_PASS, DB_NAME);
 if (mysqli_connect_errno()) {
  printf ("<p>Не удалось подключиться: %s</p>\n", mysqli_connect_error());
  exit ();
 }
 if (!mysqli_set_charset($link, DB_CODEPAGE)) {
  printf ("<p>Ошибка при загрузке набора символов: ".DB_CODEPAGE.": %s</p>\n", mysqli_error($link));
  exit ();
 }
 $sql = 'select id, name from '.TB_NAME.' order by id asc'; //Формируем запрос SQL
 $result=mysqli_query($link,$sql); //Выполняем его
 $cnt = 0; //Будем считать пропущенные записи (без заголовка)
 $str = '';
 if ($result and mysqli_num_rows($result)) {
  while ($row=mysqli_fetch_assoc ($result)) {
   $name = trim($row['name']);
   if (!empty($name)) {
    //Формируем одну непустую ссылку:
    $url = MAP_URL.'show.php?id='.$row['id'];
    $name = str_replace('&',' ',$name); //"Бродячий" амперсанд из заголовка убьёт XML!
    $str .= "<link>\n <title>$name</title>\n <url>$url</url>\n</link>\n"; 
   }
   else {
    $cnt++;
   }
  }
  mysqli_free_result ($result);
 }
 else {
  $msg = mysqli_error ($link);
  echo "<p>Получено 0 строк по запросу: [$sql]</p>";
 }
 $result_str = MAP_HEAD.$str.MAP_FOOT;
 $res = file_put_contents (FILE_NAME,$result_str);
 if ($res === false) {
  echo '<p>Ошибка записи файла '.FILE_NAME.', проверьте права</p>'."\n";
 }
 else {
  echo '<p>Файл '.FILE_NAME.' успешно создан, пропущено записей без заголовка: '.$cnt.'</p>'."\n";
 }
 mysqli_close ($link);
 echo '<p><a href="index.php">Вернуться к поиску</a></p>';
?>
 </body>
</html>

Так как мы не защищаем операцию "переиндексирования" сайта, разрешим выполнять её не чаще раза в час, а последнюю отметку времени сохраним в автосоздаваемом файле time.dat.

Вот что вышло у меня, это действительно работает как "живой индекс" по моему блогу. Конечно, список может "вываливаться" с задержкой на секунду-другую, если объём файла links.xml получился достаточно большим.

 Открыть скрипт в работе

В том виде, что скрипт представлен сейчас, некоторые "критичные" для используемых технологий символы, такие как "&" и "+", участвовать в поиске не будут. Амперсанд мы просто убираем из данных XML, иначе не будет работать DOMDocument::load.

21.06.2021, 15:34 [253 просмотра]


теги: список php javascript textprocessing сервис поиск xml время