从 PHP 7.0.15 和 7.1.1 及更高版本开始,get_browser() 的性能得到显著提升,据报道快了 100 倍。变更日志、错误描述和解决方案如下所示:
https://php.net/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
值;等等。
The cookies
value simply means that the browser itself is capable of accepting cookies and does not mean the user has enabled the browser to accept cookies or not. The only way to test if cookies are accepted is to set one with setcookie(), reload, and check for the value.
示例 #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.net/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
由于浏览器检测可能很棘手而且速度很慢,我比较了一些包。
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.net/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
如果您只需要一个非常快且简单的函数来检测浏览器名称(更新至 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%)。
令我惊讶的是,我发现没有一个 get_browser 替代方案能够输出我在使用 Opera 或 Chrome 时所期望的正确名称/版本组合。它们要么给出错误的名称,例如 Safari,而实际上应该是 Chrome,如果 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';
}
// 接下来获取 useragent 的名称,是的,单独获取,有充分的理由
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) {
// 由于我们还没有使用 "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);
?>
对 2016 年 Francesco R 帖子的跟进。
他的函数对大多数人类流量都有效;添加了几行来涵盖最常见的机器人流量。还修复了函数由于 strpos 行为而无法检测位置 0 字符串的问题。
<?php
// 此函数编写于 2018 年 12 月,并已测试
function get_browser_name($user_agent)
{
// 将用户代理字符串转换为小写。
$t = strtolower($user_agent);
// 如果字符串 *以* 指定字符串开头,strpos 将返回 0(即 FALSE)。进行一个简单的操作,并在开头添加一个空格。
// "[strpos()] 可能返回布尔值 FALSE,但也可能返回一个计算为 FALSE 的非布尔值。"
// https://php.net/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 '[Bot] Googlebot' ;
elseif (strpos($t, 'bing' ) ) return '[Bot] Bingbot' ;
elseif (strpos($t, 'slurp' ) ) return '[Bot] Yahoo! Slurp';
elseif (strpos($t, 'duckduckgo') ) return '[Bot] DuckDuckBot' ;
elseif (strpos($t, 'baidu' ) ) return '[Bot] Baidu' ;
elseif (strpos($t, 'yandex' ) ) return '[Bot] Yandex' ;
elseif (strpos($t, 'sogou' ) ) return '[Bot] Sogou' ;
elseif (strpos($t, 'exabot' ) ) return '[Bot] Exabot' ;
elseif (strpos($t, 'msn' ) ) return '[Bot] MSN' ;
// 常用工具和机器人
elseif (strpos($t, 'mj12bot' ) ) return '[Bot] Majestic' ;
elseif (strpos($t, 'ahrefs' ) ) return '[Bot] Ahrefs' ;
elseif (strpos($t, 'semrush' ) ) return '[Bot] SEMRush' ;
elseif (strpos($t, 'rogerbot' ) || strpos($t, 'dotbot') ) return '[Bot] Moz or OpenSiteExplorer';
elseif (strpos($t, 'frog' ) || strpos($t, 'screaming')) return '[Bot] Screaming Frog';
// 其他
elseif (strpos($t, 'facebook' ) ) return '[Bot] Facebook' ;
elseif (strpos($t, 'pinterest' ) ) return '[Bot] 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 '[Bot] 其他' ;
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
可以将它放在每周 cron 作业文件夹中,通常位于 /etc/cron.weekly,但不要忘记使脚本可执行(chmod 775 scriptname)。
正如 ruudrp 在 https://php.net/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';
}
// 接下来获取用户代理的名称,是的,分开获取是有原因的
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= "Your browser: " . $ua['name'] . " " . $ua['version'] . " on " .$ua['platform'] . " reports: <br >" . $ua['userAgent'];
print_r($yourbrowser);
?>
请注意,通过 browscap php.ini 设置加载 php_browscap.ini 可能会消耗大量的内存。当前版本的大小为几兆字节(即使是“精简版”),并且每个 PHP 进程可以消耗数十兆字节的内存。即使您从未调用 get_browser(),也会在 PHP 启动时加载 php_browscap.ini。
如果您不使用 get_browser(),请确保将 browscap php.ini 设置留空——也许您只是从 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.net/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 内存,直到我将“完整”版本更改为“精简”版本(从 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');
}
小心使用此函数!
此函数会消耗服务器资源的很多 CPU 和内存。
如果您只使用此函数几次,可能没问题,但如果您在每个页面请求时都使用它,或者在每个会话中使用它,那就有问题了。
此外,此函数不能正常工作,可能会返回错误的值,通配符或空值,因此它对 Web 统计来说不太有用。
最好的方法是使用 preg_match 来检测浏览器/平台。