我正在构建一个web应用程序,需要分析一个网站,检索和排列其最重要的关键词并展示出来。
获取所有单词、它们的密度并将其显示出来相对简单,但这会导致结果非常不准确(例如,停用词排名非常高)。
基本上,我的问题是:如何使用PHP创建一个关键字分析工具,其结果按照单词重要性正确排序?
我正在构建一个web应用程序,需要分析一个网站,检索和排列其最重要的关键词并展示出来。
获取所有单词、它们的密度并将其显示出来相对简单,但这会导致结果非常不准确(例如,停用词排名非常高)。
基本上,我的问题是:如何使用PHP创建一个关键字分析工具,其结果按照单词重要性正确排序?
最近,我一直在研究这个问题,我会尽力解释我所做的事情。
你需要做的第一件事是过滤并确保编码正确,因此将其转换为UTF-8:
iconv ($encoding, "utf-8", $file); // where $encoding is the current encoding
接下来,您需要去除所有HTML标签、标点符号、符号和数字。在Google上寻找如何执行此操作的函数!
$words = mb_split( ' +', $text );
由于只有1个或2个字符的单词没有任何意义,所以我们将它们全部删除。
为了去除停用词,我们首先需要检测文本所使用的语言。 有几种方法可以实现: - 检查Content-Language HTTP头 - 检查lang=""或xml:lang=""属性 - 检查Language和Content-Language元数据标签 如果以上都没有设置,可以使用外部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句话中的第6个单词更重要。
为了计算单词重要性,在上一步骤的循环内添加以下代码:
$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)));
这个计算并不完美,但它应该能给你合理的结果。
我没有提到我在工具中使用的每一个细节,但我希望它能提供一个良好的关键词分析视角。
N.B. 是的,这是受到今天博客文章关于回答自己问题的启发!
@ 优化 '步骤'
针对这些步骤,我建议采用更为高级的解决方案,将其中一些步骤合并起来。不确定是否需要使用完整的词法分析器,如果你能够完美地设计它以满足你的需求,例如只查找hX等标签内的文本,那么这可能会更好。但是,由于实现起来可能会很麻烦,因此你必须非常认真对待。尽管如此,我还是要说,使用其他语言(PHP支持较差,因为它是一种高级语言)的Flex/Bison解决方案可以大大提升速度。
然而,幸运的是,libxml
提供了出色的功能,正如下面所示,你将最终只需进行多个步骤中的一个。在分析内容之前,设置语言(停用词),缩小NodeList集合,然后从那里开始工作。
<body>
到单独的字段中<head>
和其他地方释放一点内存,例如unset($fullpage);
使用DOM解析器时,应该意识到设置可能会为属性href和src引入进一步的验证,具体取决于库(例如parse_url等)
另一种解决超时/内存消耗问题的方法是调用php-cli(对于Windows主机也适用),并且“开始处理业务”并启动下一个文档。有关更多信息,请参见此问题。
如果向下滚动一点,看看提议的模式 - 初始爬行将只在数据库中放置body(以及在你的情况下的lang),然后运行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);
}
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`
)
grep
。在大量文档上,串行搜索变得冗长。请忽略“无重量”的评论,这是我自己的算法中留下的痕迹,其中我不对关键字进行加权处理,并且只使用一列。 - mschr