在上一篇讲 H5 唤端的文章里,我们多次用到”通过 UA 判断是否在微信环境”这样的判断。这个 UA,就是 User-Agent(用户代理)。它是前端识别运行环境、做兼容适配、埋点统计乃至风控反爬的基石。但它同时也是 Web 历史上最”混乱”的一个字段——充满了历史包袱、伪装与妥协。这篇文章把 User-Agent 从概念、结构、历史到现代演进和实战用法一次讲透。

一、User-Agent 是什么

User-Agent,字面意思是”用户代理”,指代替用户去访问网络资源的软件本身。 广义上,浏览器、爬虫、命令行工具(curl、wget)、App 内的 WebView、甚至邮件客户端,都是”用户代理”。

在 HTTP 协议里,User-Agent 是一个请求头(Request Header)。每当浏览器向服务器发起请求,都会在请求头里带上这样一行字符串,用来自报家门——告诉服务器”我是谁、我基于什么内核、跑在什么操作系统上”。

一个典型的现代 Chrome UA 长这样:

1
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36

在前端 JS 里,可以通过 navigator.userAgent 读到当前浏览器的这串字符串:

1
2
console.log(navigator.userAgent);
// "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..."

服务端(如 Node、Nginx 日志、后端框架)则从 HTTP 请求头里读取它。

二、为什么这串字符串如此混乱?——一段”军备竞赛”式的历史

第一眼看这串 UA,你一定会困惑:明明是 Chrome,为什么开头写着 Mozilla?还带着 AppleWebKit、KHTML、Gecko、Safari?这不是自相矛盾吗?

答案藏在一段近三十年的历史里,这是一场典型的”兼容性军备竞赛”。

最早,Mosaic 浏览器的 UA 是 NCSA_Mosaic/2.0。

Netscape 出现后,代号 Mozilla(Mosaic Killer),UA 写作 Mozilla/1.0。当时 Netscape 支持框架(frames)等新特性,很多网站便通过 UA 判断”是不是 Mozilla”,是才发送带框架的高级页面。

微软 IE 后来居上,也支持了框架。但网站的判断逻辑只认 Mozilla,IE 如果如实报自己是 MSIE,就会被网站当成低级浏览器,只收到简化页面。于是微软干脆在自己的 UA 里也塞进 Mozilla,伪装成兼容:Mozilla/4.0 (compatible; MSIE ...)。

从此,UA 造假成了行业默认玩法。 后来的每一个新浏览器,为了不被老网站的嗅探逻辑歧视,都会把前辈的标识”继承”进自己的 UA:

  • Firefox 用 Gecko 引擎,UA 里带 Gecko 和 Mozilla。
  • Safari 用 KHTML 衍生的 WebKit 引擎,为了兼容为 KHTML/Gecko 写的网站,写成 AppleWebKit ... (KHTML, like Gecko),同时保留 Mozilla 和 Safari。
  • Chrome 早期同样用 WebKit,为了兼容 Safari 生态,UA 里既有 AppleWebKit 又有 Safari,还加上自己的 Chrome;即便后来 Chrome 换成了自研的 Blink 引擎,为了不破坏无数网站的嗅探逻辑,仍然保留了 AppleWebKit/537.36。
  • Edge(Chromium 内核后)则在末尾追加 Edg/xxx。

所以那串看似矛盾的 UA,其实是一层层”我兼容前辈”的声明叠加。理解了这段历史,就明白 UA 为什么不可能被简单解析——它天生就是为了”骗过”嗅探逻辑而设计的。

三、User-Agent 的结构拆解

尽管混乱,现代浏览器 UA 大体遵循一个可拆解的结构:

1
Mozilla/5.0 (平台/系统信息) 引擎标识 (引擎细节) 浏览器标识/版本 附加标识

以 Chrome 的 UA 为例逐段拆解:

片段 含义
Mozilla/5.0 历史遗留的”兼容令牌”,几乎所有浏览器都以此开头
(Windows NT 10.0; Win64; x64) 操作系统与架构信息(Windows 10、64 位)
AppleWebKit/537.36 渲染引擎标识(WebKit 版本)
(KHTML, like Gecko) 兼容声明,表示”像 Gecko 一样工作”
Chrome/120.0.0.0 真正的浏览器名称与版本
Safari/537.36 为兼容 Safari 生态保留的标识

移动端 UA 会额外带上设备信息,例如 iPhone 上的 Safari:

1
Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1

其中 iPhone; CPU iPhone OS 17_0 标明了设备与 iOS 版本,Mobile/15E148 是移动端标识。

而 App 内嵌 WebView 通常会在系统 UA 基础上追加自定义标识,这也是我们能识别微信、抖音等环境的关键:

  • 微信内置浏览器:末尾带 MicroMessenger/8.x.x
  • QQ 内置浏览器:带 QQ/ 或 MQQBrowser
  • 很多自家 App 会追加类似 MyApp/3.2.1 的自定义段

四、前端如何用 UA 做环境判断

尽管 UA 不可靠(下文会讲),它至今仍是前端识别环境最常用、有时甚至是唯一的手段,尤其在无法用特性检测替代的场景(如识别具体是哪个 App 的 WebView)。

常见判断的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const ua = navigator.userAgent.toLowerCase();

// 是否移动端
const isMobile = /mobile|android|iphone|ipad|ipod/.test(ua);

// 具体系统
const isIOS = /iphone|ipad|ipod/.test(ua);
const isAndroid = /android/.test(ua);

