PHP中的关键词分析

24

我正在构建一个web应用程序,需要分析一个网站,检索和排列其最重要的关键词并展示出来。

获取所有单词、它们的密度并将其显示出来相对简单,但这会导致结果非常不准确(例如,停用词排名非常高)。

基本上,我的问题是:如何使用PHP创建一个关键字分析工具,其结果按照单词重要性正确排序?


1
你可以尝试查看类似于Lucene(关键字分析器)的东西。 - CD001
针对停用词问题,我使用WordNet数据库。我还使用此控件来可视化密度+关系。http://www.codeproject.com/Articles/342715/Plotting-Circular-Relationship-Graphs-with-Silverl - Leblanc Meneses
看起来非常有前途,谢谢! - Jeroen
5个回答

53

最近,我一直在研究这个问题,我会尽力解释我所做的事情。

步骤

  1. 过滤文本
  2. 分割成单词
  3. 删除二字单词和停用词
  4. 确定单词频率和密度
  5. 确定单词突出度
  6. 确定单词容器
    1. 标题
    2. 元描述
    3. URL
    4. 标题
    5. 元关键词
  7. 计算关键词价值

1. 过滤文本

你需要做的第一件事是过滤并确保编码正确,因此将其转换为UTF-8:

iconv ($encoding, "utf-8", $file); // where $encoding is the current encoding

接下来,您需要去除所有HTML标签、标点符号、符号和数字。在Google上寻找如何执行此操作的函数!

2. 分割成单词

$words = mb_split( ' +', $text );

3. 去除2个字符的单词和停用词

由于只有1个或2个字符的单词没有任何意义,所以我们将它们全部删除。

为了去除停用词,我们首先需要检测文本所使用的语言。 有几种方法可以实现: - 检查Content-Language HTTP头 - 检查lang=""或xml:lang=""属性 - 检查Language和Content-Language元数据标签 如果以上都没有设置,可以使用外部API,例如AlchemyAPI

每种语言都需要一个停用词列表,这可以在网上轻松获得。 我一直在使用这个:http://www.ranks.nl/resources/stopwords.html

4. 确定单词频率和密度

要计算每个单词的出现次数,请使用以下方法:

$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;

5. 确定单词重要性

单词的重要性由单词在文本中的位置决定。 例如,第一句话中的第二个单词可能比第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));

6. 确定单词所在的容器

非常重要的一步是确定一个单词位于哪里 - 标题、描述等。

首先,您需要使用类似DOMDocument或PHPQuery的工具获取标题、所有元数据标签和所有标题(不要尝试使用正则表达式!)。 然后,在同一个循环中,您需要检查这些是否包含所需的单词。

7. 计算关键字价值

最后一步是计算关键字的价值。 为此,您需要对每个因素进行加权 - 密度、突出度和容器。 例如:

$value = (double) ((1 + $density) * ($prominence / 10)) * (1 + (0.5 * count ($containers)));

这个计算并不完美,但它应该能给你合理的结果。

结论

我没有提到我在工具中使用的每一个细节,但我希望它能提供一个良好的关键词分析视角。

N.B. 是的,这是受到今天博客文章关于回答自己问题的启发!


1
注意:如果有人对此有任何改进的想法,欢迎编辑我的答案或添加另一个答案,我很乐意听取! - Jeroen
@Jeroen 是的,那需要使用 C 语言,因为它可以提供显著的速度提升。 - Vlad Balmos
@Jeroen 这是目前最好的解决方案。请将其标记为答案。 - Alfred
@Jeroen,很抱歉在这个问题发布近两年后再次提出。但是,由于这个答案是我在互联网上找到的最好的答案,如果您仍然愿意提供帮助或记得这个答案中的步骤6-7的任何内容(因为已经过去2年了),我想请您帮忙。 - Déjà vu
@Jeroen 这是我的问题:http://stackoverflow.com/questions/22808192/php-domdocument-finding-words 如果你有兴趣的话。 - Déjà vu
显示剩余4条评论

4

@ 优化 '步骤'

针对这些步骤,我建议采用更为高级的解决方案,将其中一些步骤合并起来。不确定是否需要使用完整的词法分析器,如果你能够完美地设计它以满足你的需求,例如只查找hX等标签内的文本,那么这可能会更好。但是,由于实现起来可能会很麻烦,因此你必须非常认真对待。尽管如此,我还是要说,使用其他语言(PHP支持较差,因为它是一种高级语言)的Flex/Bison解决方案可以大大提升速度。

然而,幸运的是,libxml提供了出色的功能,正如下面所示,你将最终只需进行多个步骤中的一个。在分析内容之前,设置语言(停用词),缩小NodeList集合,然后从那里开始工作。

  1. 加载完整页面
  2. 检测语言
  3. 仅提取<body>到单独的字段中
  4. <head>和其他地方释放一点内存,例如unset($fullpage);
  5. 启动你的算法(如果可用pcntl - linux主机 - 则分叉并释放浏览器是一个不错的功能)

