Steamer Lane Studio技術備忘録WordPress

投稿内の特定のキーワードに指定のリンクを自動で貼る

wordpress 投稿内の特定のキーワードに指定のリンクを自動で貼る
最終更新日: 2024年8月21日

MTならkotonohalinkというプラグインでできる。これは再構築高負荷だが、便利。特にSEO上内部リンクを増やしたいときに良いが、高負荷過ぎて再構築時500エラーやTimeOutを起こす可能性もある(経験済み)。
Wordpressの場合動的生成で都度DBをぐるっと回るので表示速度への影響はある。それが大きいか小さいかはDBのサイズとこのキーワード指定の数の多さと、サーバーの物理的なパフォーマンスに依る。

だから使用には最新の注意を。WPの場合通常トップページはテンプレに記述するからこの機能の埒外だが、固定ページをコンテンツのモジュールとしてトップに取り込むなんてことをすれば話は変わる。
PSIなどのツール使って確認しながら使いましょう。

後述で文字コードのことなどに触れているが、動くならあった方がいいかもね。
WEBマスターとかやってると時事Googleの検索アルゴリズムなどの変化に触れるが、ここ最近はユーザーの利便性と検索キーに対する結果の精度向上により重きを置いたようなので、やりっぱなしで済む施策で内部リンク増えるのはプラスでなかろうかと感じる。
悪用したりやり過ぎはUI低下やGoogleのペナ対象になるから気を付けましょう。

