从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字符串;setcookie()帧、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.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
如果您只需要一个非常快速简单的函数来检测浏览器名称(更新至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.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
令我惊讶的是,我发现没有任何 get_browser 替代方案能够输出我使用 Opera 或 Chrome 时所需的正确名称/版本组合。它们要么给出错误的名称(例如,实际上应该是 Chrome 时却给出 Safari),要么在 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) {
//我们将有两个,因为我们还没有使用 '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'] . " on " .$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.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 或 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= "您的浏览器: " . $ua['name'] . " " . $ua['version'] . " on " .$ua['platform'] . " 报告: <br >" .$ua['userAgent'];
print_r($yourbrowser);
?>
请注意,通过 browscap php.ini 设置加载 php_browscap.ini 可能会消耗大量的内存。当前版本大小为几 MB(即使是“精简版”),每个 PHP 进程都可能消耗数十 MB 的 RAM。即使您从未调用过 get_browser(),也会发生这种情况,因为 php_browscap.ini 在 PHP 启动时就会加载。
如果您不使用 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 的 RAM,直到我将“完整版”替换为“精简版”(从 45MB 降至 0.7MB)才解决了这个问题。
现在,每个线程只占用 16MB……
所以,如果这对您足够了,请使用精简版!
为了补充 Francesco R 的说法,我添加了浏览器的版本信息
函数 getNavigateur($user_agent)
{
如果(empty($user_agent)) {
返回 array('nav' => 'NC', 'name' => 'NC', 'version' => 'NC');
}
$content_nav['name'] = '未知';
如果 (strpos($user_agent, 'Opera') || strpos($user_agent, 'OPR/')) {
$content_nav['name'] = 'Opera';
如果 (strpos($user_agent, 'OPR/')) {
$content_nav['reel_name'] = 'OPR/';
} 否则 {
$content_nav['reel_name'] = 'Opera';
}
}
} 否则如果 (strpos($user_agent, 'Edge')) {
$content_nav['name'] = $content_nav['reel_name'] = 'Edge';
}
} 否则如果 (strpos($user_agent, 'Chrome')) $content_nav['name'] = $content_nav['reel_name'] = 'Chrome';
} 否则如果 (strpos($user_agent, 'Safari')) $content_nav['name'] = $content_nav['reel_name'] = 'Safari';
} 否则如果 (strpos($user_agent, 'Firefox')) $content_nav['name'] = $content_nav['reel_name'] = 'Firefox';
} 否则如果 (strpos($user_agent, 'MSIE') || strpos($user_agent, 'Trident/7') || strpos($user_agent, 'Trident/7.0; rv:')) {
$content_nav['name'] = 'Internet Explorer';
如果 (strpos($user_agent, 'Trident/7.0; rv:')) {
$content_nav['reel_name'] = 'Trident/7.0; rv:';
} 否则如果 (strpos($user_agent, 'Trident/7')) {
$content_nav['reel_name'] = 'Trident/7';
} 否则 {
$content_nav['reel_name'] = 'Opera';
}
}
$pattern = '#' . $content_nav['reel_name'] . '\/*([0-9\.]*)#';
$matches = array();
如果(preg_match($pattern, $user_agent, $matches)) {
$content_nav['version'] = $matches[1];
返回 $content_nav;
}
返回 array('name' => $content_nav['name'], 'version' => '未知');
}