使用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);
    }

上面是一个类,类似于您的数据库,其中预先加载了页面“body”字段。
再次提到数据库处理方面,我最终将上述解析结果插入到一个全文标记表列中,以便将来的查找可以无缝进行。这对于数据库引擎来说是一个巨大的优势
关于全文索引的注意事项:
当处理少量文档时,全文搜索引擎可以直接扫描每个查询的文档内容,这种策略称为串行扫描。这就是一些基本工具(如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`
)

检测语言并从那一点选择您的停用词数据库是一个我自己忽略的功能,但它很棒 - 符合标准!所以感谢您的努力和回答 :)
另外,请记住不仅有标题标签,还有锚/ img标题属性。如果由于某种原因您的分析进入了“蜘蛛状状态”,我建议将参考链接()标题和textContent与目标页面结合使用。

谢谢您提供这些好建议!我很快就会开始根据您提供的代码进行重写(可能会将其放在GitHub上)。不过,有一件事,您所说的“全文标记表(无权)字段”是什么意思? - Jeroen
不同的数据库对于“全文本”一词的含义有所不同。个人只使用MySQL数据库。在这里,您需要创建一个表(或更改一个表)以使用MyISAM,然后为您的列设置索引。只能使用CHAR、VARCHAR或TEXT列,这是相当明显的,不是吗? :) 请查看:http://dev.mysql.com/doc/refman/5.0/en/fulltext-search.html,同时要注意以下注意事项:http://dev.mysql.com/doc/refman/5.0/en/fulltext-restrictions.html。 - mschr
“full-text”其实是我理解的词汇之一,我以前使用过MySQL,但是您所说的“flagged”和“weightless”是什么意思呢? - Jeroen
我会改进答案 - 全文搜索非常有用,可以搜索网站内容(例如像谷歌一样 :)),因为它使用哈希索引标记而不是“串行方法”,这类似于grep。在大量文档上,串行搜索变得冗长。请忽略“无重量”的评论,这是我自己的算法中留下的痕迹,其中我不对关键字进行加权处理,并且只使用一列。 - mschr
好的,这解释清楚了,谢谢!(虽然我不需要它,我只是在保存关键词列表) - Jeroen

4
你的算法中缺少文档导向分析(如果你没有因为某些原因而有意忽略它的话)。
每个网站都建立在一组文档上。统计每个文档中单词出现的频率可以提供关于单词覆盖范围的信息。出现在大多数文档中的单词是停用词。仅出现在少量文档中的特定单词可以形成一个特定主题的文档群集。针对特定主题的文档数量可以增加该主题单词的整体重要性,或者至少提供一个额外的因素来计算公式。
也许,你可以获益于一个预配置的分类器,其中包含每个类别/主题和关键字(通过索引现有公共类别层次结构,直到维基百科,这个任务可以部分自动化,但这本身并不是一个简单的任务)。然后你就可以将类别纳入分析中。
另外,你可以通过句子级别的分析来改善统计数据。也就是说,通过记录相同句子或短语中单词出现的频率,你可以发现陈词滥调和重复,并将它们从统计数据中排除。但是,我担心这在纯 PHP 中不容易实现。

虽然我在应用关键词分析方面已经太高级了,但这些是如何改进它的绝佳建议,谢谢! - Jeroen
@Jeroen,顺便说一下,在第一步过滤掉HTML标签可能会丢失有关文档结构的重要信息。我建议首先将文档作为HTML文档进行分析,检测其主要内容块,然后仅在主要内容上应用您的算法。这将使您能够消除菜单、表单、页脚和页眉等所有辅助内容的考虑。 - Stan
我尝试过使用Readability项目(http://www.keyvan.net/2010/08/php-readability/),但有时它会获取错误的文本块。此外,我主要分析网站的首页,所以它们通常没有真正的主文本块。 - Jeroen
然而,如果有人想使用它来分析文章/博客文章之类的东西,这绝对是一个好主意! - Jeroen

4
这可能只是一个小贡献,但我还是想提一下。

上下文评分

通过使用单词所在的位置,到一定程度上你已经在查看单词的上下文。你可以添加另一个因素,通过排名出现在标题(H1、H2等)中的单词高于段落内的单词,高于项目列表中的单词等。

频率过滤

基于语言检测停用词可能有效,但也许您可以考虑使用钟形曲线来确定哪些单词频率/密度过高(例如削减前5%,后95%)。然后将评分应用于剩余的单词。这不仅可以防止停用词的出现,还可以防止关键词滥用,至少在理论上 :)

那么对于只包含1个计数密度的文档,他会怎么做呢? :) - mschr

2
我建议您不要重复造轮子,而是使用Apache SoIr进行搜索和分析。它几乎具备您可能需要的所有功能,包括30多种语言的停用词检测[据我所知,甚至可能更多],并可处理存储在其中的数据。

1
我不是在构建一个搜索功能,而是使用它来显示网站的关键词分析;据我所知,Solr/Lucene 无法做到这一点。 - Jeroen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接