做Web开发、日志分析、风控拦截、用户运营的开发者,大概率都踩过 User-Agent解析 的坑!
看似简单的一串UA字符串,藏着无数难题:杂乱无章的字符难以识别设备、主流爬虫伪装真实用户无法甄别、开源库识别率低、小众设备直接返回「未知」、系统版本匹配错乱......
市面上多数通用UA解析库,要么规则老旧长期不更新,要么识别维度单一,无法适配国内海量机型、小众系统以及各类爬虫伪装场景,导致日志数据失真、风控误判、用户画像偏差、适配逻辑失效等一系列线上问题。
今天给大家分享一套自研全能BrowserInfo解析工具,基于Node.js开发,无多余依赖、规则全面、识别精度拉满,完美解决爬虫识别、设备品牌解析、操作系统匹配、浏览器版本拆解等核心痛点,开箱即用,适配绝大多数线上业务场景!
一、为什么不推荐通用开源UA解析库?
深耕Web日志解析与设备识别场景多年,总结出通用解析工具的三大核心痛点,也是绝大多数项目的共性难题:
- 爬虫识别缺失,伪装流量无法甄别
百度、字节、谷歌、必应、社交平台等各类爬虫,会伪装成正常用户UA访问站点,通用库无法精准识别,导致统计流量虚高、真实用户数据被污染,SEO分析、流量统计完全失真。
- 国产设备/系统识别率极低
通用库大多适配海外机型,对华为、小米、OPPO、VIVO、荣耀、一加等国产主流手机,统信UOS、鸿蒙生态等国产系统识别不全,大量设备被统一归类为「Android」「未知」,设备精细化分析无从谈起。
- 解析维度单一,数据杂乱不规范
多数工具仅能简单识别浏览器和系统,无法区分桌面端设备品牌、移动端具体机型,系统版本映射混乱,日志字段残缺,需要二次加工,大幅增加开发工作量。
- 容错性差,线上易报错
针对空UA、残缺UA、非常规格式UA字符串,通用库极易解析失败、抛出异常,影响整体业务接口稳定性。
二、自研解析工具核心能力,全方位碾压通用方案
这套工具针对线上真实场景打磨优化,补齐了行业通用短板,覆盖爬虫识别、设备品牌、设备名称、操作系统、浏览器信息五大核心解析能力,适配PC、移动端、爬虫全场景,精度、兼容性、稳定性全面升级。
✅ 能力1:全覆盖爬虫精准识别,杜绝流量污染
内置18+主流爬虫专属匹配规则,覆盖国内外主流搜索引擎、AI爬虫、社交平台爬虫:百度、字节跳动、谷歌、必应、苹果、雅虎、搜狗、360、神马、Yandex、Claude、Facebook、Twitter等。
不仅可以精准匹配爬虫名称,还能智能提取爬虫版本号,同时配置通用爬虫兜底规则,拦截未知伪装爬虫,彻底过滤虚假流量,保障日志统计、流量分析数据真实有效。
✅ 能力2:全品牌设备识别,告别「未知」设备
深度适配国内外全品类设备品牌,重点补齐国产设备识别短板:
▪ 移动端:华为、荣耀、小米、红米、OPPO、一加、真我、VIVO、iQOO、三星、魅族、中兴、努比亚、锤子等全品牌适配
▪ 桌面端:精准识别联想、戴尔、惠普、华硕、宏碁、微软、华为、小米等PC品牌
▪ 小众设备:兼容TCL、酷派、乐视、金立等小众机型,无遗漏、不归类
同时支持桌面系统精准区分,Windows、macOS、Chrome OS、统信UOS、Linux各发行版精准匹配,彻底解决设备识别模糊问题。
✅ 能力3:精细化设备名称解析,获取真实机型
区别于通用工具的粗略归类,本工具可从杂乱UA字符串中,智能清洗、提取用户真实设备型号名称,自动过滤wv后缀、Build冗余字段、多余空格,格式化输出标准化设备名称。
优先复用原始有效设备名,无效则自动解析补齐,兼顾准确性与兼容性,为用户画像、设备适配、故障溯源提供精准数据支撑。
✅ 能力4:系统+版本精准映射,告别版本混乱
精准解析iOS、Android、Windows、macOS、Chrome OS、统信UOS、Linux全系列操作系统,内置专属版本映射规则:
▪ Windows系统智能版本转换,将NT版本号自动映射为通俗易懂的Win7/8/10/11版本名称
▪ iOS、macOS自动替换下划线为标准小数点版本格式
▪ 精准识别统信UOS专业版、桌面版、服务器版等细分类型
解析结果规范统一,无需二次处理,直接用于数据统计、业务适配、版本兼容判断。
✅ 能力5:全场景浏览器识别,覆盖内外置应用
打破通用工具仅支持主流浏览器的局限,不仅适配Chrome、Safari、Edge、Firefox等常规浏览器,还全覆盖国内主流内置浏览器:微信、QQ、UC、百度、支付宝、钉钉、企业微信、小程序WebView。
同时兼容海外社交平台客户端内核识别,精准区分TikTok、YouTube、Facebook、Instagram等应用内置访问场景,全方位适配移动端生态。
✅ 能力6:高容错+高稳定,线上零风险
内置完整异常捕获机制,针对空UA、无效UA、残缺UA、异常格式UA等极端场景,不会抛出报错、不影响业务流程,自动兜底返回默认值,完美适配高并发线上生产环境。
三、核心业务适用场景,赋能各类开发需求
这套解析工具实用性极强,几乎适配所有需要解析客户端信息的Web业务场景:
-
用户行为日志分析:精准统计不同设备、系统、浏览器的用户访问数据,剔除爬虫流量,数据更真实可信
-
前端自适应适配:根据设备、系统、浏览器类型,动态适配页面样式、功能开关、兼容逻辑
-
风控安全拦截:精准识别爬虫、恶意伪装访问,助力接口风控、防刷、防爬虫策略落地
-
精细化用户运营:基于设备品牌、机型、系统版本构建用户画像,支撑精准运营、场景化推送
-
线上问题溯源:根据用户设备、浏览器信息,快速复现、定位机型兼容、版本适配类线上BUG
四、接入极简,开箱即用零成本
工具基于原生Node.js开发,零第三方依赖、轻量无侵入,支持按需引入所有方法,接入流程极简,无需复杂配置。
核心导出方法全覆盖:爬虫检测、设备品牌解析、设备名称解析、操作系统与版本解析、浏览器信息解析、一键批量解析日志信息。
支持单条UA解析、批量日志解析,兼容服务端所有Node.js项目,接入后可直接替换老旧解析方案,快速提升数据精度。
五、总结:告别解析凑活,用专业工具解决专业问题
多数项目的UA解析问题,从来不是技术难度高,而是通用工具规则老旧、场景适配不足、细节处理粗糙。
这套自研BrowserInfo解析工具,经过线上真实业务打磨,补齐了国产设备、小众系统、各类爬虫、内置浏览器的识别短板,以高精度、高兼容、高稳定、低侵入的优势,彻底解决UA解析的各类玄学问题。
如果你也被设备识别不准、爬虫流量干扰、日志数据混乱、适配兼容繁琐等问题困扰,这套工具绝对是最优替代方案!
工具特性速览
✅ 18+主流爬虫精准识别,拦截伪装虚假流量
✅ 全品类国产+海外设备品牌全覆盖
✅ 精细化设备机型清洗解析,标准化输出
✅ 全系统版本智能映射,格式统一规范
✅ 主流浏览器+国内内置浏览器全覆盖
✅ 零依赖、高容错、线上稳定无报错
✅ 开箱即用,极简接入,适配所有Node.js项目
Webfunny全链路监控埋点平台 是一站式前端监控 + 用户行为埋点 + 大数据分析平台,天然适配点位细查、用户行为回溯、批量导出等场景:
一体化架构:监控 + 埋点同一套 SDK,数据互通无壁垒
私有化部署:数据完全本地化,满足企业合规要求
高吞吐支撑:基于 ClickHouse 构建,亿级日志秒级查询
全端覆盖:H5 / 小程序 / APP / 鸿蒙全覆盖,统一导出口径
可定制强:支持接口扩展、分布式锁、限流降级等企业级能力
附源码:
/**
* browserInfo 解析工具
* 用于从 User Agent 中解析设备品牌、设备名称、操作系统、浏览器名称等信息
*/
/**
* 爬虫匹配规则列表
*/
const SPIDER_PATTERNS = [
{ pattern: /baiduspider(-render)?\/?([\d.]+)?/i, name: "百度爬虫" },
{ pattern: /bytespider\/?([\d.]+)?/i, name: "字节跳动爬虫" },
{ pattern: /googlebot\/?([\d.]+)?/i, name: "谷歌爬虫" },
{ pattern: /bingbot\/?([\d.]+)?/i, name: "必应爬虫" },
{ pattern: /applebot\/?([\d.]+)?/i, name: "苹果爬虫" },
{ pattern: /slurp\/?([\d.]+)?/i, name: "雅虎爬虫" },
{ pattern: /sosowebspider\/?([\d.]+)?/i, name: "搜搜爬虫" },
{ pattern: /sogou\s+web\s+spider\/?([\d.]+)?/i, name: "搜狗爬虫" },
{ pattern: /360spider\/?([\d.]+)?/i, name: "360爬虫" },
{ pattern: /yisouspider\/?([\d.]+)?/i, name: "神马搜索爬虫" },
{ pattern: /yandexbot\/?([\d.]+)?/i, name: "Yandex爬虫" },
{ pattern: /ahrefsbot\/?([\d.]+)?/i, name: "Ahrefs爬虫" },
{ pattern: /bitsightbot\/?([\d.]+)?/i, name: "BitSight爬虫" },
{ pattern: /claudebot\/?([\d.]+)?/i, name: "Claude爬虫" },
{ pattern: /facebookexternalhit\/?([\d.]+)?/i, name: "Facebook爬虫" },
{ pattern: /twitterbot\/?([\d.]+)?/i, name: "Twitter爬虫" },
{ pattern: /linkedinbot\/?([\d.]+)?/i, name: "LinkedIn爬虫" },
{ pattern: /(spider|crawler|bot)\/?[\d.]*?/i, name: "爬虫" }
]
/**
* 检测是否为爬虫
* @param {string} userAgent - User Agent 字符串
* @returns {object|null} 匹配的爬虫信息,包含 name 和 version
*/
function detectSpider(userAgent) {
if (!userAgent) return null
const agent = userAgent.toLowerCase()
for (let spiderPattern of SPIDER_PATTERNS) {
const spiderMatch = agent.match(spiderPattern.pattern)
if (spiderMatch) {
let version = spiderMatch[2] || spiderMatch[1] || ""
if (!version && spiderMatch[0]) {
const versionMatch = spiderMatch[0].match(/([\d.]+)/)
version = versionMatch ? versionMatch[1] : ""
}
return { name: spiderPattern.name, version }
}
}
return null
}
/**
* 根据浏览器信息判断设备品牌
* @param {string} browserInfo - 浏览器信息(User Agent)
* @returns {string} 设备品牌或系统名称
*/
function getPhoneBrandFromBrowserInfo(browserInfo) {
if (!browserInfo) {
return "未知"
}
const userAgent = browserInfo.toLowerCase()
// 先判断是否是桌面端系统
const isDesktop = /(windows nt|macintosh|mac os x|linux.*x86|linux.*x64)/i.test(userAgent) &&
!/android|mobile/i.test(userAgent)
if (isDesktop) {
// 桌面端系统 - 尝试识别品牌或返回系统名称
// Apple Mac系列
if (/macintosh|mac os x/i.test(userAgent)) {
return "Apple"
}
// Windows系统 - 尝试识别品牌(通常UA中无法识别,返回系统名称)
if (/windows nt/i.test(userAgent)) {
// 尝试识别一些可能的品牌标识
if (/lenovo/i.test(userAgent)) {
return "联想"
}
if (/dell/i.test(userAgent)) {
return "戴尔"
}
if (/hp|hewlett/i.test(userAgent)) {
return "惠普"
}
if (/asus/i.test(userAgent)) {
return "华硕"
}
if (/acer/i.test(userAgent)) {
return "宏碁"
}
if (/surface/i.test(userAgent)) {
return "微软"
}
if (/thinkpad/i.test(userAgent)) {
return "联想"
}
if (/huawei|matebook/i.test(userAgent)) {
return "华为"
}
if (/xiaomi/i.test(userAgent)) {
return "小米"
}
if (/honor/i.test(userAgent)) {
return "荣耀"
}
// 无法识别具体品牌,返回系统名称
return "Windows"
}
// Chrome OS - 需要单独检测,因为 Chrome OS 的 UA 不包含 "linux"
if (/cros/i.test(userAgent)) {
// Chrome OS 设备的 UA 中通常不包含品牌信息,但尝试识别一些可能的品牌
// 注意:大多数 Chromebook 的 UA 中不包含品牌,所以通常返回 "Chrome OS"
const uaUpper = browserInfo.toUpperCase()
if (/ACER|CHROMEBOOK.*ACER/i.test(uaUpper)) {
return "宏碁"
}
if (/ASUS|CHROMEBOOK.*ASUS/i.test(uaUpper)) {
return "华硕"
}
if (/DELL|CHROMEBOOK.*DELL/i.test(uaUpper)) {
return "戴尔"
}
if (/HP|HEWLETT|CHROMEBOOK.*HP/i.test(uaUpper)) {
return "惠普"
}
if (/LENOVO|CHROMEBOOK.*LENOVO/i.test(uaUpper)) {
return "联想"
}
if (/SAMSUNG|CHROMEBOOK.*SAMSUNG/i.test(uaUpper)) {
return "三星"
}
if (/TOSHIBA|CHROMEBOOK.*TOSHIBA/i.test(uaUpper)) {
return "东芝"
}
// 大多数情况下,Chrome OS 设备的 UA 中不包含品牌信息
// 返回 "Chrome OS" 作为统一的品牌标识
return "Chrome OS"
}
// Linux系统
if (/linux/i.test(userAgent) && !/android/i.test(userAgent)) {
// 统信 UOS(国产操作系统)
if (/uos/i.test(userAgent)) {
return "统信"
}
// Ubuntu
if (/ubuntu/i.test(userAgent)) {
return "Ubuntu"
}
// Fedora
if (/fedora/i.test(userAgent)) {
return "Fedora"
}
// 通用Linux
return "Linux"
}
return "PC"
}
// iPhone系列
if (/iphone|ipad|ipod/i.test(userAgent)) {
return "Apple"
}
// 华为系列(包括Honor荣耀)
if (/huawei|honor|harmonyos|hms/i.test(userAgent)) {
return "华为"
}
// OPPO系列(包括内部型号)
if (/oppo|oneplus|realme|pkg\d+|pkd\d+/i.test(userAgent)) {
return "OPPO"
}
// 三星系列
if (/samsung|sm-|galaxy/i.test(userAgent)) {
return "三星"
}
// 魅族系列
if (/meizu|flyme/i.test(userAgent)) {
return "魅族"
}
// 一加系列
if (/oneplus|pj[a-z]\d{3}/i.test(userAgent)) {
return "一加"
}
// 中兴系列
if (/zte|blade/i.test(userAgent)) {
return "中兴"
}
// 联想系列
if (/lenovo|moto/i.test(userAgent)) {
return "联想"
}
// TCL系列
if (/tcl/i.test(userAgent)) {
return "TCL"
}
// 努比亚系列
if (/\bnx|nubia/i.test(userAgent)) {
return "努比亚"
}
// 酷派系列
if (/coolpad/i.test(userAgent)) {
return "酷派"
}
// 锤子科技(坚果手机)
if (/smartisan|nut/i.test(userAgent)) {
return "锤子"
}
// 360手机
if (/360/i.test(userAgent) && /mobile/i.test(userAgent)) {
return "360手机"
}
// 乐视手机
if (/letv|leeco/i.test(userAgent)) {
return "乐视"
}
// 金立手机
if (/gionee/i.test(userAgent)) {
return "金立"
}
// VIVO系列
if (/vivo|iqoo|v\d{4}[a-z]*/i.test(userAgent)) {
return "VIVO"
}
// 小米系列(包括Redmi红米)
if (/\bmi\b|redmi|miui|xiaomi|2\d{4}[a-z0-9]+/i.test(userAgent)) {
return "小米"
}
// 如果是Android但没有匹配到具体品牌
if (/android/i.test(userAgent)) {
return "Android"
}
// 如果都没有匹配到
return "其他"
}
/**
* 根据浏览器信息获取设备名称
* @param {string} browserInfo - 浏览器信息(User Agent)
* @param {string} originDeviceName - 原始设备名称
* @returns {string} 设备名称
*/
function getDeviceNameFromBrowserInfo(browserInfo, originDeviceName) {
if (!browserInfo) {
return originDeviceName || "未知"
}
const ua = browserInfo
const agent = ua.toLowerCase()
// 爬虫检测
const spider = detectSpider(agent)
if (spider) {
return spider.name
}
// 如果原始设备名称有效,直接返回
if (originDeviceName && originDeviceName !== "undefined" && originDeviceName !== "PC" && originDeviceName.trim() !== "") {
return originDeviceName
}
const android = ua.match(/(Android);?[\s\/]+([\d.]+)?/)
const ipad = ua.match(/(iPad).*OS\s([\d_]+)/)
const iphone = !ipad && ua.match(/(iPhone\sOS)\s([\d_]+)/)
const mobileInfo = ua.match(/Android\s[\S\s]+Build\//)
// iOS设备
if (iphone) {
return "iPhone"
}
if (ipad) {
return "iPad"
}
// Android设备
if (android) {
let deviceName = ""
// 方法1:处理现代格式
const modernAndroidMatch = ua.match(/\(Linux;\s*Android\s+[\d.]+;\s*([^)]+)\)/i)
if (modernAndroidMatch && modernAndroidMatch[1]) {
deviceName = modernAndroidMatch[1].trim()
deviceName = deviceName.replace(/;\s*wv$/, '').trim()
}
// 方法2:尝试Build格式
if (!deviceName && mobileInfo) {
const info = mobileInfo[0]
const dNameArr = info.split(';')
for (let i = 0; i < dNameArr.length; i++) {
if (dNameArr[i].indexOf('Build') !== -1) {
deviceName = dNameArr[i].replace(/Build\//g, '')
break
}
}
if (deviceName === '' && dNameArr.length > 1) {
deviceName = dNameArr[1]
}
}
// 方法3:其他Android格式
if (!deviceName) {
const otherAndroidMatch = ua.match(/Android[\s\/][\d.]+[;\s]+([^;)]+)/i)
if (otherAndroidMatch && otherAndroidMatch[1]) {
deviceName = otherAndroidMatch[1].trim()
}
}
// 清理设备名称
if (deviceName) {
deviceName = deviceName.replace(/(^\s*)|(\s*$)/g, '')
deviceName = deviceName.replace(/Build\//g, '')
deviceName = deviceName.replace(/;\s*wv$/, '')
}
return deviceName || "Android"
}
// 桌面端
if (/(windows nt|macintosh|mac os x|linux)/i.test(agent) && !/android|mobile/i.test(agent)) {
return "PC"
}
return originDeviceName || "未知"
}
/**
* 根据浏览器信息获取操作系统和版本号
* @param {string} browserInfo - 浏览器信息(User Agent)
* @param {string} originOs - 原始操作系统
* @param {string} originOsVersion - 原始操作系统版本
* @returns {object} { os: 操作系统名称, osVersion: 版本号 }
*/
function getOsFromBrowserInfo(browserInfo, originOs, originOsVersion) {
if (!browserInfo) {
return { os: originOs || "未知", osVersion: originOsVersion || "" }
}
const ua = browserInfo
const agent = ua.toLowerCase()
// 爬虫检测
const spider = detectSpider(agent)
if (spider) {
return { os: spider.name, osVersion: spider.version }
}
// 如果原始os有效且不是web,直接返回
if (originOs && originOs !== "web" && originOs.trim() !== "" && originOsVersion && originOsVersion.trim() !== "") {
return { os: originOs, osVersion: originOsVersion || "" }
}
// iOS
const iosMatch = ua.match(/(iPhone|iPad|iPod).*?(?:iPhone\s+)?OS\s+([\d_]+)/i)
if (iosMatch) {
const version = iosMatch[2].replace(/_/g, '.')
return { os: "iOS", osVersion: version }
}
// Android
const androidMatch = ua.match(/Android\s([\d.]+)/i)
if (androidMatch) {
return { os: "Android", osVersion: androidMatch[1] }
}
// Windows
const windowsMatch = ua.match(/Windows NT ([\d.]+)/i)
if (windowsMatch) {
const versionMap = {
'10.0': '10/11',
'6.3': '8.1',
'6.2': '8',
'6.1': '7',
'6.0': 'Vista',
'5.1': 'XP',
'5.0': '2000'
}
const version = versionMap[windowsMatch[1]] || windowsMatch[1]
return { os: "Windows", osVersion: version }
}
// macOS
const macMatch = ua.match(/Mac OS X ([\d_]+)/i)
if (macMatch) {
const version = macMatch[1].replace(/_/g, '.')
return { os: "macOS", osVersion: version }
}
// Chrome OS
const chromeOSMatch = ua.match(/CrOS\s+(?:x86_64|armv7l|aarch64)\s+([\d.]+)/i)
if (chromeOSMatch) {
return { os: "Chrome OS", osVersion: chromeOSMatch[1] }
}
// 统信 UOS(国产操作系统)
const uosMatch = ua.match(/UOS\s+(?:Professional|Desktop|Server|Enterprise)?/i)
if (uosMatch) {
// 尝试提取版本信息,UOS UA 中可能包含版本号
const uosVersionMatch = ua.match(/UOS\s+(?:Professional|Desktop|Server|Enterprise)\s*([\d.]+)?/i)
const version = uosVersionMatch && uosVersionMatch[1] ? uosVersionMatch[1] : ""
// 如果没有版本号,尝试提取类型(Professional/Desktop/Server/Enterprise)
if (!version && uosMatch[0]) {
const typeMatch = uosMatch[0].match(/(Professional|Desktop|Server|Enterprise)/i)
return { os: "统信 UOS", osVersion: typeMatch ? typeMatch[1] : "" }
}
return { os: "统信 UOS", osVersion: version }
}
// Linux
if (/linux/i.test(agent) && !/android/i.test(agent)) {
return { os: "Linux", osVersion: "" }
}
return { os: originOs || "未知", osVersion: originOsVersion || "" }
}
/**
* 根据浏览器信息获取浏览器名称和版本
* @param {string} browserInfo - 浏览器信息(User Agent)
* @param {string} originBrowserName - 原始浏览器名称
* @param {string} originBrowserVersion - 原始浏览器版本
* @returns {object} { browserName, browserVersion }
*/
function getBrowserInfoFromUA(browserInfo, originBrowserName, originBrowserVersion) {
if (!browserInfo) {
return { browserName: originBrowserName || "未知", browserVersion: originBrowserVersion || "" }
}
const agent = browserInfo.toLowerCase()
// 爬虫检测 - 优先检测爬虫
const spider = detectSpider(agent)
if (spider) {
return { browserName: spider.name, browserVersion: spider.version }
}
// 如果原始浏览器名称有效,直接返回
if (originBrowserName && originBrowserName !== "其他" && originBrowserName !== "undefined" && originBrowserName.trim() !== "") {
return { browserName: originBrowserName, browserVersion: originBrowserVersion || "" }
}
let browserName = "未知"
let browserVersion = ""
// 微信浏览器
if (agent.indexOf("micromessenger") > 0) {
const wechatMatch = agent.match(/micromessenger\/([\d.]+)/i)
browserName = "微信"
browserVersion = wechatMatch && wechatMatch[1] ? wechatMatch[1] : ""
}
// QQ浏览器
else if (agent.indexOf("mqqbrowser") > 0) {
const qqMatch = agent.match(/mqqbrowser\/([\d.]+)/i)
browserName = "QQ"
browserVersion = qqMatch && qqMatch[1] ? qqMatch[1] : ""
}
// UC浏览器
else if (agent.indexOf("ucbrowser") > 0 || agent.indexOf("ubrowser") > 0) {
const ucMatch = agent.match(/u?c?browser\/([\d.]+)/i)
browserName = "UC"
browserVersion = ucMatch && ucMatch[1] ? ucMatch[1] : ""
}
// 百度浏览器
else if (agent.indexOf("baidubrowser") > 0) {
const baiduMatch = agent.match(/baidubrowser\/([\d.]+)/i)
browserName = "百度"
browserVersion = baiduMatch && baiduMatch[1] ? baiduMatch[1] : ""
}
// 支付宝内置浏览器
else if (agent.indexOf("alipayclient") > 0 || agent.indexOf("alipay") > 0) {
const alipayMatch = agent.match(/alipay(?:client)?\/([\d.]+)/i)
browserName = "支付宝"
browserVersion = alipayMatch && alipayMatch[1] ? alipayMatch[1] : ""
}
// 钉钉内置浏览器
else if (agent.indexOf("dingtalk") > 0) {
const dingMatch = agent.match(/dingtalk\/([\d.]+)/i)
browserName = "钉钉"
browserVersion = dingMatch && dingMatch[1] ? dingMatch[1] : ""
}
// 企业微信
else if (agent.indexOf("wxwork") > 0) {
const wxworkMatch = agent.match(/wxwork\/([\d.]+)/i)
browserName = "企业微信"
browserVersion = wxworkMatch && wxworkMatch[1] ? wxworkMatch[1] : ""
}
// 小程序WebView
else if (agent.indexOf("miniprogram") > 0) {
browserName = "小程序"
browserVersion = ""
}
// iOS 第三方应用(需要在 Safari 检测之前)
else if (agent.indexOf("twitter") > 0) {
const twitterMatch = agent.match(/twitter\s+for\s+(?:iphone|ipad)\/([\d.]+)/i)
browserName = "Twitter"
browserVersion = twitterMatch && twitterMatch[1] ? twitterMatch[1] : ""
}
else if (agent.indexOf("facebook") > 0 && agent.indexOf("fban") > 0) {
const fbMatch = agent.match(/fban\/([\d.]+)/i) || agent.match(/fbios\/([\d.]+)/i)
browserName = "Facebook"
browserVersion = fbMatch && fbMatch[1] ? fbMatch[1] : ""
}
else if (agent.indexOf("instagram") > 0) {
const instagramMatch = agent.match(/instagram\s*([\d.]+)/i)
browserName = "Instagram"
browserVersion = instagramMatch && instagramMatch[1] ? instagramMatch[1] : ""
}
else if (agent.indexOf("linkedinapp") > 0) {
const linkedinMatch = agent.match(/linkedinapp\/([\d.]+)/i)
browserName = "LinkedIn"
browserVersion = linkedinMatch && linkedinMatch[1] ? linkedinMatch[1] : ""
}
else if (agent.indexOf("line") > 0 && agent.indexOf("line/") > 0) {
const lineMatch = agent.match(/line\/([\d.]+)/i)
browserName = "Line"
browserVersion = lineMatch && lineMatch[1] ? lineMatch[1] : ""
}
else if (agent.indexOf("weibo") > 0) {
const weiboMatch = agent.match(/weibo\s*([\d.]+)/i) || agent.match(/weibo__([\d.]+)/i)
browserName = "微博"
browserVersion = weiboMatch && weiboMatch[1] ? weiboMatch[1] : ""
}
else if (agent.indexOf("youtube") > 0) {
const youtubeMatch = agent.match(/youtube\/([\d.]+)/i)
browserName = "YouTube"
browserVersion = youtubeMatch && youtubeMatch[1] ? youtubeMatch[1] : ""
}
else if (agent.indexOf("tiktok") > 0) {
const tiktokMatch = agent.match(/tiktok\/([\d.]+)/i)
browserName = "TikTok"
browserVersion = tiktokMatch && tiktokMatch[1] ? tiktokMatch[1] : ""
}
// IE
else if (agent.indexOf("msie") > 0) {
const ieMatch = agent.match(/msie ([\d.]+)/i)
browserName = "ie"
browserVersion = ieMatch && ieMatch[1] ? ieMatch[1].replace(';', '') : ""
}
// Edge
else if (agent.indexOf("edg") > 0) {
const edgeMatch = agent.match(/edg\/([\d.]+)/i)
browserName = "edge"
browserVersion = edgeMatch && edgeMatch[1] ? edgeMatch[1] : ""
}
// Firefox
else if (agent.indexOf("firefox") > 0) {
const ffMatch = agent.match(/firefox\/([\d.]+)/i)
browserName = "firefox"
browserVersion = ffMatch && ffMatch[1] ? ffMatch[1] : ""
}
// Safari (不是Chrome)
else if (agent.indexOf("safari") > 0 && agent.indexOf("chrome") < 0) {
const safariMatch = agent.match(/version\/([\d.]+)/i) || agent.match(/safari\/([\d.]+)/i)
browserName = "safari"
browserVersion = safariMatch && safariMatch[1] ? safariMatch[1] : ""
}
// Chrome
else if (agent.indexOf("chrome") > 0) {
const chromeMatch = agent.match(/chrome\/([\d.]+)/i)
browserName = "chrome"
browserVersion = chromeMatch && chromeMatch[1] ? chromeMatch[1] : ""
// 检测360浏览器
if (agent.indexOf("360se") > 0) {
const browser360Match = agent.match(/360se\/([\d.]+)/i)
browserName = "360"
browserVersion = browser360Match && browser360Match[1] ? browser360Match[1] : browserVersion
}
}
return { browserName, browserVersion }
}
/**
* 重新解析CUSTOMER_PV日志的browserInfo
* @param {object} logInfo - 日志对象
* @param {string} browserInfo - 浏览器UA信息
* @returns {object} 更新后的logInfo
*/
function parseBrowserInfo(logInfo, browserInfo) {
if (!browserInfo) {
return logInfo
}
try {
// 解析手机品牌 weDeviceBrand
logInfo.weDeviceBrand = logInfo.weDeviceBrand ? logInfo.weDeviceBrand : getPhoneBrandFromBrowserInfo(browserInfo)
// 解析设备名称 weDeviceName
const originDeviceName = logInfo.weDeviceName
if (!originDeviceName || originDeviceName === "undefined" || originDeviceName === "PC" || originDeviceName.trim() === "") {
logInfo.weDeviceName = getDeviceNameFromBrowserInfo(browserInfo, originDeviceName)
}
// 解析操作系统 os 和 osVersion
const originOs = logInfo.weOs
const originOsVersion = logInfo.weOsVersion
if (!originOs || originOs === "web" || originOs.trim() === "" || !originOsVersion || originOsVersion.trim() === "") {
const { os, osVersion } = getOsFromBrowserInfo(browserInfo, originOs, originOsVersion)
logInfo.weOs = os
logInfo.weOsVersion = osVersion
}
// 解析浏览器名称 browserName 和版本 browserVersion
const originBrowserName = logInfo.weBrowserName
const originBrowserVersion = logInfo.weBrowserVersion
if (!originBrowserName || originBrowserName === "其他" || originBrowserName === "undefined" || originBrowserName.trim() === "") {
const { browserName, browserVersion } = getBrowserInfoFromUA(browserInfo, originBrowserName, originBrowserVersion)
logInfo.weBrowserName = browserName
logInfo.weBrowserVersion = browserVersion
}
} catch (e) {
console.log("parseBrowserInfo error:", e)
}
return logInfo
}
module.exports = {
detectSpider,
getPhoneBrandFromBrowserInfo,
getDeviceNameFromBrowserInfo,
getOsFromBrowserInfo,
getBrowserInfoFromUA,
parseBrowserInfo
}