有序规则,因为顺序就是问题的全部
Edge 发送完整的 Chrome 标记,并在末尾追加 Edg。Opera 追加 OPR。Samsung Internet 则把自己的标记放在 Chrome 之前而不是之后。Chrome 发送 Safari。这里的每张规则表都从上到下读取,第一个匹配项胜出,所以 Edge 就是 Edge,Opera 就是 Opera,Chrome 也不会再被报告为 Safari。
粘贴一个 User Agent,即可得到浏览器、引擎、操作系统和设备;也可以一键读取你自己的。然后逐个标记阅读这串字符,因为浏览器对自己的描述大多从 1994 年起就是假的,而这个页面会告诉你是哪些部分。
浏览器:Chrome 131.0.0.0;平台:Windows 10 或 11;渲染引擎:Blink 131.0.0.0。设备类型:台式机或笔记本电脑。已读取 6 个标记。
你每按一次键,下方所有内容都会在此标签页中重新计算。字符串永远不会被发送到任何地方。
111 / 2,048 个字符 · 6 个标记
已载入示例:Windows 上的 Chrome。基准示例。五个标记中有四个是历史填充。
Windows 10 或 11 · Blink 131.0.0.0 · 台式机或笔记本电脑
各个产品之间用空格分隔,平台细节放在括号里并用分号分隔。每一段都标出了它真正表达的意思。
Mozilla/5.0遗留Netscape 兼容性前缀
每个主流浏览器至今仍以此开头,而它们都不是 Mozilla。1994 年 Netscape 自称 Mozilla;Internet Explorer 复制了这个前缀,好让为 Netscape 编写的服务器也给它提供好页面;此后所有浏览器都照抄了 Internet Explorer。5.0 自 1998 年以来就没变过。
(Windows NT 10.0; Win64; x64)注释注释块
平台细节。字符串关于操作系统、架构和设备的所有说明,都放在这样的括号里,并用分号分隔。
Windows NT 10.0真实Windows 内核版本
Windows 10 和 Windows 11 发送的都是 NT 10.0。User Agent 中没有任何内容能区分它们,所以 Microsoft 建议改用 Client Hints 的 platformVersion。
Win64真实进程架构
浏览器构建版本的位数,而不是机器的位数。
x64真实进程架构
浏览器构建版本的位数,而不是机器的位数。
AppleWebKit/537.36遗留冻结的 WebKit 构建号
Chromium 于 2013 年从 WebKit 分叉,此后一直使用 Blink。537.36 是分叉那一刻的构建号,因为网站会嗅探它而被保留下来,十多年来没有任何一个 Chrome 版本包含那份代码。
(KHTML, like Gecko)注释注释块
平台细节。字符串关于操作系统、架构和设备的所有说明,都放在这样的括号里,并用分号分隔。
KHTML, like Gecko遗留两个声明,都是假的
WebKit 最初是 KDE 的 KHTML 的分叉,所以前半部分是一条血统说明,二十年前就已不再属实。后半部分是为了让嗅探 Gecko 的脚本走现代路径而加上的。Chromium 两者都不是,却仍然两个都发送。
Chrome/131.0.0.0真实Chromium 版本(已精简)
主版本号是真实的。其后的所有数字都被 UA 精简清零了:Chrome 不再公布次版本号、构建号和补丁号,以免这个字符串被用作指纹。如果你确实需要其余部分,请向 Client Hints 索取 uaFullVersionList。
Safari/537.36遗留Safari 兼容性声明
Chrome、Edge、Opera 以及其他所有 Chromium 系浏览器都会追加这一项,好让为 Safari 编写的服务器端嗅探代码继续生效。这个数字与冻结的 AppleWebKit 构建号一致,而不对应任何曾经发布过的 Safari。
每一行都显示了它所依据的子字符串。字符串没有声明的内容会报告为“未声明”,而不是自行填补。
Chrome/131.0.0.0AppleWebKit/537.36Windows NT 10.0Windows NTWin64; x64上面的每个字段都是自我报告的
User Agent 是客户端自己选择发送的字符串。修改它只需在开发者工具里改一个设置、给 curl 加一个参数,或在任意 HTTP 库里写一行代码。这里的一切都未经验证、未经认证,也都不能用来做安全决策。
在这里无法识别 Brave、Arc 和大多数 Chromium 分支
它们原样发送 Chrome 标记,不添加任何自己的内容。Brave 是有意这样做的,为的是缩小指纹识别面。报告一个猜测比报告 Chrome 更糟,所以这里显示的是 Chrome。
在页面加载后从 navigator 中读取。它不会被发送到任何地方,也不在页面源代码中。
读取你自己的 User Agent 需要 JavaScript,所以它会在页面加载后片刻显示。如果一直为空,说明此标签页屏蔽了脚本。
User-Agent Client Hints 是上方字符串的替代方案:结构化的字段,按名称索取,敏感字段需要显式请求才会提供。Chromium 系浏览器实现了它,Safari 和 Firefox 都拒绝实现。
正在检查是否支持。
可以直接粘贴到缺陷报告里的结构:包含规则填写的每个字段,规则拒绝猜测的地方则为 null。
{
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
"browser": {
"name": "Chrome",
"version": "131.0.0.0"
},
"engine": {
"name": "Blink",
"version": "131.0.0.0"
},
"os": {
"name": "Windows",
"version": "10 或 11"
},
"device": {
"type": "desktop",
"vendor": null,
"model": null
},
"cpu": {
"architecture": "x86,64 位"
},
"agentKind": "browser",
"bot": null,
"tokens": [
{
"raw": "Mozilla/5.0",
"kind": "product",
"truth": "legacy",
"label": "Netscape 兼容性前缀"
},
{
"raw": "(Windows NT 10.0; Win64; x64)",
"kind": "comment",
"truth": "real",
"label": "注释块"
},
{
"raw": "AppleWebKit/537.36",
"kind": "product",
"truth": "legacy",
"label": "冻结的 WebKit 构建号"
},
{
"raw": "(KHTML, like Gecko)",
"kind": "comment",
"truth": "legacy",
"label": "注释块"
},
{
"raw": "Chrome/131.0.0.0",
"kind": "product",
"truth": "real",
"label": "Chromium 版本(已精简)"
},
{
"raw": "Safari/537.36",
"kind": "product",
"truth": "legacy",
"label": "Safari 兼容性声明"
}
]
}这些字符串能难倒简单粗糙的解析器。载入一个,看看是哪个标记决定了答案。
五个几乎相同的字符串,以及决定答案的细微差别。
陷阱集中的地方:iOS 引擎、Android 精简,以及一台自称 Macintosh 的 iPad。
爬虫、一台游戏主机,以及最短的诚实 User Agent。
User Agent 是客户端自己选择发送的字符串,在开发者工具里改一个设置、给 curl 加一个参数就能改掉。它无法认证任何东西,无法拦截任何东西,也不能指望它告诉你设备真的是它所声称的那样。
直接问浏览器某个功能能否使用,而不是问它叫什么。CSS.supports、对对象的属性检查以及 @supports 都能回答真正的问题,而且在编写代码时还不存在的浏览器上,它们依然能给出正确答案。
Chrome 已经冻结了次版本号、macOS 版本和 Android 型号。Client Hints 用你按需请求的命名字段取代了这个字符串。任何建立在旧字符串上的东西,都建立在一个正在被关闭的数字之上。
本页的每一项解读都由随页面提供的规则表在你的浏览器中生成:不上传任何字符串,不调用任何查询服务,也不存储任何内容。限制会明确说明而不是暗中处理:最多读取 2,048 个字符,超出部分会被忽略,页面也会提示。如果某个标记不匹配任何规则,它会被报告为“未识别”并按原样显示,因为一个言之凿凿却错误的厂商,比一个诚实的空缺更糟。
User Agent 是一叠兼容性声明,由三十年来各家浏览器为了获得与当时领先者相同的页面而层层堆积。Netscape 自称 Mozilla;Internet Explorer 照抄它,以便获得框架页面;Safari 加上了 KHTML 和 like Gecko;Chrome 加上了 AppleWebKit 和 Safari,以便拿到为 WebKit 准备的页面;Edge、Opera 和 Samsung Internet 又在 Chrome 的全部标记之上加了各自的标记。从来没有任何东西被删除过,因为删掉任何一项都会让某处的某台服务器出错。本页用有序的规则表读取字符串,让后出现的声明在应当胜出时胜出,而不是让最先出现的声明说了算:Edge 不是 Chrome,Opera 不是 Chrome,Chrome 不是 Safari,iPhone 上的 Chrome 是 WebKit,Googlebot 也不是 Android 手机。然后它会标注每个标记,让真正携带信息的两三个片段与其余十个毫无信息的片段并排可见。这一切都是随页面一起提供、在你的标签页中运行的规则表:没有依赖,没有查询服务,也不发出任何请求。
任何 User Agent 都可以:从服务器日志里复制的、缺陷报告里的,或者你自己的(页面加载后,按钮会从 navigator 中读取)。十五个真实示例一键即可载入,之所以选它们,是因为每一个都能难倒凭记忆写成的解析器。
面板会列出浏览器、引擎、操作系统、设备类型、厂商、型号和架构。下方的列表逐段解读字符串,说明每一段的含义;这很重要,因为现代 User Agent 的大部分内容根本没有任何意义。
标为“未声明”的字段,是字符串从未携带的字段;标为“未识别”的标记,是这里没有任何规则认领的标记。两者本身都是答案。在 Client Hints 能填补空缺的地方,你自己字符串旁边的面板会显示它们在此浏览器中返回的值。
Edge 发送完整的 Chrome 标记,并在末尾追加 Edg。Opera 追加 OPR。Samsung Internet 则把自己的标记放在 Chrome 之前而不是之后。Chrome 发送 Safari。这里的每张规则表都从上到下读取,第一个匹配项胜出,所以 Edge 就是 Edge,Opera 就是 Opera,Chrome 也不会再被报告为 Safari。
这是其他解析器跳过的部分,也正是这个格式如此令人困惑的原因。Mozilla/5.0 是 1994 年的 Netscape 兼容性声明。AppleWebKit/537.36 冻结在 Chromium 分叉的那一刻。KHTML, like Gecko 是两个声明,而且都是假的。Chrome 上的 Safari/537.36 是在请求不要收到糟糕的页面。每一项都在其所在位置标注出来。
iPhone 上的 Chrome 是 CriOS,底层是 WebKit。iPhone 上的 Firefox 是 FxiOS,底层同样是 WebKit。默认模式下的 iPad 会报告为 Mac。Googlebot 的智能手机字符串里包含一个完整的 Chrome User Agent。Android 上的 Chrome 现在无论在哪台设备上,都报告 Android 10 和型号 K。所有这些都能被正确读取,并在出现的位置加以解释。
Brave 和 Arc 原样发送 Chrome 标记,不添加任何内容,这是有意为之,因此它们会被报告为 Chrome 并附上说明,而不是被瞎猜。不匹配任何规则的标记会按原样显示。为了填满一栏而编造的厂商,比空着的一栏更糟。
一个按钮读取 navigator.userAgent,旁边的面板读取 navigator.userAgentData:品牌、是否移动设备、平台,以及其背后的高熵值。真实的 Chrome 版本、真实的 Android 型号,以及区分 Windows 11 和 10 的内部版本号,其实都在这里。
不用 ua-parser-js,也不用查询服务。分词器只做一次正向扫描,其中没有任何正则表达式;版本号从固定索引开始逐个字符读取;每条规则都是字面字符串查找,因此任何输入都不会引发回溯。输入上限为 2,048 个字符,截断时页面会明确提示。
它是浏览器在每次请求的 User-Agent 标头中放入的那一行,用来向服务器描述自己。按下上方工作区中的按钮,本页就会从 navigator.userAgent 中读出它并加以拆解。这个过程不会上传任何内容,读取发生在标签页内。一个典型的 User Agent 是 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36,这六个片段中只有两个(平台注释和 Chrome 版本)说的是真话。其余都是兼容性沉积物,没有哪个浏览器敢删掉。
源于 1994 年的一个决定,以及此后三十年谁也无法撤销它。Netscape 的内部代号是 Mozilla,它以 Mozilla/2.0 作为自己的身份。服务器开始在提供框架页面之前检查这个标识,因为 Netscape 支持框架而竞争对手不支持。Internet Explorer 2 支持框架后却拿不到好页面,于是它发送 Mozilla/2.0 (compatible; MSIE 2.0),从而得到了正确的页面。此后每个浏览器都照抄当时的领先者:Safari 加上了 Mozilla 和 KHTML,Chrome 加上了 Mozilla 和 Safari,Edge 加上了 Mozilla、Chrome 和 Safari。如今这个前缀只表示发送方是一个网页浏览器,而 5.0 自 1998 年以来就没变过。谁要是去掉它,就会发现某处总有某台服务器给它返回一个 1999 年的页面。
原因与 Internet Explorer 声称自己是 Netscape 相同。Chrome 在 2008 年发布时使用的是 Safari 背后的引擎 WebKit,而大量服务器端嗅探代码都把 WebKit 等同于 Safari。发送 AppleWebKit 和 Safari 标记能让 Chrome 拿到与 Safari 相同的页面,所以它就发送了。2013 年 Chromium 从 WebKit 分叉出 Blink,却照样继续发送这两个标记,因为那时删掉它们会让网站出问题。所以 AppleWebKit/537.36 是分叉那一刻的构建号,十多年来没有任何一个 Chrome 版本包含那份代码;而 Safari/537.36 也从来不是任何真实存在过的 Safari。实际后果是:解析器必须先检测 Chrome 再检测 Safari,否则每个 Chrome 用户都会被算作 Safari 用户。
Edg 表示基于 Chromium 的 Microsoft Edge,也就是 2020 年以来的所有 Edge。它故意只拼三个字母。旧版 Edge 发送的是 Edge/18.19041,而大量功能检测代码都是为了匹配字符串 Edge、并把它当作带有自己一套 bug 的 EdgeHTML 浏览器来处理而写的。如果推出一个能匹配这些检查的 Chromium 浏览器,就会让它套用并不需要的 EdgeHTML 兼容补丁,所以 Microsoft 选了一个当时还没有任何代码在匹配的标记。Android 上的 Edge 是 EdgA,iOS 上的 Edge 是 EdgiOS,手写解析器常常漏掉这两个。三种情况下,这个标记都位于一个完整的 Chrome 标记之后,因此从左到右读取、并在识别出第一个浏览器时就停下的解析器,会把 Edge 报告为 Chrome。
因为 Apple 在 iPadOS 13 中决定让 iPad 默认获取桌面版网站,而实现方式就是发送桌面字符串。如今 iPad 上的 Safari 发送 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7),其中没有任何 iPad 标记,与 Mac 上的 Safari 逐字节相同。仅凭字符串无法区分两者,而这是有意为之。如果你必须知道,线索在页面里而不在标头里:在冒充 Mac 的 iPad 上,navigator.maxTouchPoints 返回大于零的数字,而在几乎所有真正的 Mac 上返回零。只有当用户为该网站选择了“请求移动版网站”时,iPad 标记才会重新出现。
不可信,而且这不是附加说明,而是这个格式的本质属性。字符串由客户端自行选择。修改它只需在任意浏览器开发者工具的网络状况面板里改一个字段、给 curl 加一个 -A 参数,或在任意 HTTP 库里写一行代码。没有任何东西为它签名,没有任何东西验证它,这个标头也不附带任何保证。它适合用于数据分析,因为少量胡乱数据不会改变数据的整体形态;也适合用于选择下载链接,因为最坏的结果不过是装错了安装包。它绝不能用作访问控制、许可证检查、防机器人手段或任何其他安全决策。如果你需要确认某个请求真的来自 Googlebot,Google 正是为此公布了反向 DNS 范围和一份 JSON 格式的地址列表,对照它们进行验证,才是对待这一声明唯一可靠的方式。
它们是整个格式的替代方案。浏览器不再用一个字符串携带所有信息,不管网站想不想要;而是在每次请求中发送三个低熵字段(品牌列表、是否为移动设备以及平台名称),其余信息则一律保留,直到网站通过 Accept-CH 响应标头或 JavaScript 中的 navigator.userAgentData.getHighEntropyValues 点名索取。高熵值才是有用的部分:完整的浏览器版本、平台版本、架构、位数和设备型号。有两个细节值得了解。品牌列表中故意包含一个编造的品牌,名称每个版本都会变化,这样代码就必须正确解析整个列表,而不是匹配一个写死的名称,而后者恰恰是让旧字符串无法修复的那个错误。另外,支持并不普遍:Chromium 系浏览器实现了它,而 Safari 和 Firefox 都拒绝实现,这就是为什么每个浏览器发送的仍然是旧字符串。
这是 Google 分阶段推进的计划,自 2023 年的 Chrome 110 起已基本完成,目的是冻结字符串中让它成为指纹识别面的那些部分。Chrome 现在把次版本号、构建号和补丁号都报告为 0.0.0,所以完整版本读作 131.0.0.0,只有主版本号是真实的。在桌面端,平台版本被固定为一个定值。在 Android 上,每台设备都报告 Android 10 和型号 K,不管它实际是什么。Apple 更早做到了这一点,而且走的是另一条路:自 Big Sur 起,macOS 一直报告 10.15.7,因为大量嗅探代码按数值比较版本号,遇到 11 就会出错;而且在 Apple Silicon 上仍然发送 Intel 一词。结果是,这个字符串如今作为信号远比读取它的代码通常以为的要弱,任何需要真实值的场景都必须通过 Client Hints 去索取。
在 Chrome 和 Edge 中,打开开发者工具,通过三点菜单依次显示“更多工具”和“网络状况”,取消勾选“使用浏览器默认设置”,然后选择或输入一个字符串。Firefox 在 about:config 中提供 general.useragent.override。Safari 的“开发”菜单里有“用户代理”子菜单。在命令行中,curl 用 curl -A,wget 用 --user-agent,而每个 HTTP 库都有等效的一行选项。正当用途确实存在:测试你自己的网站是否给旧浏览器提供了正确内容、复现只有某个特定客户端才会遇到的 bug,以及查看爬虫收到的是什么。真正重要的是反过来想。既然你改起来这么容易,别人改起来也一样容易,所以你在服务器上写的任何检查都不能假定这个字符串是诚实的。为了绕过付费墙或速率限制而伪造它,与其说是技术问题,不如说是服务条款问题;而且这通常也会失败,因为严肃的系统早在多年前就不再信任这个标头了。
它的界面、同步和地址栏确实是 Chrome 的,但引擎不是。Apple 要求 iOS 和 iPadOS 上的每个浏览器都使用系统 WebKit 渲染,因此 Chrome iOS 版、Firefox iOS 版、Edge iOS 版和 Opera iOS 版底层都是 Safari,只是外壳不同。它们的标记分别是 CriOS、FxiOS、EdgiOS 和 OPiOS,每一个所在的字符串里也都带有 AppleWebKit 和一个 Safari 构建号,而在这种情况下,它们是真实的,而非装饰。对测试而言,实际后果才是有用的部分:在 iPhone 上的 Chrome 中报告的渲染 bug 就是 WebKit 的 bug,它会在同一设备的 Safari 中复现,而不会在其他任何平台的 Chrome 中复现。欧盟的《数字市场法》已开始放宽这一限制,但在此之外,这条规则依然成立。
探索不断扩充的工具合集,涵盖计算、文档、写作和日常工作。