// 是否在微信内置浏览器
const isWeChat = /micromessenger/.test(ua);

// 是否在 QQ 内
const isQQ = /\sqq\//.test(ua) || /mqqbrowser/.test(ua);

// 提取 Android 系统版本
const androidVer = ua.match(/android\s([\d.]+)/)?.[1];

回到唤端场景,前面提到的”微信里要引导用户到浏览器打开”,正是靠 /micromessenger/.test(ua) 这一句判断出来的。

五、UA 的核心问题:它不可信

用 UA 做判断,必须清醒地知道它有两个根本缺陷。

第一,UA 可以被任意伪造。 它只是一串请求头文本,浏览器的开发者工具、命令行工具、爬虫、自动化框架都能随意修改它。爬虫伪装成正常浏览器、开发者调试时切换设备,都是改 UA。所以任何依赖 UA 做安全判断(如风控、鉴权)的逻辑都是不可靠的,UA 只能作为辅助信号,不能作为唯一依据。

第二,UA 嗅探(UA Sniffing)容易过时和误判。 用正则匹配 UA 来判断浏览器能力,是一种脆弱的做法:新浏览器、新版本、新设备层出不穷,硬编码的判断逻辑很容易漏判或错判。历史上无数网站因为 UA 判断写死,导致新浏览器访问时被误当成”不支持”而降级——这正是当年各家浏览器不得不在 UA 里造假的根源。

因此业界的最佳实践是:能用特性检测(Feature Detection)就不要用 UA 嗅探。 判断浏览器是否支持某个能力,应该直接检测那个能力本身,而不是猜浏览器型号:

1
2
3
4
5
6
7
8
9
// ❌ 不好:靠 UA 猜能力
if (/chrome/.test(ua)) { useWebGL(); }

// ✅ 更好:直接检测能力是否存在
if ('IntersectionObserver' in window) {
// 用这个 API
} else {
// 走 polyfill 或降级
}

UA 嗅探只应保留给那些特性检测无法覆盖的场景,比如识别”当前是不是微信 WebView””是不是某台特定机型”,这类信息没法通过检测某个 API 得到。

六、现代演进:Client Hints 与 UA 的”去精细化”

正因为 UA 又长、又乱、又不可靠,还携带了大量可用于指纹追踪(fingerprinting)的隐私信息,业界正在推动它的替代与瘦身。

User-Agent Client Hints(UA-CH) 是 Chrome 主导的新机制。核心思路是:不再把所有信息一股脑塞进一个 UA 字符串,而是拆成一组独立的、按需索取的请求头(如 Sec-CH-UA、Sec-CH-UA-Platform、Sec-CH-UA-Mobile),服务器需要哪部分信息就声明请求哪部分,浏览器默认只给出粗粒度信息(如品牌和大版本号),精确信息(如完整版本、机型)需显式请求。

在 JS 侧对应的是 navigator.userAgentData API:

1
2
3
4
5
6
7
8
9
10
if (navigator.userAgentData) {
console.log(navigator.userAgentData.mobile); // 是否移动端(布尔值)
console.log(navigator.userAgentData.platform); // "Windows" / "Android" 等
console.log(navigator.userAgentData.brands); // 品牌与版本数组

// 高熵值信息需异步、按需获取
navigator.userAgentData.getHighEntropyValues([
'platformVersion', 'model', 'fullVersionList'
]).then(info => console.log(info));
}

与此配套,Chrome 正在推进”UA 缩减(User-Agent Reduction)”:逐步冻结和简化传统 navigator.userAgent 里的细节,比如把系统小版本、设备型号统一成固定占位值,减少可被用于追踪的信息量。这意味着未来直接从 UA 字符串里抠精确版本、机型会越来越不可靠,需要迁移到 Client Hints。

需要注意的是,UA-CH 目前主要在 Chromium 系浏览器落地,Safari、Firefox 支持有限;而且它只在 https 下可用。所以现阶段仍是”传统 UA + Client Hints”并存的过渡期,做兼容适配时两者都要考虑。

七、实战建议小结

把上面的内容落到实处,用 UA 时可以遵循几条原则:

优先特性检测,UA 嗅探作为补充。 判断”能不能用某个能力”时用特性检测;只有判断”运行在哪个具体环境/App里”这类信息,才用 UA。

不要用 UA 做安全决策。 风控、鉴权、防刷不能只信 UA,它太容易伪造,只能作为多信号中的一个弱信号。

UA 解析交给成熟库。 自己写正则解析 UA 极易出错且难维护,服务端可用 ua-parser-js 这类库,它们持续跟进新设备和浏览器。

为 Client Hints 做准备。 新项目在需要环境信息时,优先考虑 navigator.userAgentData,并在服务端准备好读取 Sec-CH-UA-* 请求头,同时保留传统 UA 作为不支持环境的兜底。

移动端与 WebView 场景重点关注自定义标识。 微信、QQ、自家 App 的 WebView 识别,是国内业务里 UA 判断最不可替代的用途,把这些标识的匹配规则维护清楚,并做好兼容降级。


User-Agent 是 Web 世界里一个典型的”历史包袱”:它诞生于善意(自报身份以做适配),却在浏览器间的兼容竞赛中变得冗长、矛盾、不可信。理解它的结构和历史,能帮你在需要时正确地使用它;而认清它的局限,则能帮你避免把关键逻辑建立在这根并不牢靠的柱子上。面向未来,特性检测与 Client Hints 才是更可靠的方向,UA 字符串则会慢慢退居为一个逐渐瘦身的兼容遗产。