Для веб-приложения, которое я создаю, мне нужно проанализировать веб-сайт, получить и ранжировать его наиболее важные ключевые слова и отобразить их.
Получение всех слов, их плотность и отображение их относительно просты, но это дает очень искаженные результаты (например, рейтинг стоп-слов очень высок).
В основном, мой вопрос: как я могу создать инструмент анализа ключевых слов в PHP, который приводит к списку, правильно упорядоченному по значению слова?
Недавно я сам работал над этим, и я попытаюсь объяснить, что я сделал как можно лучше.
Первое, что вам нужно сделать, это фильтр, убедитесь, что кодировка верна, поэтому конвертировать в UTF-8:
iconv ($encoding, "utf-8", $file); // where $encoding is the current encoding
После этого вам нужно снять все теги html, знаки препинания, символы и цифры. Посмотрите на функции, как это сделать в Google!
$words = mb_split( ' +', $text );
Любое слово, состоящее из 1 или 2 символов, не будет иметь никакого значения, поэтому мы удалим все из них.
Чтобы удалить стоп-слова, нам сначала нужно определить язык. Есть несколько способов, которыми мы можем это сделать: – Проверка HTTP-заголовка Content-Language – Проверка атрибута lang = "" или xml: lang = "" – Проверка тегов метаданных языка и контента-языка Если ни один из них не установлен, вы можете использовать внешний API, такой как AlchemyAPI .
Вам понадобится список стоп-слов на язык, который можно легко найти в Интернете. Я использовал этот: http://www.ranks.nl/resources/stopwords.html
Чтобы подсчитать количество вхождений в слово, используйте следующее:
$uniqueWords = array_unique ($keywords); // $keywords is the $words array after being filtered as mentioned in step 3 $uniqueWordCounts = array_count_values ( $words );
Теперь проведите цикл $ uniqueWords и вычислите плотность каждого слова следующим образом:
$density = $frequency / count ($words) * 100;
Слово протуберанца определяется положением слов в тексте. Например, второе слово в первом предложении, вероятно, более важно, чем шестое слово в 83-м предложении.
Чтобы вычислить его, добавьте этот код в тот же цикл из предыдущего шага:
$keys = array_keys ($words, $word); // $word is the word we're currently at in the loop $positionSum = array_sum ($keys) + count ($keys); $prominence = (count ($words) - (($positionSum - 1) / count ($keys))) * (100 / count ($words));
Очень важная часть – определить, где находится слово – в названии, описании и многом другом.
Во-первых, вам нужно захватить заголовок, все теги метаданных и все заголовки, используя что-то вроде DOMDocument или PHPQuery ( не пытайтесь использовать регулярное выражение!). Затем вам нужно проверить, в том же цикле, содержат ли они слова.
Последний шаг – рассчитать значение ключевых слов. Для этого вам нужно взвесить каждый фактор – плотность, известность и контейнеры. Например:
$value = (double) ((1 + $density) * ($prominence / 10)) * (1 + (0.5 * count ($containers)));
Этот расчет далек от совершенства, но он должен дать вам достойные результаты.
Я не упоминал каждую деталь того, что я использовал в своем инструменте, но я надеюсь, что он предлагает хороший обзор анализа ключевых слов.
NB Да, это было вдохновлено сегодняшним blogpost о том, как отвечать на ваши вопросы!
Одна вещь, которая отсутствует в вашем алгоритме, – это документально-ориентированный анализ (если вы не упустили его намеренно по какой-то причине).
Каждый сайт построен на наборе документов. Подсчет частот слов для всех и каждого документа предоставит вам информацию о покрытии слов. Словами, которые встречаются в большинстве документов, являются слова остановки. Слова, относящиеся к ограниченному числу документов, могут составлять кластер документов по определенной теме. Количество документов, относящихся к определенной теме, может повысить общую значимость слов этой темы или, по крайней мере, предоставить дополнительный коэффициент, который будет учитываться в ваших формулах.
Возможно, вы могли бы воспользоваться заранее сконфигурированным классификатором, который содержит категории / темы и ключевые слова для каждого из них (эта задача может быть частично автоматизирована путем индексации существующих общедоступных иерархий категорий, вплоть до Википедии, но это не простая задача). Затем вы можете включать категории в анализ.
Кроме того, вы можете улучшить статистику путем анализа уровня предложения. То есть, имея частоты того, как часто слова встречаются в одном и том же предложении или фразе, вы можете обнаружить клише и дубликаты и исключить их из статистики. Но, боюсь, это не так легко впишется в чистый PHP.
Это, вероятно, небольшой вклад, но, тем не менее, я упомянул об этом.
В определенной степени вы уже смотрите на контекст слова, используя положение, в котором оно помещено. Вы могли бы добавить еще один фактор к этому, ранжируя слова, которые появляются в заголовке (H1, H2 и т. Д.) Выше слов внутри абзаца, выше, чем, возможно, слова в маркированном списке и т. Д.
Обнаружение стоп-слов на основе языка может работать, но, возможно, вы могли бы использовать кривую колокола, чтобы определить, какие частоты / плотности слов слишком экстравагантны (например, полоса ниже 5% и верхняя 95%). Затем примените оценку остальных слов. Это не только предотвращает стоп-слова, но и злоупотребляет ключевыми словами, по крайней мере, в теории 🙂
@ уточнение «Шаги»
Что касается выполнения этих многочисленных шагов, я бы пошел с немного расширенным решением, скрепляя некоторые из ваших шагов вместе.
Не уверен, но лучше ли полноценный лексер , если вы прекрасно его спланируете в соответствии с вашими потребностями, например, смотрите только текст в формате hX и т. Д. Но вы должны иметь в виду бизнес-бизнес, поскольку он может стать головной болью для реализации. Хотя я остановлюсь и скажу, что решение Flex / Bison на другом языке (PHP предлагает низкую поддержку, поскольку это такой язык высокого уровня) будет «безумным» ускорением.
Однако, к счастью, libxml
предоставляет великолепные функции, и, как показано ниже, вы будете иметь несколько шагов в одном. Перед тем, как вы анализируете содержимое, язык установки (стоп-слова), уменьшите набор NodeList и работайте там.
<body>
в отдельное поле <head>
и других, например, например. unset($fullpage);
с unset($fullpage);
При использовании парсеров DOM следует понимать, что настройки могут вводить дополнительную проверку атрибутов href и src, в зависимости от библиотеки (например, parse_url и нравится)
Еще один способ получить от тайм-аута / потребления памяти – вызвать php-cli (также работает для хоста Windows) и «заняться бизнесом» и запустить следующий документ. См. Этот вопрос для получения дополнительной информации.
Если вы немного прокрутите список, посмотрите на предлагаемую схему – первоначальный обход поместил бы только тело в базу данных (и, кроме того, занесен в ваш случай), а затем запустил cron-скрипт, заполнив ft_index, используя следующую функцию
function analyse() { ob_start(); // dont care about warnings, clean ob contents after parse $doc->loadHTML("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html;charset=UTF-8\"/></head><body><pre>" . $this->html_entity_decode("UTF-8") . "</pre></body>"); ob_end_clean(); $weighted_ft = array('0'=>"",'5'=>"",'15'=>""); $includes = $doc->getElementsByTagName('h1'); // relevance wieght 0 foreach ($includes as $h) { $text = $h->textContent; // check/filter stopwords and uniqueness // do so with other weights as well, basically narrow it down before counting $weighted_ft['0'] .= " " . $text; } // relevance wieght 5 $includes = $doc->getElementsByTagName('h2'); foreach ($includes as $h) { $weighted_ft['5'] .= " " . $h->textContent; } // relevance wieght 15 $includes = $doc->getElementsByTagName('p'); foreach ($includes as $p) { $weighted_ft['15'] .= " " . $p->textContent; } // pseudo; start counting frequencies and stuff // foreach weighted_ft sz do // foreach word in sz do // freqency / prominence } function html_entity_decode($toEncoding) { $encoding = mb_detect_encoding($this->body, "ASCII,JIS,UTF-8,ISO-8859-1,ISO-8859-15,EUC-JP,SJIS"); $body = mb_convert_encoding($this->body, $toEncoding, ($encoding != "" ? $encoding : "auto")); return html_entity_decode($body, ENT_QUOTES, $toEncoding); }
Вышеупомянутый класс, похожий на вашу базу данных, которая имеет поле «тело» страницы, загруженное в prehand.
Опять же, что касается обработки базы данных, я закончил тем, что вставлял вышеприведенный результат в полнотекстовую помеченную табличную колонку, так что будущие поиски бы выглядели без видимых на вид. Это огромное преимущество для двигателей db .
Примечание по полнотекстовому индексированию:
При работе с небольшим количеством документов можно, чтобы полнотекстовая поисковая система напрямую просматривала содержимое документов с каждым запросом, стратегию, называемую последовательным сканированием. Вот что делают некоторые рудиментарные инструменты, такие как grep, при поиске.
Ваш алгоритм индексирования отфильтровывает некоторые слова, хорошо. Но они перечислены тем, сколько веса они несут – есть стратегия, чтобы придумать здесь, поскольку полнотекстовая строка не переносит приведенные веса. Вот почему в этом примере дается базовая стратегия по разбиению строк на 3 разные строки.
После ввода в базу данных столбцы должны тогда напоминать это, поэтому схема может быть такой, где мы будем поддерживать весовые коэффициенты – и все же предлагаем метод сверхбыстрого запроса
CREATE TABLE IF NOT EXISTS `oo_pages` ( `id` smallint(5) unsigned NOT NULL AUTO_INCREMENT, `body` mediumtext COLLATE utf8_danish_ci NOT NULL COMMENT 'PageBody entity encoded html', `title` varchar(31) COLLATE utf8_danish_ci NOT NULL, `ft_index5` mediumtext COLLATE utf8_danish_ci NOT NULL COMMENT 'Regenerated cron-wise, weighted highest', `ft_index10` mediumtext COLLATE utf8_danish_ci NOT NULL COMMENT 'Regenerated cron-wise, weighted medium', `ft_index15` mediumtext COLLATE utf8_danish_ci NOT NULL COMMENT 'Regenerated cron-wise, weighted lesser', `ft_lastmodified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT 'last cron run', PRIMARY KEY (`id`), UNIQUE KEY `alias` (`alias`), FULLTEXT KEY `ft_index5` (`ft_index5`), FULLTEXT KEY `ft_index10` (`ft_index10`), FULLTEXT KEY `ft_index15` (`ft_index15`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_danish_ci;
Можно добавить такой индекс:
ALTER TABLE `oo_pages` ADD FULLTEXT ( `named_column` )
Дело в том, что вы обнаруживаете язык, а затем выбираете свою базу данных с этого момента, это функция, которую я сам забыл, но ее отличная – и по книге! Так что для ваших усилий и ответа:
Кроме того, имейте в виду, что есть не только тег title, но и атрибуты заголовка anchor / img. Если по какой-то причине ваша аналитика переходит в состояние , подобное пауку , я бы предложил объединить ссылку ссылки ( <a>
) title и textContent с целевой страницей <title>
Я бы рекомендовал вместо того, чтобы изобретать колесо, вы используете Apache SoIr для поиска и анализа. У него есть почти все, что вам может понадобиться, в том числе обнаружение стоп-слова для 30 + языков (насколько я помню, возможно, еще больше) и делать множество вещей с данными, хранящимися в нем.