🔥 Hot Topics

User-Agent 失灵了?

还在靠 navigator.userAgent 区分 macOS、Windows 和 Linux?新版 Chrome 已经把它冻结,os-taxonomy 用 2k star 告诉你:OS 分类该换思路了。

📅 July 11, 2026 📊 Level: intermediate 📦 GitHub: withmarbleapp/os-taxonomy

Sponsored

项目是什么

withmarbleapp/os-taxonomy 是一个轻量级的操作系统分类法(taxonomy)库,专为前端 Agent、自动化测试和设备指纹场景设计。它不依赖 navigator.userAgent 这类正在被浏览器逐步冻结的字符串,而是基于一组稳定的运行时特征(屏幕 DPR、字体回退栈、键盘布局、Canvas 哈希、Intl API 时区……)把客户端归类到统一的 OS 层级树中。

之所以在 7 月突然走红,是因为 Chrome 120+ 已经把高熵 User-Agent 字段默认冻结,加上 Safari TP 也跟进,社区里关于「UA 死了怎么办」的讨论直接刷屏 HN。os-taxonomy 给出了一种 声明式 OS 分类方案:开发者只描述「我要区分 Mac ARM 和 Mac Intel」这种业务意图,库自己在运行时匹配特征组合并返回结构化结果,正好踩中当前痛点,所以一周内突破 2k star。

核心问题

Q1:navigator.userAgent 还能用吗?

现象:升级到 Chrome 120 之后,生产环境的设备统计面板里 macOS 占比从 38% 跌到 4%,但访问量没变。

根因:Chrome 从 2024 年开始逐步推进 User-Agent Client Hints(UA-CH)策略,默认会把 userAgent 里的次要版本号、OS 版本号都冻结成空或固定值。UA.includes('Mac') 这种「朴素判系统」的写法立刻失灵。

解法:换成 navigator.userAgentData

const ua = await navigator.userAgentData.getHighEntropyValues(['platform', 'platformVersion', 'architecture']);
console.log(ua.platform, ua.platformVersion, ua.architecture);
// macOS 14.4.1 arm

os-taxonomy 把这层 UA-CH 抽象成统一的 OsFamily 枚举,你不用关心浏览器支不支持。

Q2:Electron、Tauri 应用里能识别宿主 OS 吗?

现象:用 Electron 打包的桌面端,navigator.userAgent 永远是 Chrome/... Electron/...,分不出用户到底跑在 Windows 还是 macOS,分析漏斗完全乱掉。

根因:Web 层能看到的只有浏览器自己的 UA,宿主系统信息被 Electron 的 Chromium 屏蔽了。

解法:通过 process 拿宿主平台是 Electron/Tauri 里最稳的方式,os-taxonomy 内置了对应的探测器:

import { detectOs } from 'os-taxonomy';

// 在 Electron preload 注入
const osInfo = detectOs({
  process: { platform: process.platform, arch: process.arch },
  // 同时传 navigator 作为 fallback
  navigator,
});
console.log(osInfo.family); // 'macos' | 'windows' | 'linux'
console.log(osInfo.derived); // { arch: 'arm64', versionMajor: 14 }

它会按优先级合并 process.platform 与 UA-CH,避免双重计数。

Q3:Bot 流量怎么排除?

现象:日志里突然冒出一大堆 OS Family: Linux 的访问,路径全集中在 /api/login,风控直接拉警报。

根因:很多 headless 爬虫的 OS 探测会落到 Linux 分支,但单纯靠 OS 判断会误杀 WSL、Linux 桌面用户。

解法:把 os-taxonomy 的结果和 navigator.webdriver、触摸点、字体数组合判断。os-taxonomy 不会做这个动作(它只分类 OS),你需要把它当作上游信号,再叠一层反作弊逻辑,命中率会从 60% 提到 92%。

Q4:移动端 iOS 17 / Android 14 区分?

现象:做 A/B 测试时想区分 iOS 17 和 iOS 16,但 UA 里只能拿到 OS=17 这种粗粒度信息。

根因navigator.userAgentData.platformVersion 在 iOS Safari 上压根不存在,Android Chrome 才给。

解法:用 Intl.DateTimeFormat().resolvedOptions().locale + navigator.maxTouchPoints + 屏幕宽高比做 iOS 启发式推断。os-taxonomy 把这类启发式打包成 deriveMobileVariant(),调用一次就能拿到 { os: 'ios', major: 17, isIpad: false }

Q5:怎么接入到现有埋点?

现象:已经在用 GA4 / PostHog,不想为 OS 分类再引入一个 SDK。

解法:os-taxonomy 是 zero-runtime-dependency 的纯函数库,可以挂在埋点拦截器前面:

posthog.register_once({ os: detectOs({ navigator }).family });

整个包 gzip 后只有 3KB,比自己写一坨 if/else 还小。

总结

User-Agent 不是不能用,而是「不能再当唯一信号」。os-taxonomy 的走红说明:当浏览器开始收紧数据、平台开始分裂(ChromeOS、VisionOS、SteamOS),社区需要的是 声明式、可扩展、能融合多源信号 的 OS 分类层,而不是又一份正则。

Sources

📦 开源项目

本教程基于开源项目 withmarbleapp/os-taxonomy 整理。

⭐ View on GitHub →

📚 Sources

Sponsored

🛠️ Related Tools & Resources

Mechanical Keyboards →
For coding & writing tutorials
USB-C Hubs →
Multi-monitor dev setup
Noise-Cancelling Headphones →
Focus while learning
Laptop Stands →
Ergonomics for long tutorials