使用使用者代理字串(UA 嗅探)進行瀏覽器檢測

每次向伺服器發出請求時,瀏覽器都會包含一個 User-Agent HTTP 標頭,其值稱為使用者代理(UA)字串。此字串旨在標識瀏覽器、其版本號及其主機作業系統。

http
User-Agent: <product> / <product-version> <comment>

你也可以透過 JavaScript 中的 navigator.userAgent 屬性訪問此字串。

js
console.log(window.navigator.userAgent);
// Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:138.0) Gecko/20100101 Firefox/138.0

嘗試解析 UA 字串(有時稱為“UA 嗅探”)並根據 UA 字串中的值改變網站行為可能會很誘人,但這很難可靠地完成,並且通常會導致錯誤。

本文件描述了使用 UA 字串進行瀏覽器檢測的常見陷阱以及推薦的替代方案。最後,我們提供了一些使用字串進行 UA 檢測的提示,但你應該只在絕對必要時才這樣做!

為什麼特性檢測優於瀏覽器檢測

為了說明為什麼根據瀏覽器調整站點功能會引入複雜性和可能的錯誤,請考慮以下示例。一個應用程式希望使用 JavaScript 中的 splitUpString() 函式,並使用後行斷言?<=…)。

js
let splitUpString;

if (navigator.userAgent.includes("Chrome")) {
  const camelCaseExpression = new RegExp("(?<=[A-Z])");
  splitUpString = (str) => String(str).split(camelCaseExpression);
} else {
  // This fallback is inefficient, but it works
  splitUpString = (str) =>
    String(str)
      .split(/(.*?[A-Z])/)
      .filter(Boolean);
}
console.log(splitUpString("fooBar")); // ["fooB", "ar"]
console.log(splitUpString("jQWhy")); // ["jQ", "W", "hy"]

此程式碼做了幾個可能錯誤的假設,如果它在錯誤的瀏覽器或瀏覽器版本上執行,將破壞程式碼。

  1. 所有包含子字串 Chrome 的使用者代理字串都表示 Chrome 瀏覽器。

    基於 UA 字串的瀏覽器檢測最大的問題之一是,瀏覽器和使用者代理經常假裝是其他瀏覽器,或包含基於多個瀏覽器的資訊。

  2. 如果瀏覽器是 Chrome,則後行特性始終可用。實際上,瀏覽器可能是支援此特性之前的舊版 Chrome,或者可能是後續版本中刪除了此特性。

  3. 最重要的是,它假設沒有其他瀏覽器支援該特性,而該特性可能隨時新增到任何其他瀏覽器中。所有不匹配的瀏覽器將不得不使用效率低下的備用方案。

需要注意的是,這些問題將無論瀏覽器檢測方法如何都存在;UA 嗅探、客戶端提示、其他 HTTP 標頭的存在、缺失或內容等等。知道使用哪個瀏覽器無關緊要,在這種情況下我們實際尋找的是特性檢測,這將在下面更詳細地描述。

UA 嗅探的替代方案

以下部分描述了瀏覽器檢測的替代方案,這些方案比 UA 嗅探更健壯,並且適用於更多場景。

特性檢測

特性檢測是指你檢查客戶端是否支援某個特定特性,而不是弄清楚是哪個瀏覽器在渲染你的頁面。對於不支援某個特性的情況,你可以使用備用方案。以下特性檢測示例檢查客戶端是否支援 地理位置 API。你可以透過檢查全域性 Navigator 物件上是否提供了 geolocation 屬性來完成此操作。

js
if ("geolocation" in navigator) {
  navigator.geolocation.getCurrentPosition((position) => {
    // show the location on a map, such as the Google Maps API
  });
} else {
  // Show a static map instead
}

你可以對許多特性執行此操作。例如,你可以確定是否可以內聯檢視 PDF 檔案,或者是否支援 虛擬鍵盤 API,等等。

js
if ("application/pdf" in navigator.mimeTypes) {
  // browser supports inline viewing of PDF files.
}
if ("virtualKeyboard" in navigator) {
  // browser supports the Virtual Keyboard API
}

