【问题标题】:Keyword analysis in PHPPHP中的关键字分析
【发布时间】:2012-05-23 14:16:56
【问题描述】:

对于我正在构建的 Web 应用程序,我需要分析网站,检索并排列最重要的关键字并显示这些关键字。

获取所有单词、它们的密度并显示它们相对简单,但这会产生非常倾斜的结果(例如,停用词排名非常高)。

基本上,我的问题是:如何在 PHP 中创建一个关键字分析工具,以生成按单词重要性正确排序的列表?

【问题讨论】:

  • 你可以试试看类似 Lucene (keywordAnalyzer)...
  • 对于停用词问题,我使用 wordnet 数据库。我还使用此控件来可视化密度 + 关系。 codeproject.com/Articles/342715/…
  • 看起来很有希望,谢谢!

标签: php full-text-search analytics keyword pagerank


【解决方案1】:

最近,我自己一直在做这件事,我会尽量解释我做了什么。

步骤

  1. 过滤文本
  2. 分词
  3. 删除 2 个字符的单词和停用词
  4. 确定词频+密度
  5. 确定单词突出度
  6. 确定单词容器
    1. 标题
    2. 元描述
    3. 网址
    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="" 属性 - 检查语言和内容语言元数据标签 如果这些都没有设置,您可以使用外部 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)));

这个计算远非完美,但它应该会给你不错的结果。

结论

我没有提到我在工具中使用的每一个细节,但我希望它可以为关键字分析提供一个很好的视角。

注意是的,这是受今天关于回答您自己问题的博文的启发!

【讨论】:

  • 注意:如果有人对如何改进有任何想法,非常欢迎您编辑我的答案或添加另一个答案,我很乐意听到他们的声音!
  • @Jeroen,您可以通过将最密集的部分移动到 php 扩展中来提高整个过程的速度。
  • @Vlad:这意味着用 C 语言编写整个代码,对吧?这绝对是一种显着加快速度的方法,但不幸的是,我目前缺乏这样做的专业知识。
  • @Jeroen 是的,这需要 C,因为它提供了显着的速度提升。
  • @Jeroen 这是最好的解决方案。将此标记为答案。
【解决方案2】:

您的算法中缺少的一件事是面向文档的分析(如果您出于某种原因没有故意省略它)。

每个网站都建立在一个文档集之上。计算所有文档的词频将为您提供有关词覆盖率的信息。大多数文档中出现的词都是停用词。特定于有限数量文档的词可以形成关于特定主题的文档集群。与特定主题相关的文档数量可以提高该主题单词的整体重要性,或者至少提供一个额外的因素来计算在您的公式中。

也许,您可以从包含类别/主题和每个类别/主题和关键字的预配置分类器中受益(此任务可以通过索引现有的公共类别层次结构来部分自动化,直至 Wikipedia,但这本身并不是一项简单的任务)。然后,您可以将类别纳入分析。

此外,您可以通过句子级别的分析来改进统计数据。也就是说,具有单词在同一个句子或短语中出现的频率,您可以发现陈词滥调和重复,并从统计数据中消除它们。但是,恐怕这在纯 PHP 中并不容易实现。

【讨论】:

  • 虽然这对我应用关键字分析的方式来说太高级了,但这些是关于如何改进它的极好的建议,谢谢!
  • @Jeroen,顺便说一句,在第一步过滤掉 html 标签可能会丢失有关文档结构的重要信息。我建议先将文档分析为 html-document,检测其主要内容块,然后才将您的算法应用于主要内容。这将允许您从考虑中消除菜单、表单、页脚和页眉以及所有辅助内容。
  • 我尝试过,使用可读性项目 (keyvan.net/2010/08/php-readability),但有时它会得到不正确的文本块。此外,我主要分析网站的首页,因此它们通常没有真正的正文块。
  • 但是,如果有人会用它来分析文章/博客文章之类的东西,那绝对是个好主意!
【解决方案3】:

这可能是一个很小的贡献,但我还是会提到它。

上下文评分

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

频次消毒

根据语言检测停用词可能会奏效,但也许您可以考虑使用钟形曲线来确定哪些词的频率/密度过于夸张(例如,去除下 5% 和上 95%)。然后将评分应用于剩余的单词。它不仅可以防止停用词,还可以防止关键字滥用,至少在理论上是这样:)

【讨论】:

  • 那么他会对只包含 1 个计数密度的文档做什么? :)
【解决方案4】:

@提炼'步骤'

关于执行这些步骤,我会采用一些“增强”的解决方案,将你的一些步骤缝合在一起。

不确定是否完整的lexer 更好,如果您完美地设计它以满足您的需求,例如仅在 hX 等中查找文本。但是您必须指的是 _serious 业务,因为实施起来可能会令人头疼。虽然我会指出我的观点并说另一种语言的Flex / Bison 解决方案(PHP 提供的支持很差,因为它是一种高级语言)将是一个“疯狂”的速度提升。

但是,幸运的是 libxml 提供了出色的功能,如下所示,您最终将拥有多个步骤。在分析内容之前,设置语言(停用词),缩小 NodeList 集并从那里开始工作。

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

在使用 DOM 解析器时,应该意识到设置可能会引入对属性 href 和 src 的进一步验证,具体取决于库(例如 parse_url 和 likes)

另一种解决超时/内存消耗问题的方法是调用 php-cli(也适用于 Windows 主机)并“继续工作”并开始下一个文档。请参阅this question 了解更多信息。

如果您向下滚动一点,请查看建议的架构 - 初始抓取只会将主体放入数据库中(在您的情况下另外还有 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 标题属性。如果由于某种原因您的分析进入 类似蜘蛛的状态,我建议将参考链接 (&lt;a&gt;) titletextContent 结合起来与目标页面&lt;title&gt;

【讨论】:

  • 感谢您的这些好建议!我将根据您提供的代码很快开始重写我的代码(可能会放在 GitHub 上) 不过有一件事,a full-text flagged table (weightless) field 到底是什么意思?
  • “全文”一词的含义因数据库而异。就我个人而言,我只工作 MySQL dbs。在这里,您必须创建一个表(或更改一个表)以使用 MyISAM,然后为您的列设置索引。只有 CHAR、VARCHAR 或 TEXT 列是可用的,虽然你不会这么说吗? :) 检查:dev.mysql.com/doc/refman/5.0/en/fulltext-search.html,同时牢记注意事项:dev.mysql.com/doc/refman/5.0/en/fulltext-restrictions.html
  • full-text 实际上是我理解的词之一,我之前使用过 MySQL,但是 flaggedweightless 是什么意思?
  • 不完善的答案 - 全文对于搜索网站内容非常有用(即像 google :),因为它使用哈希,由令牌索引而不是“串行方法”,类似于 @987654339 @。在大量文档上,连续搜索变得冗长。在我自己的算法中留下“失重”的评论,我没有在关键字上加上权重,我只用一栏飞行
  • 好的,这就解释了,谢谢! (虽然我不需要它,我只是保存关键字列表)
【解决方案5】:

我建议您使用 Apache SoIr 进行搜索和分析,而不是重新发明轮子。它几乎拥有您可能需要的一切,包括 30 多种语言的停用词检测 [据我所知,可能更多] 并使用其中存储的数据执行大量工作。

【讨论】:

  • 我不是在构建搜索功能,我是用它来显示网站的关键字分析;据我所知,Solr/Lucene 无法做到这一点..
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-07-25
  • 2016-02-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多