function my_autolink_keywords_in_paragraphs($content) {$keywords = array('キーワード1' => 'リンク先のURL','キーワード2' => 'リンク先のURL','キーワード3' => 'リンク先のURL','キーワード4' => 'リンク先のURL','キーワード5' => 'リンク先のURL');// <p>タグ内のテキストを抽出し、キーワードをリンクに変換$new_content = preg_replace_callback('/<p>(.*?)<\/p>/is', function($matches) use ($keywords) {$paragraph = $matches[1];foreach ($keywords as $keyword => $url) {// HTML エスケープに対応$keyword_escaped = preg_quote($keyword, '/');$pattern = '/('.$keyword_escaped.')/u';$replacement = '<a href="' . esc_url($url) . '" class="totri">' . $keyword . '</a>';$paragraph = preg_replace($pattern, $replacement, $paragraph);}return '<p>' . $paragraph . '</p>';}, $content);return $new_content;}add_filter('the_content', 'my_autolink_keywords_in_paragraphs');

概要
プラグインでこうしたものはあるが(使ってもいないけど)当スタジオは開発終了などの憂き目にあわないようPL使わない派なのでこの簡易なコードを作った。
$keywords array内は設定ページに表示されるためのinputとか書いてできるが、コードが冗長になるからfunction内弄る形です。
singleまたはpageの<p>タグ内に指定のキーワードがあった場合、preg_replaceでリンク付きのキーワードに置換するもの。
<P>内限定としたのは、hタグの行内にリンクがあると綺麗でないから。見出しに要らないでしょ。
$replacement変数指定部分のclass属性は好きに変えて、特に指定しないならこの部分消してもいい。

注意
キーワードの指定は正規表現を使っているが、「森のクマさん」「クマさん」といった指定をする場合は、処理順序をケアする必要がある。
それと文字コードにも注意。上記コードはUTF8対応だが文字コードの整合が取れないと正常に稼働しません。

修正版:上記コードでは内部ページの場合リンク先投稿内のキーワードにもリンクが貼られてしまうので、サイトの内部リンク対象限定で、keywordに対応するページのID指定しそのIDのページはこの処理から除外するようにした。
また既存のaタグ内にキーワードがあった場合そこにも働いてしまうので既存のaタグは除外するようにした。
function my_autolink_keywords_in_paragraphs($content) {// キーワードとページID$keywords_and_ids = array('森のクマさん' => 7404,'クマさん' => 6332,'動物園' => 6842);

 // 現在のページIDを取得
$current_page_id = get_the_ID();

// 現在のページが除外対象かどうかをチェック
if (in_array($current_page_id, $keywords_and_ids)) {
return $content;
}
// <a>タグ内の内容を一時的にプレースホルダーに置き換える
$placeholders = [];
$content = preg_replace_callback('/<a\b[^>]*>(.*?)<\/a>/is', function($matches) use (&$placeholders) {
$placeholder = '__PLACEHOLDER_' . count($placeholders) . '__';
$placeholders[$placeholder] = $matches[0];
return $placeholder;
}, $content);
// <p>タグ内のテキストを抽出し、キーワードをリンクに変換
$content = preg_replace_callback('/<(p)>(.*?)<\/(p)>/is', function($matches) use ($keywords_and_ids) {
$tag = $matches[1]; // 'p' または 'li'
$content = $matches[2]; // タグ内の内容

foreach ($keywords_and_ids as $keyword => $page_id) {
$url = get_permalink($page_id);
$keyword_escaped = preg_quote($keyword, '/');
$pattern = '/('.$keyword_escaped.')/u';
$replacement = '<a href="' . esc_url($url) . '" class="totri">$1</a>';
$content = preg_replace($pattern, $replacement, $content);
}
return '<' . $tag . '>' . $content . '</' . $tag . '>';
}, $content);
// プレースホルダーを元の<a>タグに戻す
$content = str_replace(array_keys($placeholders), array_values($placeholders), $content);
return $content;
}
add_filter('the_content', 'my_autolink_keywords_in_paragraphs');

コード=処理なのでできるだけ単純な方が良い、もちろんサーバーの物理的な性能にもよるが、表示の遅いページはそれだけでセキュリティリスクなどの心配も生じる。もちろん検索エンジンの評価も落ちる(多分コンテンツ>表示速度的なアルゴリズムだとは思うが)から早いに越したことはない。
プラグインならこのコードより優れたものもあるかもしれないが、重くなるのとPLの開発終了を嫌うとこうなる。
投稿時意図しないところにaが貼られるので場合によりレイアウトの崩れなどが起きるかもしれないが、CSSで調整するのが早いね。

変形・応用版

ある案件用に改変した。あくまでも参考程度に。
内部リンク(投稿ID指定)と外部リンク(URL指定)の混在
対象がpだけだったがliも対象に
除外キーワード設定(例:「クマさん」を指定しているが、クマさんを内包する「森のクマさん」にはリンクを貼りたくない時など)

function my_autolink_keywords_in_paragraphs($content) {// 内部リンクのキーワードとページID$internal_keywords_and_ids = array('クマさん' => 332,'動物園' => 684);// 外部リンクのキーワードとURL$external_keywords_and_urls = array('URL1' => 'https://URL1','URL2' => 'https://URL2');//IDとURLのキーワードと処理順注意、ここでは家族葬を内包するキーのあるURLを先に処理// 除外キーワードのリスト$excluded_keywords = array('除外1','除外2');// 現在のページIDを取得$current_page_id = get_the_ID();// 現在のページが内部リンクキーワードに対応するかチェックif (in_array($current_page_id, $internal_keywords_and_ids)) {return $content;}// <a>タグ内の内容を一時的にプレースホルダーに置き換える$placeholders = [];$content = preg_replace_callback('/<a\b[^>]*>(.*?)<\/a>/is', function($matches) use (&$placeholders) {$placeholder = '__PLACEHOLDER_' . count($placeholders) . '__';$placeholders[$placeholder] = $matches[0];return $placeholder;}, $content);// 外部リンクのキーワードを処理$content = preg_replace_callback('/<(p)>(.*?)<\/(p)>/is', function($matches) use ($external_keywords_and_urls, $internal_keywords_and_ids, $excluded_keywords) {$tag = $matches[1]; // 'p' または 'li'$inner_content = $matches[2]; // タグ内の内容// 除外キーワードが含まれているかチェック$exclude = false;foreach ($excluded_keywords as $excluded_keyword) {if (strpos($inner_content, $excluded_keyword) !== false) {$exclude = true;break;}}if (!$exclude) {// 外部リンクのキーワードにリンクを適用foreach ($external_keywords_and_urls as $keyword => $url) {$keyword_escaped = preg_quote($keyword, '/');$pattern = '/(' . $keyword_escaped . ')(?![^<]*<\/a>)/u'; // <a>タグ内を除外$replacement = '<a href="' . esc_url($url) . '" class="totri">$1</a>';$inner_content = preg_replace($pattern, $replacement, $inner_content);}// 内部リンクのキーワードにリンクを適用foreach ($internal_keywords_and_ids as $keyword => $page_id) {$url = get_permalink($page_id);$keyword_escaped = preg_quote($keyword, '/');$pattern = '/(' . $keyword_escaped . ')(?![^<]*<\/a>)/u'; // <a>タグ内を除外$replacement = '<a href="' . esc_url($url) . '" class="totri">$1</a>';$inner_content = preg_replace($pattern, $replacement, $inner_content);}}return '<' . $tag . '>' . $inner_content . '</' . $tag . '>';}, $content);// プレースホルダーを元の<a>タグに戻す$content = str_replace(array_keys($placeholders), array_values($placeholders), $content);return $content;}add_filter('the_content', 'my_autolink_keywords_in_paragraphs');

キーワード指定が複雑な場合はそれだけで正常稼働しない場合がある。特にキーワードを内包する他のキーワードや除外キーワードが絡み合うと自動リンクが貼られないことがある。
少しキーワードを参照するレベルの上下を試みたが結果は芳しくない、当該案件では稼働したのでこのままにした。
htmlタグは当初参照をp|liとして作ったがliのインラインにpがある場合にレイアウトが大崩れするので分けた。
全体に冗長になってきたので、動的生成には向かないかも。
環境だがMySQL5.7、php8.1、WP6.1up、テーマは当スタジオのオリジナル。テーマによりダメなケースもあるかもしれないが、その場合はデフォルトを親テーマとしてやれば動くかも。