微博、推特等社交平台的核心互動元素是 @用戶提及 與 #話題標籤#。本文展示如何用 PHP 正則精準提取並轉換為可點擊連結。
<?php
$content = '@jb51 和 @twitter 在研究用 #PHP# 的 #正則表達式# 過濾話題和對象名';
// 1. 提取 #話題#(雙井號包裹,不含井號本身)
$topicPattern = '/#([^#]+)#/u'; // u 修飾符支援 Unicode
preg_match_all($topicPattern, $content, $topicMatches);
$topics = $topicMatches[1]; // ['PHP', '正則表達式']
// 2. 提取 @用戶名(@ 開頭,字母數字下劃線)
$userPattern = '/@([\w\d_]+)/u'; // \w 包含字母數字下劃線及 Unicode 字母
preg_match_all($userPattern, $content, $userMatches);
$users = $userMatches[1]; // ['jb51', 'twitter']
// 3. 同時替換為連結(順序重要:先處理話題再處理用戶,避免巢狀替換問題)
$linkedContent = $content;
// 話題 -> 搜尋連結
$linkedContent = preg_replace_callback($topicPattern, function($m) {
return '<a href="https://s.weibo.com/weibo?q=%23' . urlencode($m[1]) . '%23" target="_blank">#' . htmlspecialchars($m[1]) . '#</a>';
}, $linkedContent);
// 用戶 -> 個人主頁連結
$linkedContent = preg_replace_callback($userPattern, function($m) {
return '<a href="https://weibo.com/' . urlencode($m[1]) . '" target="_blank">@' . htmlspecialchars($m[1]) . '</a>';
}, $linkedContent);
echo $linkedContent;
/*
輸出:
<a href="https://weibo.com/jb51">@jb51</a> 和 <a href="https://weibo.com/twitter">@twitter</a> 在研究用
<a href="https://s.weibo.com/weibo?q=%23PHP%23">#PHP#</a> 的
<a href="https://s.weibo.com/weibo?q=%23%E6%AD%A3%E5%89%87%E8%A1%A8%E8%BE%BE%E5%BC%8F%23">#正則表達式#</a> 過濾話題和對象名
*/
?>
| 模式 | 解析 | 匹配範例 |
|---|---|---|
/#([^#]+)#/u | 捕獲組:非井號字符一個以上,雙井號包裹 | #PHP#、#正則表達式# |
/@([\w\d_]+)/u | 捕獲組:@ 後跟單詞字符(含 Unicode 字母)、數字、下劃線 | @jb51、@user_name、@用戶名 |
微博用戶名實際規則:1-30 字符,可含中文、英文、數字、下劃線、減號,不能純數字。
// 更精確的微博用戶名正則
$userPatternStrict = '/@([\p{L}\p{N}_-]{1,30})/u';
// \p{L} 任意 Unicode 字母、\p{N} 任意 Unicode 數字
// 話題標籤:1-30 字符,不含井號、@、網址特殊字符
$topicPatternStrict = '/#([^#@\s\/\\\\]{1,30})#/u';
<?php
class WeiboContentParser {
private const TOPIC_PATTERN = '/#([^#@\s\/\\\\]{1,30})#/u';
private const USER_PATTERN = '/@([\p{L}\p{N}_-]{1,30})/u';
private const TOPIC_URL = 'https://s.weibo.com/weibo?q=%23%s%23';
private const USER_URL = 'https://weibo.com/%s';
public static function extractTopics(string $text): array {
preg_match_all(self::TOPIC_PATTERN, $text, $m);
return array_unique($m[1] ?? []);
}
public static function extractUsers(string $text): array {
preg_match_all(self::USER_PATTERN, $text, $m);
return array_unique($m[1] ?? []);
}
public static function autoLink(string $text, array $options = []): string {
$defaults = [
'topic_url' => self::TOPIC_URL,
'user_url' => self::USER_URL,
'target' => '_blank',
'rel' => 'noopener noreferrer',
'topic_class' => 'weibo-topic',
'user_class' => 'weibo-user',
];
$opts = array_merge($defaults, $options);
$attr = 'target="' . $opts['target'] . '" rel="' . $opts['rel'] . '"';
// 話題替換
$text = preg_replace_callback(self::TOPIC_PATTERN, function($m) use ($opts, $attr) {
$url = sprintf($opts['topic_url'], urlencode($m[1]));
$label = htmlspecialchars($m[1]);
return '<a href="' . $url . '" class="' . $opts['topic_class'] . '" ' . $attr . '>#' . $label . '#</a>';
}, $text);
// 用戶替換
$text = preg_replace_callback(self::USER_PATTERN, function($m) use ($opts, $attr) {
$url = sprintf($opts['user_url'], urlencode($m[1]));
$label = htmlspecialchars($m[1]);
return '<a href="' . $url . '" class="' . $opts['user_class'] . '" ' . $attr . '>@' . $label . '</a>';
}, $text);
return $text;
}
}
// 使用
$raw = '測試 @official_account 和 #重要話題#';
echo WeiboContentParser::autoLink($raw);
?>
.weibo-topic { color: #e6162d; background: #fff1f0; padding: 0 2px; border-radius: 3px; }
.weibo-user { color: #1da1f2; background: #e6f7ff; padding: 0 2px; border-radius: 3px; }