私が構築している Web アプリケーションでは、Web サイトを分析し、最も重要なキーワードを取得してランク付けし、それらを表示する必要があります。
すべての単語とその密度を取得して表示するのは比較的簡単ですが、非常に偏った結果が得られます (ストップワードのランクが非常に高くなるなど)。
基本的に、私の質問は次のとおりです: PHP でキーワード分析ツールを作成して、単語の重要度によって正しく並べ替えられたリストを作成するにはどうすればよいですか?
私が構築している Web アプリケーションでは、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 を使用できます。
言語ごとのストップワードのリストが必要になります。これは Web 上で簡単に見つけることができます。私はこれを使用しています: 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;
単語のプロミネンスは、テキスト内の単語の位置によって定義されます。たとえば、最初の文の 2 番目の単語は、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)));
この計算は完璧にはほど遠いですが、まともな結果が得られるはずです。
ツールで使用した内容のすべてを詳しく説明したわけではありませんが、キーワード分析の参考になれば幸いです。
注: はい、これは、あなた自身の質問への回答に関する今日のブログ投稿に触発されたものです!
@リファイン'ステップ'
これらの多くのステップを実行することに関して、私は少し「強化された」ソリューションを使用して、いくつかのステップを一緒に縫合します。
完全なレクサーが優れているかどうかはわかりませんが、ニーズに合わせて完全に設計する場合、たとえばhX内のテキストのみを探す場合などです。ただし、実装するのは頭痛の種になる可能性があるため、_深刻なビジネスを意味する必要があります。私の指摘を述べて、別の言語のFlex / Bisonソリューション(PHPは非常に高水準の言語であるためサポートが不十分です)は「非常識な」速度向上になると言いますが。
ただし、幸いなことlibxml
にすばらしい機能が提供されており、以下に示すように、1つに複数のステップが含まれることになります。内容を分析する前に、言語(ストップワード)を設定し、NodeListセットを縮小して、そこから作業します。
<body>
別のフィールドにのみ抽出する<head>
ます。unset($fullpage);
DOMパーサーを使用している間、ライブラリ(parse_urlなど)に応じて、設定によって属性hrefおよびsrcの検証がさらに行われる可能性があることを理解する必要があります。
タイムアウト/メモリ消費を回避するもう1つの方法は、php-cli(Windowsホストでも機能します)を呼び出して「ビジネスに取り掛かる」ことで、次のドキュメントを開始することです。詳細については、この質問を参照してください。
少し下にスクロールする場合は、提案されたスキーマを確認してください。最初のクロールでは、データベースに本体のみが配置され(さらに、この場合はlangが追加されます)、次の関数を使用しながらft_indexに入力してcronスクリプトを実行します。
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`
)
言語を検出し、その時点からストップワードデータベースを選択することは、私自身が省略した機能ですが、その気の利いた機能です。だからあなたの努力とこの答えに感謝します:)
また、タイトルタグだけでなく、アンカー/imgタイトル属性もあることに注意してください。何らかの理由で分析がスパイダーのような状態になった場合は、参照リンク(<a>
)のtitleとtextContentをターゲットページと組み合わせることをお勧めします<title>
これはおそらく小さな貢献ですが、それでも言及します。
ある程度までは、単語が配置されている位置を使用して、単語のコンテキストをすでに確認しています。見出し(H1、H2など)に表示される単語を段落内の単語よりも高く、おそらく箇条書きの単語よりも高くランク付けすることで、これに別の要素を追加できます。
言語に基づいてストップワードを検出することは機能するかもしれませんが、ベルカーブを使用して、どの単語の頻度/密度が贅沢すぎるかを判断することを検討できます(たとえば、下位5%と上位95%のストリップ)。次に、残りの単語にスコアを適用します。ストップワードを防ぐだけでなく、少なくとも理論的にはキーワードの乱用も防ぎます:)
アルゴリズムに欠けているものの 1 つは、ドキュメント指向の分析です (何らかの理由で意図的に省略していない場合)。
すべてのサイトは、ドキュメント セットに基づいて構築されています。すべての文書の単語頻度を数えることで、単語の範囲に関する情報が得られます。ほとんどのドキュメントに出てくる単語はストップ ワードです。限られた数のドキュメントに固有の単語は、特定のトピックに関するドキュメントのクラスターを形成できます。特定のトピックに関連するドキュメントの数は、トピックの単語の全体的な重要性を高めるか、少なくとも数式でカウントされる追加の要素を提供します.
おそらく、カテゴリ/トピックとそれぞれのキーワードを含む事前設定された分類器の恩恵を受けることができます (このタスクは、ウィキペディアまでのカテゴリの既存の公開階層をインデックス化することで部分的に自動化できますが、これは簡単なタスクではありません)。次に、カテゴリを分析に含めることができます。
また、文レベルでの分析により、統計を改善することができます。つまり、単語が同じ文またはフレーズに出現する頻度を把握することで、決まり文句や重複を発見し、統計から除外できます。しかし、残念ながら、これは純粋な PHP では簡単に実装できません。
車輪を再発明する代わりに、Apache SoIr を検索と分析に使用することをお勧めします。30以上の言語のストップワード検出(私が覚えている限り、それ以上かもしれません)や、保存されたデータを使って多くのことを行うなど、必要なものはほとんどすべて揃っています。