對於樣式,你還可以在 CSS 中使用 @supports 規則進行特性檢測,如果你想檢查某個特性是否存在,可以結合使用 not 關鍵字。有關在 CSS 中使用此方法的更多資訊,請參閱使用特性查詢

css
@supports (display: grid) {
  .box {
    display: grid;
    gap: 2rem;
  }
}

@supports not (property: value) {
  /* CSS rules for fallback */
}

在極少數情況下,如果不同瀏覽器對某個特性的行為不同,你應該測試瀏覽器如何實現 API,並根據此來確定如何使用它。要了解更多資訊,請參閱實現特性檢測文件。

移動裝置檢測

濫用 UA 嗅探(和客戶端提示)的常見情況是檢測客戶端是否為移動裝置。通常人們實際上是希望檢測使用者的裝置是否支援觸控且螢幕較小,這樣他們就可以透過為按鈕新增額外的填充等方式來最佳化他們的網站。

相反,你應該使用現代 API 進行特性檢測。例如,要檢查觸控支援,請嘗試 Navigator 介面中的 maxTouchPoints 屬性。

js
if (navigator.maxTouchPoints > 1) {
  // browser supports multi-touch
}

對於其他問題,如佈局,請使用現代 CSS,如 flexboxgrid,以實現靈活佈局。不要在小螢幕上隱藏內容,而是動態調整佈局。媒體查詢應該處理大多數佈局更改,減少對基於 JavaScript 的調整的需求。

如果您想確保使用者旋轉裝置或在不同螢幕模式之間切換時平滑過渡,可以檢視檢測裝置方向。對於可摺疊裝置,有更新的 API,例如裝置姿態 API,但請務必檢查相容性資料,因為支援情況差異很大。

客戶端提示

對於基於 Blink 的瀏覽器(Chromium、Edge、Brave、Vivaldi 等),另一種選擇是使用者代理客戶端提示。在客戶端提示中,伺服器透過 HTTP 標頭或JavaScript API 主動從客戶端請求裝置資訊。

客戶端提示在檢測基於 Blink 的瀏覽器方面優於 UA 嗅探,因為它們不那麼容易被偽造,並且提供更小、更可靠的資訊,這些資訊更容易解析。根據客戶端提示更改站點功能仍然是一個壞主意!在可能的情況下,您應該如上所述使用特性檢測和漸進增強。

例如,在 HTTP 機制中,伺服器會隨響應包含 Accept-CH 標頭以及客戶端在後續請求中應包含的標頭列表。假設伺服器向客戶端傳送此響應:

http
Accept-CH: Sec-CH-UA-Mobile, Sec-CH-UA-Platform, Sec-CH-UA

這會請求客戶端在後續請求中傳送以下標頭:

  • Sec-CH-UA-Mobile:一個布林值,指示客戶端是否為移動裝置。
  • Sec-CH-UA-Platform:客戶端正在執行的平臺(“Windows”、“Android”等)。
  • Sec-CH-UA:使用者代理的品牌和重要版本資訊。

假設客戶端支援客戶端提示,則 UA 客戶端提示可能會出現在後續請求中:

http
GET /my/page HTTP/1.1
Host: example.site

Sec-CH-UA: " Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96"
Sec-CH-UA-Mobile: ?1
Sec-CH-UA-Platform: "Android"

要了解更多關於客戶端提示的資訊,請參閱HTTP 客戶端提示。在使用此功能之前,請務必檢視瀏覽器相容性詳情以獲取更多資訊。

其他技術和原則

漸進增強

這種設計技術涉及以“分層”的方式開發您的網站,採用自下而上的方法,從一個更簡單的層開始,並在後續層中改進網站的功能,每一層都使用更多功能。

優雅降級

這是一種自上而下的方法,您使用所有您想要的功能構建最佳網站,然後對其進行調整以使其在舊版瀏覽器上執行。這可能比漸進增強更難做到,效果也更差,但在某些情況下可能會有用。

使用瀏覽器檢測的無效理由

如果您仍然在考慮瀏覽器檢測而不是特性檢測和漸進增強,請檢查您是否受到以下(無效的)原因的驅動:

