对于我正在构建的 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 元数据标签 如果这些都没有设置,您可以使用像AlchemyAPI这样的外部 API 。
您将需要每种语言的停用词列表,可以在网络上轻松找到。我一直在使用这个: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)));
这个计算远非完美,但它应该会给你不错的结果。
我没有提到我在工具中使用的每一个细节,但我希望它可以为关键字分析提供一个很好的视角。
注意:是的,这是受今天关于回答您自己问题的博文的启发!
您的算法中缺少的一件事是面向文档的分析(如果您出于某种原因没有故意省略它)。
每个站点都建立在一个文档集上。计算所有文档的词频将为您提供有关词覆盖率的信息。大多数文档中出现的词都是停用词。特定于有限数量文档的词可以形成关于特定主题的文档集群。与特定主题相关的文档数量可以提高该主题单词的整体重要性,或者至少提供一个额外的因素来计算在您的公式中。
也许,您可以从包含类别/主题和每个主题的关键字的预配置分类器中受益(此任务可以通过索引现有的公共类别层次结构部分自动化,直至 Wikipedia,但这本身并不是一项简单的任务)。然后,您可以将类别纳入 analisys。
此外,您可以通过对句子级别的分析来改进统计数据。也就是说,具有单词在同一个句子或短语中出现的频率,您可以发现陈词滥调和重复,并从统计数据中消除它们。但是,恐怕这在纯 PHP 中不容易实现。
这可能是一个很小的贡献,但我还是会提到它。
在某种程度上,您已经通过使用单词的位置来查看单词的上下文。您可以通过将出现在标题(H1、H2 等)中的单词排名高于段落内的单词、可能高于项目符号列表中的单词等来添加另一个因素。
根据一种语言检测停用词可能会起作用,但也许您可以考虑使用钟形曲线来确定哪些词频率/密度过于奢侈(例如,去除下 5% 和上 95%)。然后将评分应用于剩余的单词。它不仅可以防止停用词,还可以防止关键词滥用,至少在理论上是这样的:)
@提炼'步骤'
关于执行这些许多步骤,我会采用一些“增强”解决方案,将您的一些步骤缝合在一起。
不确定,如果你完全设计一个完整的词法分析器来满足你的需求,例如只在 hX 等中寻找文本,那么它是否更好。但是你必须是认真的,因为它可能是一个令人头疼的实现。虽然我会指出我的观点并说另一种语言的Flex / Bison解决方案(PHP 提供的支持很差,因为它是一种高级语言)将是一个“疯狂”的速度提升。
但是,幸运的是,libxml
它提供了出色的功能,如下所示,您最终将拥有多个步骤。在分析内容之前,设置语言(停用词),缩小 NodeList 集并从那里开始工作。
<body>
到单独的字段中<head>
,例如。unset($fullpage);
在使用 DOM 解析器时,应该意识到设置可能会引入对属性 href 和 src 的进一步验证,具体取决于库(例如 parse_url 和 likes)
解决超时/内存消耗问题的另一种方法是调用 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);
}
以上是一个类,类似于您的数据库,其中预先加载了页面“body”字段。
同样,就数据库处理而言,我最终将上述解析结果插入到带有全文标记的表格列中,以便将来的查找顺利进行。这对 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 title 属性。如果由于某种原因您的分析进入类似蜘蛛的状态,我建议将参考链接 ( <a>
)标题和textContent与目标页面结合起来<title>
我建议您使用 Apache SoIr 进行搜索和分析,而不是重新发明轮子。它几乎拥有您可能需要的一切,包括 30 多种语言的停用词检测 [据我所知,可能更多] 并使用存储在其中的数据进行大量工作。