使用用户代理字符串(UA 嗅探)进行浏览器检测
每次向服务器发出请求时,浏览器都会包含一个 User-Agent
HTTP 标头,其值称为用户代理(UA)字符串。此字符串旨在标识浏览器、其版本号及其主机操作系统。
User-Agent: <product> / <product-version> <comment>
你也可以通过 JavaScript 中的 navigator.userAgent
属性访问此字符串。
console.log(window.navigator.userAgent);
// Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:138.0) Gecko/20100101 Firefox/138.0
尝试解析 UA 字符串(有时称为“UA 嗅探”)并根据 UA 字符串中的值改变网站行为可能会很诱人,但这很难可靠地完成,并且通常会导致错误。
本文档描述了使用 UA 字符串进行浏览器检测的常见陷阱以及推荐的替代方案。最后,我们提供了一些使用字符串进行 UA 检测的提示,但你应该只在绝对必要时才这样做!
为什么特性检测优于浏览器检测
为了说明为什么根据浏览器调整站点功能会引入复杂性和可能的错误,请考虑以下示例。一个应用程序希望使用 JavaScript 中的 splitUpString()
函数,并使用后行断言(?<=…
)。
let splitUpString;
if (navigator.userAgent.includes("Chrome")) {
const camelCaseExpression = new RegExp("(?<=[A-Z])");
splitUpString = (str) => String(str).split(camelCaseExpression);
} else {
// This fallback is inefficient, but it works
splitUpString = (str) =>
String(str)
.split(/(.*?[A-Z])/)
.filter(Boolean);
}
console.log(splitUpString("fooBar")); // ["fooB", "ar"]
console.log(splitUpString("jQWhy")); // ["jQ", "W", "hy"]
此代码做了几个可能错误的假设,如果它在错误的浏览器或浏览器版本上运行,将破坏代码。
-
所有包含子字符串
Chrome
的用户代理字符串都表示 Chrome 浏览器。基于 UA 字符串的浏览器检测最大的问题之一是,浏览器和用户代理经常假装是其他浏览器,或包含基于多个浏览器的信息。
-
如果浏览器是 Chrome,则后行特性始终可用。实际上,浏览器可能是支持此特性之前的旧版 Chrome,或者可能是后续版本中删除了此特性。
-
最重要的是,它假设没有其他浏览器支持该特性,而该特性可能随时添加到任何其他浏览器中。所有不匹配的浏览器将不得不使用效率低下的备用方案。
需要注意的是,这些问题将无论浏览器检测方法如何都存在;UA 嗅探、客户端提示、其他 HTTP 标头的存在、缺失或内容等等。知道使用哪个浏览器无关紧要,在这种情况下我们实际寻找的是特性检测,这将在下面更详细地描述。
UA 嗅探的替代方案
以下部分描述了浏览器检测的替代方案,这些方案比 UA 嗅探更健壮,并且适用于更多场景。
特性检测
特性检测是指你检查客户端是否支持某个特定特性,而不是弄清楚是哪个浏览器在渲染你的页面。对于不支持某个特性的情况,你可以使用备用方案。以下特性检测示例检查客户端是否支持 地理位置 API。你可以通过检查全局 Navigator
对象上是否提供了 geolocation
属性来完成此操作。
if ("geolocation" in navigator) {
navigator.geolocation.getCurrentPosition((position) => {
// show the location on a map, such as the Google Maps API
});
} else {
// Show a static map instead
}
你可以对许多特性执行此操作。例如,你可以确定是否可以内联查看 PDF 文件,或者是否支持 虚拟键盘 API,等等。
if ("application/pdf" in navigator.mimeTypes) {
// browser supports inline viewing of PDF files.
}
if ("virtualKeyboard" in navigator) {
// browser supports the Virtual Keyboard API
}
对于样式,你还可以在 CSS 中使用 @supports
规则进行特性检测,如果你想检查某个特性是否存在,可以结合使用 not
关键字。有关在 CSS 中使用此方法的更多信息,请参阅使用特性查询。
@supports (display: grid) {
.box {
display: grid;
gap: 2rem;
}
}
@supports not (property: value) {
/* CSS rules for fallback */
}
在极少数情况下,如果不同浏览器对某个特性的行为不同,你应该测试浏览器如何实现 API,并根据此来确定如何使用它。要了解更多信息,请参阅实现特性检测文档。
移动设备检测
滥用 UA 嗅探(和客户端提示)的常见情况是检测客户端是否为移动设备。通常人们实际上是希望检测用户的设备是否支持触摸且屏幕较小,这样他们就可以通过为按钮添加额外的填充等方式来优化他们的网站。
相反,你应该使用现代 API 进行特性检测。例如,要检查触摸支持,请尝试 Navigator
接口中的 maxTouchPoints 属性。
if (navigator.maxTouchPoints > 1) {
// browser supports multi-touch
}
对于其他问题,如布局,请使用现代 CSS,如 flexbox 和 grid,以实现灵活布局。不要在小屏幕上隐藏内容,而是动态调整布局。媒体查询应该处理大多数布局更改,减少对基于 JavaScript 的调整的需求。
如果您想确保用户旋转设备或在不同屏幕模式之间切换时平滑过渡,可以查看检测设备方向。对于可折叠设备,有更新的 API,例如设备姿态 API,但请务必检查兼容性数据,因为支持情况差异很大。
客户端提示
对于基于 Blink 的浏览器(Chromium、Edge、Brave、Vivaldi 等),另一种选择是用户代理客户端提示。在客户端提示中,服务器通过 HTTP 标头或JavaScript API 主动从客户端请求设备信息。
客户端提示在检测基于 Blink 的浏览器方面优于 UA 嗅探,因为它们不那么容易被伪造,并且提供更小、更可靠的信息,这些信息更容易解析。根据客户端提示更改站点功能仍然是一个坏主意!在可能的情况下,您应该如上所述使用特性检测和渐进增强。
例如,在 HTTP 机制中,服务器会随响应包含 Accept-CH
标头以及客户端在后续请求中应包含的标头列表。假设服务器向客户端发送此响应:
Accept-CH: Sec-CH-UA-Mobile, Sec-CH-UA-Platform, Sec-CH-UA
这会请求客户端在后续请求中发送以下标头:
Sec-CH-UA-Mobile
:一个布尔值,指示客户端是否为移动设备。Sec-CH-UA-Platform
:客户端正在运行的平台(“Windows”、“Android”等)。Sec-CH-UA
:用户代理的品牌和重要版本信息。
假设客户端支持客户端提示,则 UA 客户端提示可能会出现在后续请求中:
GET /my/page HTTP/1.1
Host: example.site
Sec-CH-UA: " Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96"
Sec-CH-UA-Mobile: ?1
Sec-CH-UA-Platform: "Android"
要了解更多关于客户端提示的信息,请参阅HTTP 客户端提示。在使用此功能之前,请务必查看浏览器兼容性详情以获取更多信息。
其他技术和原则
使用浏览器检测的无效理由
如果您仍然在考虑浏览器检测而不是特性检测和渐进增强,请检查您是否受到以下(无效的)原因的驱动:
- 您正在尝试解决特定浏览器版本中的特定错误。
-
您不太可能是第一个遇到它的人。专家或持有不同观点的人可能会给您提示,以更好地避免或解决该错误。如果问题不常见,值得检查该错误是否已通过错误跟踪系统(Mozilla;WebKit;Blink;Opera)报告给浏览器厂商。浏览器制造商确实会关注错误报告,您的报告可能有助于修复或为问题提供更可靠的解决方案。
- 根据访问者的浏览器提供不同的 HTML。
-
这通常不是个好主意,但在极少数情况下是必要的。您可以通过添加非语义的
<div>
或<span>
元素来避免吗?考虑您的设计是否存在问题:您可以使用渐进增强或流式布局来帮助消除对这样做的需求吗?与重构 HTML 相比,准确应用 UA 检测的努力应该是一个决定性因素。 - 试图找出访问者的浏览器是否具有特定功能。
-
您的网站需要使用某些浏览器尚不支持的特定功能,并且您希望为使用不兼容浏览器的用户提供一个功能较少但您知道可以运行的旧版网站。这是使用 UA 检测的最糟糕的原因,因为所有浏览器最终都可能会赶上。此外,以这种方式为每个浏览器测试不同的功能是不切实际的。
提取相关的 UA 字符串部分
如果您已经探索了上述所有选项,并且仍然需要解析 UA 字符串作为最后的手段,本节有一些提示会有所帮助。不幸的是,用户代理字符串的不同部分没有统一性,因此我们进入了棘手的部分。
渲染引擎
共享共同渲染引擎的浏览器将以相同的方式显示页面:通常可以合理地假设在一个浏览器中有效的方法在另一个浏览器中也有效。目前有三个主要的渲染引擎:Blink、Gecko 和 WebKit。
在以下示例中,渲染引擎是字符串 Gecko/20100101
,表示渲染引擎是 Gecko
,而“gecko-Trail”是固定字符串 20100101
,表示“桌面”。
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:138.0) Gecko/20100101 Firefox/138.0
检测渲染引擎名称在网站中很常见,许多用户代理历来都添加了其他渲染引擎名称,以避免网站仅根据渲染引擎名称而将其排除。因此,在检测渲染引擎时,务必注意不要触发误报,因为此方法特别不可靠。考虑以下在 macOS 上 Chrome 134 中发送的 UA 字符串:
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
引擎 | 必须包含 | 详情 |
---|---|---|
Blink | Chrome/xyz |
|
Gecko | Gecko/xyz |
|
WebKit | AppleWebKit/xyz |
WebKit 浏览器会添加一个 like Gecko 字符串,如果检测不仔细,可能会对 Gecko 造成误报。 |
Presto | Opera/xyz |
已废弃;Presto 在 Opera 浏览器版本 >= 15 中不再使用(请参阅“Blink”)。 |
EdgeHTML | Edge/xyz |
已废弃;EdgeHTML 在 Edge 浏览器版本 >= 79 中不再使用(请参阅“Blink”)。 |
渲染引擎版本
大多数渲染引擎将版本号放在 RenderingEngine/VersionNumber
令牌中,但 Gecko 是一个显著的例外。在以下示例中,它是字符串 rv:138.0
,表示渲染引擎版本号是 138.0
,这与 Firefox 版本相同。
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:138.0) Gecko/20100101 Firefox/138.0
浏览器名称和版本
当人们说他们想要“浏览器检测”时,他们实际上通常想要“渲染引擎检测”。这通常意味着检测“Gecko”或“WebKit”,而不是“Firefox”或“Safari”。
大多数浏览器以 BrowserName/VersionNumber
的格式设置名称和版本。但是由于名称不是用户代理字符串中该格式的唯一信息,您无法发现浏览器的名称,您只能检查您正在查找的名称是否存在。在以下示例中,浏览器名称是字符串 Firefox/138.0
,表示浏览器名称是 Firefox
,软件版本是 138.0
。
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:138.0) Gecko/20100101 Firefox/138.0
有些浏览器发送相互矛盾的信息:例如,Chrome 既报告 Chrome 又报告 Safari。因此,要检测 Safari,您必须检查 Safari 字符串,并且没有 Chrome 字符串,Chromium 通常也报告自己是 Chrome,而 SeaMonkey 报告自己是 Firefox。
在使用正则表达式处理 BrowserName
部分时要小心,因为用户代理还包含围绕关键字/值语法的字符串。例如,Safari 和 Chrome 都包含字符串 like Gecko
。
浏览器名称 | 必须包含 | 不得包含 |
---|---|---|
Firefox | Firefox/xyz |
Seamonkey/xyz |
Seamonkey | Seamonkey/xyz |
|
Chrome | Chrome/xyz |
Chromium/xyz 或 Edg.*/xyz |
Chromium | Chromium/xyz |
|
Safari | Safari/xyz * |
Chrome/xyz 或 Chromium/xyz |
Opera 15+(基于 Blink 引擎) | OPR/xyz |
|
Opera 12-(基于 Presto 引擎) | Opera/xyz |
* Safari 提供两个版本号:一个技术版本号在 Safari/xyz
令牌中,一个用户友好版本号在 Version/xyz
令牌中。
当然,绝对不能保证其他浏览器在某些情况下不会伪造这些信息。这就是为什么使用用户代理字符串进行浏览器检测是不可靠的,并且只能在检查版本号的情况下进行(伪造旧版本不太可能)。
操作系统检测
操作系统在大多数 UA 字符串中发送(尽管不是以网络为中心的平台),但格式各异。它是一个固定字符串,位于两个分号之间,在用户代理的注释部分,这些字符串特定于每个浏览器。
它们指示操作系统,通常还包括其版本以及有关依赖硬件的信息(32 位或 64 位、Mac 的 Intel/PPC,或 Windows PC 的 x86/ARM CPU 架构)。在以下示例中,它是字符串 Intel Mac OS X 10.15
。
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:138.0) Gecko/20100101 Firefox/138.0
在所有情况下,这些字符串都可能发生变化,因此您应该只在检测已发布的浏览器时使用它们,以便提前了解模式。考虑进行访问者或 UA 字符串调查,以便在新浏览器版本发布时调整您的代码。
移动设备、平板电脑或桌面
执行用户代理嗅探最常见的原因是确定浏览器在哪种类型的设备上运行。
- 切勿假设浏览器或渲染引擎只在一种设备类型上运行。特别是,不要依赖不同浏览器或渲染引擎的不同默认设置。
- 切勿使用 OS 令牌来定义浏览器是在移动设备、平板电脑还是桌面上。OS 可能在多种设备类型上运行(例如,Android 在平板电脑和手机上都运行)。
下表总结了常见浏览器厂商指示其浏览器在移动设备上运行的方式:
浏览器 | 规则 | 示例 |
---|---|---|
Mozilla (Gecko, Firefox) | 注释中包含 Mobile 或 Tablet 。 |
Mozilla/5.0 (Android 15; Mobile; rv:136.0) Gecko/136.0 Firefox/136.0 |
基于 WebKit (Android, Safari) | 注释外包含 Mobile Safari 令牌。 |
Mozilla/5.0 (Linux; U; Android 4.0.3; de-ch; HTC Sensation Build/IML74K) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30 |
基于 Blink (Chromium, Google Chrome, Opera 15+, Android 上的 Edge) | 注释外包含 Mobile Safari 令牌。 |
Mozilla/5.0 (Linux; Android 4.4.2; Nexus 5 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.117 Mobile Safari/537.36 OPR/20.0.1396.72047 |
Windows 10 Mobile 上的 Edge | 注释外包含 Mobile/xyz 和 Edge/ 令牌。 |
Mozilla/5.0 (Windows Phone 10.0; Android 6.0.1; Xbox; Xbox One) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Mobile Safari/537.36 Edge/16.16299 |
简而言之,你可以在 UA 字符串中的任何位置查找字符串 Mobi
。如果设备足够大,没有被标记为 Mobi
,你应该提供桌面网站(作为最佳实践,桌面设备也应该支持触摸输入,因为桌面设备可能有触摸屏)。
另见
- CSS 媒体查询
- HTTP 客户端提示
- 实现特性检测
- 迁移到用户代理客户端提示 on web.dev (2021)