Эта тема на forum.dklab.ru


Антон Макаренко: Создание индекса HTML-страницы
Пытаюсь написать, разминки ради, простого поискового паука для небольшого сайта.
Алгоритм работы примерно такой:
- собираем URL, которые надо проиндексировать
- паук ходит по этим URL, получает HTML-содержимое
- содержимое разбивается на слова по некоторым критериям
- слова загоняются в таблицу БД, проставляется соответствие слов URL-ам
- поиск будет осуществляться по таблицам индекса в БД, возвращяться будут URL и title страниц

Собрать URL и получить содержимое не представляется трудным, поэтому опустим этот процесс. Со структурой БД пока точно не определился, поэтому также приводить ее не буду.
Интересуют ваши комментарии к скрипту разбора HTML-страницы на слова:
<?php
// config
define('MIN_WORD_LENGTH', 3);
define('MAX_WORD_LENGTH', 15);

$contents='some html code';

// remove html entities
$contents=preg_replace('/&.*?;/i', ' ', $contents);

// obtain title
preg_match('/<title>(.*?)<\/title>/is', $contents, $title);
$title=strip_html($title[1]);

// strip html (based on php manual)
$contents=strip_html($contents);

// remove punctuation and other stuff
$contents=preg_replace('/(\s\W+)|(\W+\s)/is', ' ', $contents);
// remove "short" words
$contents=thorough_preg_replace('/\s\S{1,'.(MIN_WORD_LENGTH-1).'}\s/is', ' ', $contents);
// remove "long" words
$contents=thorough_preg_replace('/\s\S{'.(MAX_WORD_LENGTH+1).',}\s/is', ' ', $contents);
// repeating whitespace
$contents=preg_split('/\s+/is', $contents);

// create relevancy index
$index=array();
while (count($contents)>0)
{
$word=array_shift($contents);
if (!empty($word))
{
if (!isset($index[$word]))
$index[$word]=1;
else
$index[$word]++;
}
}
arsort($index);
// now index is an associative array of 'word'=>relevancy


// ... to be continued ...

/**
* Loop preg_replace() until no matches of regexp will be found
*
* @param string $pattern
* @param string $replacement
* @param string $subject
* @return string
*/
function thorough_preg_replace($pattern, $replacement, $subject)
{
do
{
$subject=preg_replace($pattern, $replacement, $subject);
} while (preg_match($pattern, $subject)>0);
return $subject;
}

/**
* Remove html and other evil stuff from a text. Regexps taken from php.net
*
* @param string $contents
* @return mixed
*/
function strip_html($contents)
{
return preg_replace(
array(
"'<script[^>]*?>.*?</script>'si",
"'<[\/\!]*?[^<>]*?>'si",
"'([\r\n])[\s]+'",
"'&#(\d+);'e"), ' ', $contents);
}
?>
В частности, возник вопрос с издержками производительности thorough_preg_replace(). Можно ли обойтись без этой функции и при этом в одну итерацию "почистить" текст от "коротких" и "длинных" слов?
И еще preg_replace('/(\s\W+)|(\W+\s)/is', ' ', $contents); вместе с "висячей" пунктуацией отрезает часть слов, если текст в multibyte-кодировке (на несоответствие количеству символов пока смотрю сквозь пальцы).
Заранее благодарю за советы.
Юрий Насретдинов:
Кстати содержимое <style> тоже не надо индексировать :)
Антон Макаренко:
Юpий Насрeтдинов
Спасибо за совет со стилями.

Написал небольшой класс, который анализирует HTML/XHML-документ и составляет индекс.
Сделано:
- получить содержимое по URL/URI
- получить title документа
- выдрать слова для индекса, составить релевантность
- в индекс также включаются значение title документа, значения alt и title внутри тегов

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