您正在嘗試解決特定瀏覽器版本中的特定錯誤。

您不太可能是第一個遇到它的人。專家或持有不同觀點的人可能會給您提示,以更好地避免或解決該錯誤。如果問題不常見,值得檢查該錯誤是否已透過錯誤跟蹤系統(MozillaWebKitBlinkOpera)報告給瀏覽器廠商。瀏覽器製造商確實會關注錯誤報告,您的報告可能有助於修復或為問題提供更可靠的解決方案。

根據訪問者的瀏覽器提供不同的 HTML。

這通常不是個好主意,但在極少數情況下是必要的。您可以透過新增非語義的 <div><span> 元素來避免嗎?考慮您的設計是否存在問題:您可以使用漸進增強或流式佈局來幫助消除對這樣做的需求嗎?與重構 HTML 相比,準確應用 UA 檢測的努力應該是一個決定性因素。

試圖找出訪問者的瀏覽器是否具有特定功能。

您的網站需要使用某些瀏覽器尚不支援的特定功能,並且您希望為使用不相容瀏覽器的使用者提供一個功能較少但您知道可以執行的舊版網站。這是使用 UA 檢測的最糟糕的原因,因為所有瀏覽器最終都可能會趕上。此外,以這種方式為每個瀏覽器測試不同的功能是不切實際的。

提取相關的 UA 字串部分

如果您已經探索了上述所有選項,並且仍然需要解析 UA 字串作為最後的手段,本節有一些提示會有所幫助。不幸的是,使用者代理字串的不同部分沒有統一性,因此我們進入了棘手的部分。

渲染引擎

共享共同渲染引擎的瀏覽器將以相同的方式顯示頁面:通常可以合理地假設在一個瀏覽器中有效的方法在另一個瀏覽器中也有效。目前有三個主要的渲染引擎:BlinkGeckoWebKit

在以下示例中,渲染引擎是字串 Gecko/20100101,表示渲染引擎是 Gecko,而“gecko-Trail”是固定字串 20100101,表示“桌面”。

http
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:138.0) Gecko/20100101 Firefox/138.0

檢測渲染引擎名稱在網站中很常見,許多使用者代理歷來都添加了其他渲染引擎名稱,以避免網站僅根據渲染引擎名稱而將其排除。因此,在檢測渲染引擎時,務必注意不要觸發誤報,因為此方法特別不可靠。考慮以下在 macOS 上 Chrome 134 中傳送的 UA 字串:

http
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
引擎 必須包含 詳情
Blink Chrome/xyz
Gecko Gecko/xyz
WebKit AppleWebKit/xyz WebKit 瀏覽器會新增一個 like Gecko 字串,如果檢測不仔細,可能會對 Gecko 造成誤報。
Presto Opera/xyz 已廢棄;Presto 在 Opera 瀏覽器版本 >= 15 中不再使用(請參閱“Blink”)。
EdgeHTML Edge/xyz 已廢棄;EdgeHTML 在 Edge 瀏覽器版本 >= 79 中不再使用(請參閱“Blink”)。

渲染引擎版本

大多數渲染引擎將版本號放在 RenderingEngine/VersionNumber 令牌中,但 Gecko 是一個顯著的例外。在以下示例中,它是字串 rv:138.0,表示渲染引擎版本號是 138.0,這與 Firefox 版本相同。

http
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:138.0) Gecko/20100101 Firefox/138.0

瀏覽器名稱和版本

當人們說他們想要“瀏覽器檢測”時,他們實際上通常想要“渲染引擎檢測”。這通常意味著檢測“Gecko”或“WebKit”,而不是“Firefox”或“Safari”。

大多數瀏覽器以 BrowserName/VersionNumber 的格式設定名稱和版本。但是由於名稱不是使用者代理字串中該格式的唯一資訊,您無法發現瀏覽器的名稱,您只能檢查您正在查詢的名稱是否存在。在以下示例中,瀏覽器名稱是字串 Firefox/138.0,表示瀏覽器名稱是 Firefox,軟體版本是 138.0

http
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:138.0) Gecko/20100101 Firefox/138.0

