前端埋点日志解决方案:完整 User-Agent 解析工具实现,爬虫识别、设备品牌、操作系统、浏览器版本全解析(附完整源码)

一只会飞的鱼儿 2小时前 ⋅ 2 阅读
ad

 做Web开发、日志分析、风控拦截、用户运营的开发者,大概率都踩过 User-Agent解析 的坑!

看似简单的一串UA字符串,藏着无数难题:杂乱无章的字符难以识别设备、主流爬虫伪装真实用户无法甄别、开源库识别率低、小众设备直接返回「未知」、系统版本匹配错乱……

市面上多数通用UA解析库,要么规则老旧长期不更新,要么识别维度单一,无法适配国内海量机型、小众系统以及各类爬虫伪装场景,导致日志数据失真、风控误判、用户画像偏差、适配逻辑失效等一系列线上问题。

今天给大家分享一套自研全能BrowserInfo解析工具,基于Node.js开发,无多余依赖、规则全面、识别精度拉满,完美解决爬虫识别、设备品牌解析、操作系统匹配、浏览器版本拆解等核心痛点,开箱即用,适配绝大多数线上业务场景!

一、为什么不推荐通用开源UA解析库?

深耕Web日志解析与设备识别场景多年,总结出通用解析工具的三大核心痛点,也是绝大多数项目的共性难题:

1. 爬虫识别缺失,伪装流量无法甄别

百度、字节、谷歌、必应、社交平台等各类爬虫,会伪装成正常用户UA访问站点,通用库无法精准识别,导致统计流量虚高、真实用户数据被污染,SEO分析、流量统计完全失真。

2. 国产设备/系统识别率极低

通用库大多适配海外机型,对华为、小米、OPPO、VIVO、荣耀、一加等国产主流手机,统信UOS、鸿蒙生态等国产系统识别不全,大量设备被统一归类为「Android」「未知」,设备精细化分析无从谈起。

3. 解析维度单一,数据杂乱不规范

多数工具仅能简单识别浏览器和系统,无法区分桌面端设备品牌、移动端具体机型,系统版本映射混乱,日志字段残缺,需要二次加工,大幅增加开发工作量。

4. 容错性差,线上易报错

针对空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业务场景:

1. 用户行为日志分析:精准统计不同设备、系统、浏览器的用户访问数据,剔除爬虫流量,数据更真实可信

2. 前端自适应适配:根据设备、系统、浏览器类型,动态适配页面样式、功能开关、兼容逻辑

3. 风控安全拦截:精准识别爬虫、恶意伪装访问,助力接口风控、防刷、防爬虫策略落地

4. 精细化用户运营:基于设备品牌、机型、系统版本构建用户画像,支撑精准运营、场景化推送

5. 线上问题溯源:根据用户设备、浏览器信息,快速复现、定位机型兼容、版本适配类线上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
}

关于Webfunny

Webfunny专注于前端监控系统,前端埋点系统的研发。 致力于帮助开发者快速定位问题,帮助企业用数据驱动业务,实现业务数据的快速增长。支持H5/Web/PC前端、微信小程序、支付宝小程序、UniApp和Taro等跨平台框架。实时监控前端网页、前端数据分析、错误统计分析监控和BUG预警,第一时间报警,快速修复BUG!支持私有化部署,Docker容器化部署,可支持千万级PV的日活量!

  点赞 1   收藏 1
  • 一只会飞的鱼儿
    共发布84篇文章 获得10个收藏
全部评论: 0