<?php
/**
* Parses a html/xhtml document and makes words search index
*
* Requires php ini setting url_fopen=On
* Requires php_mbstring extension
*
* Currently it is not recommended for multibyte strings
* And it is recommended to parse valid XHTML
*
* @version 0.2
* @author Anton Makarenko <php at ripfolio dot com>
*/
class HTML_Search_Indexer
{
var $encoding;
var $html_content;
var $page_index;
var $page_title;

var $min_word_length=3;
var $max_word_length=15;

var $base_url;

/**
* Set encoding and base url
*
* @param string $encoding
* @param string $base_url
* @return HTML_Search_Indexer
*/
function HTML_Search_Indexer($encoding, $base_url=null)
{
$this->encoding=$encoding;
if (empty($base_url))
$this->base_url='http://'.$_SERVER['HTTP_HOST'];
else
$this->base_url=$base_url;
}

/**
* Get contents of url and create search words index
*
* File paths instead of url are conditionally supported
* It returns bool, but class attributes are set.
*
* @param string $url
* @return bool
*/
function parse_by_url($url)
{
// get contents
$this->html_content=@file_get_contents($url);
if (empty($this->html_content))
return false;
// determine page title
if (0<preg_match('/<title>(.*?)<\/title>/is', $this->html_content, $matches))
$this->page_title=$matches[1];
else
// page without title will not be parsed
return false;
// pre-parse contents
$this->page_index=$this->html_content;
$this->page_index=$this->pre_parse($this->page_index);
// create page index
$this->page_index=$this->cleanup_text($this->strip_html($this->page_index));
// add page title to page index
$this->page_index.=' '.$this->cleanup_text($this->strip_html($this->page_title));
// add elements titles, alts to page index
if (0<preg_match_all('/<.*?(title|alt)="(.*?)".*?>/is', $this->html_content, $matches))
$this->page_index.=' '.$this->cleanup_text($this->strip_html(implode(' ', $matches[2])));
// transform index to array of words
$contents=preg_split('/\s+/is', $this->page_index);
// create relevancy index
$index=array();
while (count($contents)>0)
{
$word=array_shift($contents);
if (!empty($word))
{
if (!isset($index[$word]))
$index[$word]=1;
else
$index[$word]++;
}
}
arsort($index);
$this->page_index=$index;
return true;
}

/**
* The same as parse_url, but uses uri and adds base url
*
* @param string $uri
* @return bool
*/
function parse_by_uri($uri)
{
return $this->parse_by_url($this->base_url.$uri);
}

/**
* Method is called before creating page index. Actually, does nothing.
*
* @param string $contents
* @return string
*/
function pre_parse($contents)
{
// reserved for derived classes
return $contents;
}

/**
* Remove html and other evil stuff from a text. Regexps taken from php.net
*
* @param string $contents
*/
function strip_html($contents)
{
return trim(preg_replace(
array(
"'<script[^>]*?>.*?</script>'si" // scripts
,"'<style[^>]*?>.*?</style>'si" // styles
,"'<[\/\!]*?[^<>]*?>'si" // other html tags
,"'([\r\n])[\s]+'" // whitespace
,"'&(#\d+|[a-z]+);'e" // html entites
),' ', $contents));
}

/**
* Cleanup text for better indexing
*
* @param string $contents
* @return string
*/
function cleanup_text($contents)
{
// remove "hanging" punctuation
$contents=preg_replace('/(\s+\W+)|(\W+\s+)/is', ' ', $contents); // breaks multibyte characters (!)
// remove "short" words
$contents=$this->thorough_preg_replace('/\s\S{1,'.($this->min_word_length-1).'}\s/is', ' ', $contents);
// remove "long" words
$contents=$this->thorough_preg_replace('/\s\S{'.($this->max_word_length+1).',}\s/is', ' ', $contents);
// make all words lowercase
$contents=mb_strtolower($contents, $this->encoding);
return $contents;
}

/**
* Loop preg_replace() until no matches of regexp will be found
*
* @param string $pattern
* @param string $replacement
* @param string $subject
* @return string
*/
function thorough_preg_replace($pattern, $replacement, $subject)
{
do
{
$subject=preg_replace($pattern, $replacement, $subject);
} while (preg_match($pattern, $subject)>0);
return $subject;
}
}
?>

Эта тема на forum.dklab.ru