有些瀏覽器傳送相互矛盾的資訊:例如,Chrome 既報告 Chrome 又報告 Safari。因此,要檢測 Safari,您必須檢查 Safari 字串,並且沒有 Chrome 字串,Chromium 通常也報告自己是 Chrome,而 SeaMonkey 報告自己是 Firefox。

在使用正則表示式處理 BrowserName 部分時要小心,因為使用者代理還包含圍繞關鍵字/值語法的字串。例如,Safari 和 Chrome 都包含字串 like Gecko

瀏覽器名稱 必須包含 不得包含
Firefox Firefox/xyz Seamonkey/xyz
Seamonkey Seamonkey/xyz
Chrome Chrome/xyz Chromium/xyzEdg.*/xyz
Chromium Chromium/xyz
Safari Safari/xyz * Chrome/xyzChromium/xyz
Opera 15+(基於 Blink 引擎) OPR/xyz
Opera 12-(基於 Presto 引擎) Opera/xyz

* Safari 提供兩個版本號:一個技術版本號在 Safari/xyz 令牌中,一個使用者友好版本號在 Version/xyz 令牌中。

當然,絕對不能保證其他瀏覽器在某些情況下不會偽造這些資訊。這就是為什麼使用使用者代理字串進行瀏覽器檢測是不可靠的,並且只能在檢查版本號的情況下進行(偽造舊版本不太可能)。

作業系統檢測

作業系統在大多數 UA 字串中傳送(儘管不是以網路為中心的平臺),但格式各異。它是一個固定字串,位於兩個分號之間,在使用者代理的註釋部分,這些字串特定於每個瀏覽器。

它們指示作業系統,通常還包括其版本以及有關依賴硬體的資訊(32 位或 64 位、Mac 的 Intel/PPC,或 Windows PC 的 x86/ARM CPU 架構)。在以下示例中,它是字串 Intel Mac OS X 10.15

http
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:138.0) Gecko/20100101 Firefox/138.0

在所有情況下,這些字串都可能發生變化,因此您應該只在檢測已釋出的瀏覽器時使用它們,以便提前瞭解模式。考慮進行訪問者或 UA 字串調查,以便在新瀏覽器版本釋出時調整您的程式碼。

移動裝置、平板電腦或桌面

執行使用者代理嗅探最常見的原因是確定瀏覽器在哪種型別的裝置上執行。

  • 切勿假設瀏覽器或渲染引擎只在一種裝置型別上執行。特別是,不要依賴不同瀏覽器或渲染引擎的不同預設設定。
  • 切勿使用 OS 令牌來定義瀏覽器是在移動裝置、平板電腦還是桌面上。OS 可能在多種裝置型別上執行(例如,Android 在平板電腦和手機上都執行)。

下表總結了常見瀏覽器廠商指示其瀏覽器在移動裝置上執行的方式:

瀏覽器 規則 示例
Mozilla (Gecko, Firefox) 註釋中包含 MobileTablet Mozilla/5.0 (Android 15; Mobile; rv:136.0) Gecko/136.0 Firefox/136.0
基於 WebKit (Android, Safari) 註釋外包含 Mobile Safari 令牌。 Mozilla/5.0 (Linux; U; Android 4.0.3; de-ch; HTC Sensation Build/IML74K) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30
基於 Blink (Chromium, Google Chrome, Opera 15+, Android 上的 Edge) 註釋外包含 Mobile Safari 令牌。 Mozilla/5.0 (Linux; Android 4.4.2; Nexus 5 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.117 Mobile Safari/537.36 OPR/20.0.1396.72047
Windows 10 Mobile 上的 Edge 註釋外包含 Mobile/xyzEdge/ 令牌。 Mozilla/5.0 (Windows Phone 10.0; Android 6.0.1; Xbox; Xbox One) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Mobile Safari/537.36 Edge/16.16299

簡而言之,你可以在 UA 字串中的任何位置查詢字串 Mobi。如果裝置足夠大,沒有被標記為 Mobi,你應該提供桌面網站(作為最佳實踐,桌面裝置也應該支援觸控輸入,因為桌面裝置可能有觸控式螢幕)。

另見