$_SERVER['HTTP_USER_AGENT'] 包含用戶端瀏覽器、作業系統、裝置等資訊。雖然現代專案建議使用成熟函式庫(如 WhichBrowser/Parser、piwik/device-detector),但了解原生解析邏輯有助於除錯與輕量級需求。本文提供完整的瀏覽器/版本偵測函式與爬蟲辨識函式,並說明正則局限與最佳實踐。
<?php
/**
* 解析 User-Agent 取得瀏覽器名稱與版本
*
* @param string $ua User-Agent 字串,預設自動取 $_SERVER['HTTP_USER_AGENT']
* @return string 格式:'BrowserName Version',失敗回傳 'Unknown Browser'
*/
function getUserBrowser($ua = null) {
if ($ua === null) {
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
}
if (empty($ua)) return 'Unknown Browser';
$browser = '';
$version = '';
// 依優先順序匹配(較特殊的優先)
// 1. Microsoft Edge (Chromium-based) - 必須在 Chrome 之前
if (preg_match('/Edg\/([\d.]+)/i', $ua, $m)) {
$browser = 'Microsoft Edge';
$version = $m[1];
}
// 2. Chrome (含 Chromium 內核瀏覽器)
elseif (preg_match('/(?:Chrome|CriOS)\/([\d.]+)/i', $ua, $m)) {
$browser = 'Chrome';
$version = $m[1];
}
// 3. Firefox
elseif (preg_match('/Firefox\/([\d.]+)/i', $ua, $m)) {
$browser = 'Firefox';
$version = $m[1];
}
// 4. Safari (需排除 Chrome、Edge 等也包含 Safari 關鍵字的 UA)
elseif (preg_match('/Version\/([\d.]+).*Safari/i', $ua, $m)) {
$browser = 'Safari';
$version = $m[1];
}
// 5. Internet Explorer 11 (Trident/7.0; rv:11.0)
elseif (preg_match('/Trident\/7\.0.*rv:([\d.]+)/i', $ua, $m)) {
$browser = 'Internet Explorer';
$version = $m[1];
}
// 6. 舊版 IE (MSIE x.x)
elseif (preg_match('/MSIE\s([\d.]+)/i', $ua, $m)) {
$browser = 'Internet Explorer';
$version = $m[1];
}
// 7. Opera (舊版 Presto 內核)
elseif (preg_match('/Opera[\s\/]([\d.]+)/i', $ua, $m)) {
$browser = 'Opera';
$version = $m[1];
}
// 8. Opera (新版 Chromium 內核,OPR/xxx)
elseif (preg_match('/OPR\/([\d.]+)/i', $ua, $m)) {
$browser = 'Opera';
$version = $m[1];
}
// 9. 其他較少見瀏覽器
elseif (preg_match('/Maxthon/i', $ua)) {
$browser = 'Maxthon';
}
elseif (preg_match('/UCBrowser\/([\d.]+)/i', $ua, $m)) {
$browser = 'UC Browser';
$version = $m[1];
}
elseif (preg_match('/QQBrowser\/([\d.]+)/i', $ua, $m)) {
$browser = 'QQ Browser';
$version = $m[1];
}
return $browser . ($version ? ' ' . $version : '');
}
?>
<?php
// 測試各種 User-Agent
$testUAs = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0',
'Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko',
'Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36',
];
foreach ($testUAs as $ua) {
echo getUserBrowser($ua) . PHP_EOL;
}
/*
輸出範例:
Chrome 120.0.0.0
Firefox 121.0
Safari 17.2
Microsoft Edge 120.0.0.0
Internet Explorer 11.0
Chrome 120.0.0.0
*/
?>
<?php
/**
* 判斷是否為搜尋引擎爬蟲
*
* @param string $ua User-Agent 字串
* @return string|false 爬蟲名稱(如 'Googlebot', 'Baiduspider'),非爬蟲回傳 false
*/
function detectSearchBot($ua = null) {
if ($ua === null) {
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
}
if (empty($ua)) return false;
$uaLower = strtolower($ua);
// 爬蟲特徵字 => 顯示名稱對照表
$bots = [
'googlebot' => 'Googlebot',
'mediapartners-google' => 'Google AdSense',
'adsbot-google' => 'Google AdsBot',
'baiduspider' => 'Baiduspider',
'bingbot' => 'Bingbot',
'msnbot' => 'MSNBot',
'yandexbot' => 'YandexBot',
'yandex' => 'Yandex',
'slurp' => 'Yahoo Slurp',
'duckduckbot' => 'DuckDuckBot',
'sogou' => 'Sogou Spider',
'360spider' => '360Spider',
'bytespider' => 'Bytespider (Toutiao)',
'ia_archiver' => 'Alexa Crawler',
'facebookexternalhit' => 'Facebook Crawler',
'twitterbot' => 'Twitter Bot',
'linkedinbot' => 'LinkedIn Bot',
'whatsapp' => 'WhatsApp Crawler',
'telegrambot' => 'Telegram Bot',
'slackbot' => 'Slack Bot',
'discordbot' => 'Discord Bot',
'applebot' => 'Applebot',
'pinterest' => 'Pinterest Bot',
];
foreach ($bots as $pattern => $name) {
if (strpos($uaLower, $pattern) !== false) {
return $name;
}
}
// 通用 bot 關鍵字兜底
if (preg_match('/(bot|crawler|spider|archiver)/i', $ua)) {
return 'Unknown Bot';
}
return false;
}
?>
<?php
function logVisitorInfo() {
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
$browser = getUserBrowser($ua);
$bot = detectSearchBot($ua);
$data = [
'time' => date('Y-m-d H:i:s'),
'ip' => $ip,
'ua' => $ua,
'browser' => $browser,
'is_bot' => $bot !== false,
'bot_name' => $bot,
'url' => $_SERVER['REQUEST_URI'] ?? '',
'referer' => $_SERVER['HTTP_REFERER'] ?? '',
];
// 寫入日誌檔或資料庫
$logLine = json_encode($data, JSON_UNESCAPED_UNICODE) . PHP_EOL;
file_put_contents(__DIR__ . '/logs/visitors.log', $logLine, FILE_APPEND | LOCK_EX);
// 若為爬蟲,可選擇不執行昂貴的業務邏輯
if ($bot) {
// 可選:返回簡易頁面、不計入統計、不執行 Session 初始化等
}
return $data;
}
?>
| 問題 | 說明 | 建議 |
|---|---|---|
| UA 字串極其複雜多變 | 瀏覽器版本更新、裝置種類爆炸、偽裝 UA、混合關鍵字 | 生產環境優先使用 piwik/device-detector 或 WhichBrowser/Parser(Composer 安裝) |
| 正則效能 | 大量請求時逐條正則匹配有額外開銷 | 編譯為單一大正則、或快取解析結果(Redis keyed by UA hash) |
| 維護成本 | 新瀏覽器/爬蟲持續出現,需持續更新規則 | 使用社群維護的資料庫(Device Detector 定期釋出 regexes.yaml 更新) |
| 偽裝 UA | 爬蟲偽裝成瀏覽器、瀏覽器偽裝成其他瀏覽器 | 結合 IP 信譽、行為分析(請求頻率、JS 執行能力)綜合判斷 |
composer require piwik/device-detector
<?php
require 'vendor/autoload.php';
use DeviceDetector\DeviceDetector;
use DeviceDetector\Parser\Device\DeviceParserAbstract;
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
$dd = new DeviceDetector($ua);
$dd->parse();
echo '裝置: ' . $dd->getDeviceName() . PHP_EOL;
echo '品牌: ' . $dd->getBrand() . PHP_EOL;
echo '瀏覽器: ' . $dd->getClient('name') . ' ' . $dd->getClient('version') . PHP_EOL;
echo '作業系統: ' . $dd->getOs('name') . ' ' . $dd->getOs('version') . PHP_EOL;
echo '是否爬蟲: ' . ($dd->isBot() ? '是' : '否') . PHP_EOL;
?>