內容管理系統、部落格、論壇常需將文章中的特定關鍵字 (如「SEO」、「PHP」、「JavaScript」) 自動轉為指定 URL 的超連結,提升站內導流與 SEO 權重傳遞。
<?php
$keywords = [
['網頁特效', '/js_a/js.html'],
['seo', '/seo/seo.html'],
['php', '/phper/php.html'],
['jsp', '/jsp/jsp.html'],
['asp', '/asp/asp.html'],
['ps', '/fw/photo.html'],
['photoshop', '/fw/photo.html'],
['javascript', '/js_a/js.html'],
['.net', '/net/net.html'],
['非主流', '/fw/photo.html'],
['網絡', '/mon/mon.html'],
['css', '/cssdiv/css.html'],
['平面設計', '/fw/photo.html'],
['網站', '/person/'],
['網頁制作', '/wy/yw.html'],
['搜索引擎', '/seo/seo.html'],
['優化', '/seo/seo.html'],
['動畫', '/flash_a/flash.html'],
['數據庫', '/database/database.html'],
['掙錢', '/mon/mon.html'],
['運營', '/mon/mon.html'],
];
$str = "今天是2010年5月30號,我的網站出現的問題這對seo有很多的問題,seo就是搜索引擎優化了,以前學php好啊現在覺得jsp好,css+div,網頁,網頁設計,網頁制作,網頁學習,網頁教學,Photoshop,Flash,HTML,CSS,Dreamweaver,Fireworks,ASP,PHP,JSP,ASP.NET,網站建設,網站開發,網頁特效,平面設計,個人網站,網頁素材";
echo "原始內容:$str<br><br>";
foreach ($keywords as $pair) {
$kw = $pair[0];
$url = $pair[1];
if (strpos($str, $kw) !== false) {
$str = str_replace($kw, '' . $kw . '', $str);
}
}
echo "處理後內容:$str";
?>
若關鍵字有包含關係 (如 `php` 和 `php framework`),短關鍵字先替換會破壞長關鍵字;且替換後的 `` 標籤內容若再被匹配,會產生巢狀 `` 導致 HTML 失效。
解決:按關鍵字長度降序排序,且僅在純文字節點替換 (需 DOM 解析)。
$str = preg_replace('/' . preg_quote($kw, '/') . '/i', '$0', $str);
純 `str_replace` 會誤替換 `href`、`alt`、`title` 等屬性值。正確做法:使用 `DOMDocument` 遍歷文字節點 (`TEXT_NODE`) 進行替換。
<?php
// 建立關鍵字 -> URL 映射
$map = [
'seo' => '/seo/seo.html',
'php' => '/phper/php.html',
// ...
];
// 按長度降序排序,優先匹配長關鍵字
uksort($map, function($a, $b) { return strlen($b) - strlen($a); });
// 組合單一正則:(seo|php|javascript|...)
$pattern = '/(' . implode('|', array_map('preg_quote', array_keys($map), array_fill(0, count($map), '/'))) . ')/i';
$content = preg_replace_callback($pattern, function($m) use ($map) {
$kw = $m[0];
// 找到映射 (不分大小寫)
$url = '';
foreach ($map as $k => $v) {
if (strcasecmp($k, $kw) === 0) { $url = $v; break; }
}
return $url ? '' . $kw . '' : $kw;
}, $content);
?>
<?php
function autoLinkKeywords($html, array $keywordMap) {
$dom = new DOMDocument();
@$dom->loadHTML('' . $html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
$xpath = new DOMXPath($dom);
// 只選取文字節點,排除 script/style/textarea/a 內部
$textNodes = $xpath->query('//text()[not(parent::script) and not(parent::style) and not(parent::textarea) and not(parent::a)]');
// 按長度降序
uksort($keywordMap, fn($a, $b) => strlen($b) - strlen($a));
$pattern = '/(' . implode('|', array_map('preg_quote', array_keys($keywordMap), array_fill(0, count($keywordMap), '/'))) . ')/i';
foreach ($textNodes as $textNode) {
$newHtml = preg_replace_callback($pattern, function($m) use ($keywordMap) {
$kw = $m[0];
foreach ($keywordMap as $k => $v) {
if (strcasecmp($k, $kw) === 0) return '' . $kw . '';
}
return $kw;
}, $textNode->nodeValue);
if ($newHtml !== $textNode->nodeValue) {
$fragment = $dom->createDocumentFragment();
$fragment->appendXML($newHtml);
$textNode->parentNode->replaceChild($fragment, $textNode);
}
}
return $dom->saveHTML();
}
?>
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| str_replace 迴圈 | 簡單易懂 | 大小寫敏感、破壞 HTML 結構、巢狀標籤、效能差 | 純文字、簡單測試 |
| preg_replace_callback 單一正則 | 效能較好、支援大小寫、長度優先 | 仍可能破壞屬性/標籤 | 簡單 HTML、無巢狀風險 |
| DOMDocument + XPath | 精準替換文字節點、保護 HTML 結構 | 記憶體佔用大、速度較慢、需處理編碼 | 生產環境、複雜 HTML、安全要求高 |
關鍵字自動連結看似簡單,實則涉及 HTML 解析、替換順序、編碼、效能等多重考量。簡易專案可用正則優化版;正式產品強烈建議使用 `DOMDocument` 方案,確保輸出 HTML 結構完整、安全。