在 PHP 7.0.15 和 7.1.1 及更高版本中,get_browser() 現在效能好得多 - 據報導快了 100 倍。變更記錄、錯誤描述和解決方案在這裡
https://php.dev.org.tw/ChangeLog-7.php(搜尋 get_browser())
https://bugs.php.net/bug.php?id=70490
https://github.com/php/php-src/pull/2242
(PHP 4, PHP 5, PHP 7, PHP 8)
get_browser — 判斷使用者瀏覽器的功能
嘗試透過在 browscap.ini 檔案中查詢瀏覽器的資訊,判斷使用者瀏覽器的功能。
資訊會以物件或陣列的形式傳回,其中包含代表各種資料元素的資料,例如,瀏覽器的主版本號碼和次版本號碼以及 ID 字串;針對諸如框架、JavaScript 和 Cookie 等功能,會傳回 true
/false
值等等。
cookies
值僅表示瀏覽器本身能夠接受 Cookie,並不表示使用者已啟用瀏覽器接受 Cookie 或不接受。測試是否接受 Cookie 的唯一方法是使用 setcookie() 設定一個 Cookie,重新載入,並檢查該值。
範例 #1 列出有關使用者瀏覽器的所有資訊
<?php
echo $_SERVER['HTTP_USER_AGENT'] . "\n\n";
$browser = get_browser(null, true);
print_r($browser);
?>
以上範例將會輸出類似如下的內容
Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7) Gecko/20040803 Firefox/0.9.3 Array ( [browser_name_regex] => ^mozilla/5\.0 (windows; .; windows nt 5\.1; .*rv:.*) gecko/.* firefox/0\.9.*$ [browser_name_pattern] => Mozilla/5.0 (Windows; ?; Windows NT 5.1; *rv:*) Gecko/* Firefox/0.9* [parent] => Firefox 0.9 [platform] => WinXP [browser] => Firefox [version] => 0.9 [majorver] => 0 [minorver] => 9 [cssversion] => 2 [frames] => 1 [iframes] => 1 [tables] => 1 [cookies] => 1 [backgroundsounds] => [vbscript] => [javascript] => 1 [javaapplets] => 1 [activexcontrols] => [cdf] => [aol] => [beta] => 1 [win16] => [crawler] => [stripper] => [wap] => [netclr] => )
注意:
為了使其運作,您的 php.ini 中的 browscap 組態設定必須指向您系統上 browscap.ini 檔案的正確位置。
browscap.ini 並未與 PHP 捆綁在一起,但您可以在這裡找到最新的 » php_browscap.ini 檔案。
雖然 browscap.ini 包含許多瀏覽器的資訊,但它仰賴使用者更新以保持資料庫為最新狀態。該檔案的格式相當不言自明。
在 PHP 7.0.15 和 7.1.1 及更高版本中,get_browser() 現在效能好得多 - 據報導快了 100 倍。變更記錄、錯誤描述和解決方案在這裡
https://php.dev.org.tw/ChangeLog-7.php(搜尋 get_browser())
https://bugs.php.net/bug.php?id=70490
https://github.com/php/php-src/pull/2242
此函式對於當今的需求來說太慢了。
如果您需要瀏覽器/裝置/作業系統偵測,請嘗試此處列出的其中一個套件:https://github.com/ThaDafinser/UserAgentParser
如果您只需要一個非常快速且簡單的函式來偵測瀏覽器名稱(更新至 2016 年 5 月)
<?php
function get_browser_name($user_agent)
{
if (strpos($user_agent, 'Opera') || strpos($user_agent, 'OPR/')) return 'Opera';
elseif (strpos($user_agent, 'Edge')) return 'Edge';
elseif (strpos($user_agent, 'Chrome')) return 'Chrome';
elseif (strpos($user_agent, 'Safari')) return 'Safari';
elseif (strpos($user_agent, 'Firefox')) return 'Firefox';
elseif (strpos($user_agent, 'MSIE') || strpos($user_agent, 'Trident/7')) return 'Internet Explorer';
return 'Other';
}
// 用法:
echo get_browser_name($_SERVER['HTTP_USER_AGENT']);
?>
此函式也解決了 Edge(在使用者代理中包含字串「Safari」和「Chrome」)、Chrome(包含字串「Safari」)和 IE11(不包含像所有其他 IE 版本那樣的「MSIE」)的問題。
請注意,「strpos」是檢查字串的最快函式(遠比「preg_match」更好),而 Opera + Edge + Chrome + Safari + Firefox + Internet Explorer 是當今最常用的瀏覽器(超過 97%)。
由於瀏覽器偵測可能很棘手且速度很慢,我比較了幾個套件。
http://thadafinser.github.io/UserAgentParserComparison/v5/index.html
https://github.com/sinergi/php-browser-detector
https://github.com/WhichBrowser/Parser-PHP
https://github.com/piwik/device-detector
https://php.dev.org.tw/manual/en/function.get-browser.php
以下為結果
使用者代理字串
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36
Sinergi 套件
---------------
在 Windows 10.0 上的 Chrome 63.0.3239.84
耗時 0.0022480487823486 秒。
---------------
WhichBrowser 套件
---------------
在 Windows 10 上的 Chrome 63
耗時 0.021045207977295 秒。
---------------
Piwik 套件
---------------
在 Windows 10 上的 Chrome 63.0
耗時 0.079447031021118 秒。
---------------
get_browser 套件
---------------
在 Windows 10 上的 Chrome 63.0
耗時 0.09611701965332 秒。
---------------
使用者代理字串
Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0
Sinergi 套件
---------------
在 Windows 10.0 上的 Firefox 57.0
耗時 0.0023159980773926 秒。
---------------
WhichBrowser 套件
---------------
在 Windows 10 上的 Firefox 57.0
耗時 0.019663095474243 秒。
---------------
Piwik 套件
---------------
在 Windows 10 上的 Firefox 57.0
耗時 0.079678058624268 秒。
---------------
get_browser 套件
---------------
在 Windows 10 上的 Firefox 57.0
耗時 0.02236008644104 秒。
---------------
目前為止,在速度上(不一定是覆蓋率)始終勝出的是
https://github.com/sinergi/php-browser-detector
令我驚訝的是,我發現沒有一個 get_browser 的替代方案輸出我使用 Opera 或 Chrome 時所尋找的正確名稱/版本組合。它們要么給出錯誤的名稱,例如實際上應該是 Chrome 的 Safari,如果 ua 字串包含版本號(如最新版本的 Chrome 和 Opera),則報告的數字是錯誤的。因此,我從各種示例中獲取了一些片段並將它們組合起來,並添加了版本檢查。
<?php
function getBrowser()
{
$u_agent = $_SERVER['HTTP_USER_AGENT'];
$bname = 'Unknown';
$platform = 'Unknown';
$version= "";
//首先取得平台?
if (preg_match('/linux/i', $u_agent)) {
$platform = 'linux';
}
elseif (preg_match('/macintosh|mac os x/i', $u_agent)) {
$platform = 'mac';
}
elseif (preg_match('/windows|win32/i', $u_agent)) {
$platform = 'windows';
}
// 接下來取得使用者代理的名稱,分開取得是有原因的
if(preg_match('/MSIE/i',$u_agent) && !preg_match('/Opera/i',$u_agent))
{
$bname = 'Internet Explorer';
$ub = "MSIE";
}
elseif(preg_match('/Firefox/i',$u_agent))
{
$bname = 'Mozilla Firefox';
$ub = "Firefox";
}
elseif(preg_match('/Chrome/i',$u_agent))
{
$bname = 'Google Chrome';
$ub = "Chrome";
}
elseif(preg_match('/Safari/i',$u_agent))
{
$bname = 'Apple Safari';
$ub = "Safari";
}
elseif(preg_match('/Opera/i',$u_agent))
{
$bname = 'Opera';
$ub = "Opera";
}
elseif(preg_match('/Netscape/i',$u_agent))
{
$bname = 'Netscape';
$ub = "Netscape";
}
// 最後取得正確的版本號
$known = array('Version', $ub, 'other');
$pattern = '#(?<browser>' . join('|', $known) .
')[/ ]+(?<version>[0-9.|a-zA-Z.]*)#';
if (!preg_match_all($pattern, $u_agent, $matches)) {
// 我們沒有匹配的數字,繼續
}
// 查看我們有多少個
$i = count($matches['browser']);
if ($i != 1) {
//由於我們尚未使用「其他」參數,因此將有兩個
//查看版本是在名稱之前還是之後
if (strripos($u_agent,"Version") < strripos($u_agent,$ub)){
$version= $matches['version'][0];
}
else {
$version= $matches['version'][1];
}
}
else {
$version= $matches['version'][0];
}
// 檢查是否有數字
if ($version==null || $version=="") {$version="?";}
return array(
'userAgent' => $u_agent,
'name' => $bname,
'version' => $version,
'platform' => $platform,
'pattern' => $pattern
);
}
// 現在試試看
$ua=getBrowser();
$yourbrowser= "您的瀏覽器: " . $ua['name'] . " " . $ua['version'] . " 在 " .$ua['platform'] . " 回報: <br >" . $ua['userAgent'];
print_r($yourbrowser);
?>
接續 Francesco R 於 2016 年的貼文。
他的函數適用於大多數人類流量;新增了一些行以涵蓋最常見的機器人流量。也修正了由於 strpos 行為導致函數無法偵測位置 0 的字串的問題。
<?php
// 函數編寫及測試於 2018 年 12 月
function get_browser_name($user_agent)
{
// 忽略大小寫。
$t = strtolower($user_agent);
// 如果字串 *開頭* 就是該字串,strpos 會返回 0 (即 FALSE)。使用一個蹩腳的技巧,在開頭加上一個空格。
// "[strpos()] 可能會返回布林值 FALSE,但也可能返回一個非布林值,其值會被評估為 FALSE。"
// https://php.dev.org.tw/manual/en/function.strpos.php
$t = " " . $t;
// 人類 / 一般使用者
if (strpos($t, 'opera' ) || strpos($t, 'opr/') ) return 'Opera' ;
elseif (strpos($t, 'edge' ) ) return 'Edge' ;
elseif (strpos($t, 'chrome' ) ) return 'Chrome' ;
elseif (strpos($t, 'safari' ) ) return 'Safari' ;
elseif (strpos($t, 'firefox' ) ) return 'Firefox' ;
elseif (strpos($t, 'msie' ) || strpos($t, 'trident/7')) return 'Internet Explorer';
// 搜尋引擎
elseif (strpos($t, 'google' ) ) return '[機器人] Googlebot' ;
elseif (strpos($t, 'bing' ) ) return '[機器人] Bingbot' ;
elseif (strpos($t, 'slurp' ) ) return '[機器人] Yahoo! Slurp';
elseif (strpos($t, 'duckduckgo') ) return '[機器人] DuckDuckBot' ;
elseif (strpos($t, 'baidu' ) ) return '[機器人] Baidu' ;
elseif (strpos($t, 'yandex' ) ) return '[機器人] Yandex' ;
elseif (strpos($t, 'sogou' ) ) return '[機器人] Sogou' ;
elseif (strpos($t, 'exabot' ) ) return '[機器人] Exabot' ;
elseif (strpos($t, 'msn' ) ) return '[機器人] MSN' ;
// 常見工具和機器人
elseif (strpos($t, 'mj12bot' ) ) return '[機器人] Majestic' ;
elseif (strpos($t, 'ahrefs' ) ) return '[機器人] Ahrefs' ;
elseif (strpos($t, 'semrush' ) ) return '[機器人] SEMRush' ;
elseif (strpos($t, 'rogerbot' ) || strpos($t, 'dotbot') ) return '[機器人] Moz 或 OpenSiteExplorer';
elseif (strpos($t, 'frog' ) || strpos($t, 'screaming')) return '[機器人] Screaming Frog';
// 其他
elseif (strpos($t, 'facebook' ) ) return '[機器人] Facebook' ;
elseif (strpos($t, 'pinterest' ) ) return '[機器人] Pinterest' ;
// 檢查機器人使用者代理中常用的字串
elseif (strpos($t, 'crawler' ) || strpos($t, 'api' ) ||
strpos($t, 'spider' ) || strpos($t, 'http' ) ||
strpos($t, 'bot' ) || strpos($t, 'archive') ||
strpos($t, 'info' ) || strpos($t, 'data' ) ) return '[機器人] 其他' ;
return '其他 (未知)';
}
?>
這裡有更深入的文章
https://www.256kilobytes.com/content/show/1922/how-to-parse-a-user-agent-in-php-with-minimal-effort
PHP cron 腳本,可自動更新 browscap.ini。它會比較版本號碼,以判斷是否需要更新
<?php
$eol="\r\n"; //設定行尾 - cron
$fileurl = "https://browscap.org/stream?q=PHP_BrowsCapINI";
$verurl = "https://browscap.org/version-number";
$file = "/path/to/browscap.ini";
//尋找目前版本
$fp = fopen($file, "r+");
while (($line = stream_get_line($fp, 1024 * 1024, "\n")) !== false) {
if(strpos($line,"Version=")===0) {
list($temp, $curver) = explode("=",$line);
break;
}
}
fclose($fp);
echo("目前的 browscap.ini 檔案版本: " . $curver);
//取得 browscap.org 目前版本
$newver = file_get_contents($verurl);
echo($eol . "新的 browscap.ini 檔案版本: " . $newver);
//如果新的版本可用,則更新
if($newver > $curver) {
if(file_put_contents($file, file_get_contents($fileurl))) {
echo($eol . "browscap.ini 已更新!");
}
else {
echo($eol . "browscap.ini 更新失敗!");
}
}
else {
echo($eol . "browscap.ini 已是最新版本!");
}
echo($eol . "Cron 工作結束。" . $eol");
?>
若要在 Linux 伺服器上自動更新 browscap.ini 檔案,您可以使用這個簡單的 shell 腳本
wget -O /etc/browscap.ini "http://browscap.org/stream?q=Full_PHP_BrowsCapINI"
chmod 664 /etc/browscap.ini
您可以將它放入通常位於 /etc/cron.weekly 中的每週 cron 工作資料夾,只是別忘了讓該腳本可執行 (chmod 775 scriptname)。
由於 ruudrp 提供了程式碼 https://php.dev.org.tw/manual/en/function.get-browser.php#101125,,我加入了 Internet Explorer 11 的程式碼
<?php
function getBrowser()
{
$u_agent = $_SERVER['HTTP_USER_AGENT'];
$bname = 'Unknown';
$platform = 'Unknown';
$version= "";
//首先取得平台?
if (preg_match('/linux/i', $u_agent)) {
$platform = 'linux';
}
elseif (preg_match('/macintosh|mac os x/i', $u_agent)) {
$platform = 'mac';
}
elseif (preg_match('/windows|win32/i', $u_agent)) {
$platform = 'windows';
}
// 接著,取得 useragent 的名稱,是分開處理的,這是有原因的
if(preg_match('/MSIE/i',$u_agent) && !preg_match('/Opera/i',$u_agent))
{
$bname = 'Internet Explorer';
$ub = "MSIE";
}
elseif(preg_match('/Trident/i',$u_agent))
{ // 此條件適用於 IE11
$bname = 'Internet Explorer';
$ub = "rv";
}
elseif(preg_match('/Firefox/i',$u_agent))
{
$bname = 'Mozilla Firefox';
$ub = "Firefox";
}
elseif(preg_match('/Chrome/i',$u_agent))
{
$bname = 'Google Chrome';
$ub = "Chrome";
}
elseif(preg_match('/Safari/i',$u_agent))
{
$bname = 'Apple Safari';
$ub = "Safari";
}
elseif(preg_match('/Opera/i',$u_agent))
{
$bname = 'Opera';
$ub = "Opera";
}
elseif(preg_match('/Netscape/i',$u_agent))
{
$bname = 'Netscape';
$ub = "Netscape";
}
// 最後取得正確的版本號碼
// 增加了 "|:"
$known = array('Version', $ub, 'other');
$pattern = '#(?<browser>' . join('|', $known) .
')[/|: ]+(?<version>[0-9.|a-zA-Z.]*)#';
if (!preg_match_all($pattern, $u_agent, $matches)) {
// 如果沒有匹配的號碼,則繼續執行
}
// 查看我們有多少個
$i = count($matches['browser']);
if ($i != 1) {
// 我們會有兩個,因為我們還沒有使用 'other' 參數
// 查看版本號是在名稱之前還是之後
if (strripos($u_agent,"Version") < strripos($u_agent,$ub)){
$version= $matches['version'][0];
}
else {
$version= $matches['version'][1];
}
}
else {
$version= $matches['version'][0];
}
// 檢查是否有號碼
if ($version==null || $version=="") {$version="?";}
return array(
'userAgent' => $u_agent,
'name' => $bname,
'version' => $version,
'platform' => $platform,
'pattern' => $pattern
);
}
// 現在試試看
$ua=getBrowser();
$yourbrowser= "您的瀏覽器: " . $ua['name'] . " " . $ua['version'] . " 於 " .$ua['platform'] . " 報告: <br >" . $ua['userAgent'];
print_r($yourbrowser);
?>
請注意,透過 php.ini 中的 browscap 設定載入 php_browscap.ini 可能會消耗大量的記憶體。目前版本的檔案大小有好幾 MB(即使是「精簡版」),並且可以在每個 PHP 處理程序中消耗數十 MB 的 RAM。即使您從未呼叫 get_browser(),也會發生這種情況,因為 php_browscap.ini 會在 PHP 啟動時載入。
如果您不使用 get_browser(),請務必將 php.ini 中的 browscap 設定留空 – 也許您只從 PHP 網頁呼叫它,而不是從 PHP CLI 程式碼呼叫。
我建議您比較載入和未載入 php_browscap.ini 時處理程序的記憶體消耗。如有必要,請考慮建立您自己的精簡版 php_browscap.ini 副本,其中只包含對您重要的瀏覽器。
對於那些使用此函數來鎖定 MSIE 的人,更好的做法可能是使用 MSIE 特有的條件註解。更多資訊:<http://msdn.microsoft.com/en-us/library/ms537512%28VS.85%29.aspx>.
例如,表示您不理會使用 MSIE 6 或更早版本的用戶
<!--[if lt IE 7]>您似乎正在使用 <em>非常</em>舊版本的 MS Internet Explorer (MSIE)。如果您真的想繼續使用 MSIE,至少請<a href="http://www.microsoft.com/windows/internet-explorer/">升級</a>。<![endif]-->
您不應該僅僅依賴此方法來解決跨瀏覽器相容性問題。良好的做法是加入 IE 樣式表的 HTML if 語句,以及動態檢查瀏覽器類型。
請注意,此函數會顯示特定瀏覽器可能可以顯示的內容,但不會顯示使用者已開啟/關閉的內容。
因此,即使使用者已關閉 JavaScript,此函數也可能會告訴您瀏覽器能夠執行 JavaScript。
請小心使用自行開發的版本。
例如,Francesco R 評價很高的版本 [ https://php.dev.org.tw/manual/en/function.get-browser.php#119332 ] 對於 Edge 來說已不再正確
目前的 Edge 報告的字串類似於
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0
請注意是 "Edg/xxx" 而不是 "Edge"
雖然將他的程式碼更新為以下內容是很簡單的
<?php
function get_browser_name($user_agent)
{
if (strpos($user_agent, 'Opera') || strpos($user_agent, 'OPR/')) return 'Opera';
elseif (strpos($user_agent, 'Edg')) return 'Edge';
elseif (strpos($user_agent, 'Chrome')) return 'Chrome';
elseif (strpos($user_agent, 'Safari')) return 'Safari';
elseif (strpos($user_agent, 'Firefox')) return 'Firefox';
elseif (strpos($user_agent, 'MSIE') || strpos($user_agent, 'Trident/7')) return 'Internet Explorer';
return 'Other';
}
?>
你永遠不知道未來數值何時會變更。
如果你在 php.ini 設定中使用「完整」的 Browscap INI 檔案要小心:我曾疑惑為什麼每個 Apache 執行緒在我的伺服器上會佔用 350MB 的 RAM,直到我將「完整」版本改為「精簡」版本(從 45MB 到 0.7MB)。
現在,每個執行緒只佔用 16MB...
所以如果這對你來說足夠,請使用精簡版本!
為了完善 Francesco R 的內容,我加入了瀏覽器的版本。
function getNavigateur($user_agent)
{
if(empty($user_agent)) {
return array('nav' => 'NC', 'name' => 'NC', 'version' => 'NC');
}
$content_nav['name'] = 'Unknown';
if (strpos($user_agent, 'Opera') || strpos($user_agent, 'OPR/')) {
$content_nav['name'] = 'Opera';
if (strpos($user_agent, 'OPR/')) {
$content_nav['reel_name'] = 'OPR/';
} else {
$content_nav['reel_name'] = 'Opera';
}
}
elseif (strpos($user_agent, 'Edge')) {
$content_nav['name'] = $content_nav['reel_name'] = 'Edge';
}
elseif (strpos($user_agent, 'Chrome')) $content_nav['name'] = $content_nav['reel_name'] = 'Chrome';
elseif (strpos($user_agent, 'Safari')) $content_nav['name'] = $content_nav['reel_name'] = 'Safari';
elseif (strpos($user_agent, 'Firefox')) $content_nav['name'] = $content_nav['reel_name'] = 'Firefox';
elseif (strpos($user_agent, 'MSIE') || strpos($user_agent, 'Trident/7') || strpos($user_agent, 'Trident/7.0; rv:')) {
$content_nav['name'] = 'Internet Explorer';
if (strpos($user_agent, 'Trident/7.0; rv:')) {
$content_nav['reel_name'] = 'Trident/7.0; rv:';
} elseif (strpos($user_agent, 'Trident/7')) {
$content_nav['reel_name'] = 'Trident/7';
} else {
$content_nav['reel_name'] = 'Opera';
}
}
$pattern = '#' . $content_nav['reel_name'] . '\/*([0-9\.]*)#';
$matches = array();
if(preg_match($pattern, $user_agent, $matches)) {
$content_nav['version'] = $matches[1];
return $content_nav;
}
return array('name' => $content_nav['name'], 'version' => 'Inconnu');
}