2013-06-11

使用 PHP 分析 User Agent

現在不少網頁都為了流動裝置編寫了專為流動裝置的瀏覽器而製作的網頁
當使用流動裝置傳取桌面版的網頁時,會自動移至流動裝置版的分頁
判斷瀏覽器是否流動裝置便需要先觀察 User Agent……

當使用網頁瀏覽器時,會提供稱為 User Agent 的訊息,訊息通常會包括:
使用者使用的作業系統名稱、作業系統版本、瀏覽器名稱、瀏覽器版本、排版引擎名稱、排版引擎版本
當能力分折 User Agent 的資料,便可以獲得使用者的作業系統名稱從判斷是否流動裝置,便可以轉移至合適的網頁版本
甚至可以獲得使用者的瀏覽器的預設語言,當使用存取網頁時讓網頁設換至相對語言,讓網頁更加人性化

閣下可以參巧以下程序碼
或到以下連結 https://bitbucket.org/hkgoldenmra/php-user-agent-parser
或利用 git 下載專案
git clone https://hkgoldenmra@bitbucket.org/hkgoldenmra/php-user-agent-parser.git
<?php
class UserAgentParser{
    const VERSION_FORMAT = '(?:[0-9A-Za-z]+)(?:[\.\-\_][0-9A-Za-z]+)*\+?';
    // add more windows version here
    private static $WINDOWS_VERSIONS = array(
        'ce' => 'CE',
        '95' => '95',
        '98' => '98',
        '4.0' => 'NT 4.0',
        '5.0' => '2000',
        '5.1' => 'XP',
        '5.2' => '2003',
        '6.0' => 'VISTA',
        '6.1' => '7',
        '6.2' => '8',
    );
    // add more samsung galaxy versio here
    private static $SAMSUNG_GALAXY_VERSIONS = array(
        'i8150' => 'W',
        'i8160' => 'Ace 2',
        'i8190n' => 'S3 Mini',
        'i8530' => 'Beam',
        'i8730' => 'Express',
        'i9001' => 'S+',
        'i9003' => 'S',
        'i9070' => 'S Advance',
        'i9100' => 'S2',
        'i9103' => 'R',
        'i9105p' => 'S2+',
        'i9190' => 'S4 Mini',
        'i9192' => 'S4 Mini Duo',
        'i9195' => 'S4 Mini LTE',
        'i9210' => 'S2 LTE',
        'i9250' => 'Nexus',
        'i9295' => 'S4 Active',
        'i9300' => 'S3',
        'i9305' => 'S3 LTE',
        'i9500' => 'S4',
        'i9505' => 'S4 LTE',
        'n7000' => 'Note',
        'n7100' => 'Note 2',
        'n7105' => 'Note 2 LTE',
        'p6200' => 'Tab 7.0+',
        'p7500' => 'Tab 10.1',
        's5301' => 'Pocket',
        's5360' => 'Y',
        's5380' => 'Ace',
        's5380d' => 'Wave Y',
        's5660' => 'Gio',
        's6102' => 'Y Duos',
        's6500d' => 'Mini 2',
        's6802' => 'Ace Duos',
        's7500' => 'Ace+',
        's7562' => 'S Duos',
        'nexus' => 'Nexus',
    );
    // add more mobile os here
    private static $MOBILE_OS_NAMES = array(
        'blackberry(?: (%s))?' => 'BlackBerry',
        'meego(?: (%s))?' => 'Nokia MeeGo',
        'ipod.*?cpu.*?os(?: (%s))?' => 'iPod',
        'iphone.*?cpu.*?os(?: (%s))?' => 'iPhone',
        'ipad.*?cpu.*?os(?: (%s))?' => 'iPad',
        'sonyericsson(%s)?' => 'Sony Xperia',
        'htc sensation(?: (%s))?' => 'HTC Sensation',
        '(?:(?:galaxy)|(?:gt))(?:[ \-](%s))?' => 'Samsung Galaxy',
        'nexus one(?: (%s))?' => 'Nexus One',
        'android(?: (%s))?' => 'Android',
    );
    // add more os here
    private static $OS_NAMES = array(
        'windows phone os(?: (%s))?' => 'Windows Phone OS',
        '(?:(?:winnt)|(?:windows(?: nt)?))(?: (%s))?' => 'Windows',
        'mac os x(?: (%s))?' => 'Mac OS X',
        '(?:mac(?:intosh)|(?:_powerpc))(?: (%s))?' => 'Macintosh',
        'linux mint(?:\/(%s))?' => 'Linux Mint',
        'ubuntu(?:\/(%s))?' => 'Ubuntu',
        'debian(?:\/(%s))?' => 'Debian',
        '(?:mandriva(?: linux)?)(?: (%s))?' => 'Mandriva',
        'fedora(?: (%s))?' => 'Fedora',
        'oracle(?: (%s))?' => 'Oracle',
        'gentoo(?: (%s))?' => 'Gentoo',
        'freebsd(?: (%s))?' => 'FreeBSD',
        'openbsd(?: (%s))?' => 'OpenBSD',
        '(?:s(?:olaris)|(?:unos))(?: (%s))?' => 'Solaris',
        '(?:(?:linux)|(?:x11))(?: (%s))?' => 'GNU/Linux',
    );
    // add more mobile browser here
    private static $MOBILE_BROWSER_NAMES = array(
        'opera.*mini.*version(?:\/(%s))?' => 'Opera Mini',
        'opera.*mobi.*version(?:\/(%s))?' => 'Opera Mobile',
        'dolphin(?:\/(%s))?' => 'Dolphin Browser',
        'nokiabrowser(?:\/(%s))?' => 'Nokia Browser',
        'iemobile(?:\/(%s))?' => 'Internet Explorer Mobile',
        'fennec(?:\/(%s))?' => 'Fennec',
        'android.*version(?:\/(%s))?' => 'Android Browser',
        'crios(?:\/(%s))?' => 'CriOS',
        '(?:ip.*cpu.*os.*version(?:\/(%s))?)' => 'Mobile Safari',
        '(?:ip.*cpu.*os.*mobile(?:\/(%s))?)' => 'Mobile Safari',
        'dalvik(?:\/(%s))?' => 'Dalvik',
    );
    // add more browser here
    private static $BROWSER_NAMES = array(
        'opera.*version(?:\/(%s))?' => 'Opera',
        'maxthon(?:\ (%s))?' => 'Maxthon',
        'msie(?:\ (%s))?' => 'Internet Explorer',
        'iceweasel(?:\/(%s))?' => 'Iceweasel',
        '(?:n(?:etscape)|(?:avigator))(?:\/(%s))?' => 'Netscape Navigator',
        'thunderbird(?:\/(%s))?' => 'Thunderbird',
        'thunderbrowse(?:\/(%s))?' => 'ThunderBrowse',
        'firefox(?:\/(%s))?' => 'Firefox',
        'google earth(?:\/(%s))?' => 'Google Earth',
        'midori(?:\/(%s))?' => 'Midori',
        'iron(?:\/(%s))?' => 'Iron',
        'chromium(?:\/(%s))?' => 'Chromium',
        'chrome(?:\/(%s))?' => 'Chrome',
        'epiphany(?:\/(%s))?' => 'Epiphany',
        'konqueror(?:\/(%s))?' => 'Konqueror',
        'version(?:\/(%s))?\ safari' => 'Safari',
        'netsurf(?:\/(%s))?' => 'NetSurf',
        'wget(?:\/(%s))?' => 'Wget',
        'dillo(?:\/(%s))?' => 'Dillo',
    );
    // add more robot here
    private static $ROBOT_NAMES = array(
        'baiduspider(?:\/(%s))?' => 'Baidu Spider',
        'bingbot(?:\/(%s))?' => 'Bing Bot',
        'googlebot(?:\/(%s))?' => 'Google Bot',
        'ia\_archiver(?:\/(%s))?' => 'Internet Archive',
    );
    // add more layout engine here
    private static $LAYOUT_NAMES = array(
        'trident(?:\/(%s))?' => 'Trident',
        '(?:apple)?webkit(?:\/(%s))?' => 'WebKit',
        'khtml(?:\/(%s))?' => 'KHTML',
        'gecko(?:\/(%s))?' => 'Gecko',
        'presto(?:\/(%s))?' => 'Presto',
    );
    private $user_agent = '';
    private $colored_user_agent = '';
    private $default_language = '';
    private $is_mobile = false;
    private $is_robot = false;
    private $os_name = '';
    private $os_version = '';
    private $browser_name = '';
    private $browser_version = '';
    private $layout_name = '';
    private $layout_version = '';
    /*
    the parent must under child in order
    */
    public static final function MOBILE_OS_NAMES(){
        return self::$MOBILE_OS_NAMES;
    }
    public static final function OS_NAMES(){
        return self::$OS_NAMES;
    }
    public static final function MOBILE_BROWSER_NAMES(){
        return self::$MOBILE_BROWSER_NAMES;
    }
    public static final function BROWSER_NAMES(){
        return self::$BROWSER_NAMES;
    }
    public static final function ROBOT_NAMES(){
        return self::$ROBOT_NAMES;
    }
    public static final function LAYOUT_NAMES(){
        return self::$LAYOUT_NAMES;
    }
    public function get_user_agent(){
        return $this->user_agent;
    }
    public function get_colored_user_agent(){
        return $this->colored_user_agent;
    }
    public function get_default_language(){
        return $this->default_language;
    }
    public function is_robot(){
        return $this->is_robot;
    }
    public function is_mobile(){
        return $this->is_mobile;
    }
    public function get_os_name(){
        return $this->os_name;
    }
    public function get_os_version(){
        return $this->os_version;
    }
    public function get_browser_name(){
        return $this->browser_name;
    }
    public function get_browser_version(){
        return $this->browser_version;
    }
    public function get_layout_name(){
        return $this->layout_name;
    }
    public function get_layout_version(){
        return $this->layout_version;
    }
    private function set_user_agent($user_agent){
        if (strlen($user_agent) === 0){
            $user_agent = $_SERVER['HTTP_USER_AGENT'];
        }
        $this->colored_user_agent = $this->user_agent = $user_agent;
    }
    private function set_default_language(){
        $matches = array();
        if (preg_match('/^(..(?:\-..))[\,\;]?/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches) > 0){
            $this->default_language = preg_replace('/[\-]/', '_', strtolower($matches[1]));
        }
    }
    private function set_name_version($array, &$name, &$version, $color, $check_is_robot = false, $check_is_mobile = false){
        $matches = array();
        foreach ($array as $k => $v){
            $regex = sprintf('/(?:%s)/i', sprintf($k, self::VERSION_FORMAT));
            if (preg_match($regex, $this->user_agent, $matches) > 0){
                $name = $v;
                $this->colored_user_agent = str_replace($matches[0], sprintf('<span style="color:%s;">%s</span>', $color, $matches[0]), $this->colored_user_agent);
                if (count($matches) > 1){
                    $version = preg_replace('/[_\-]/i', '.', $matches[1]);
                    switch ($v){
                        case 'Windows': {
                            $version = self::$WINDOWS_VERSIONS[$version];
                        } break;
                        case 'Samsung Galaxy': {
                            $version = self::$SAMSUNG_GALAXY_VERSIONS[$version];
                        } break;
                    }
                }
                $this->is_robot = $check_is_robot;
                $this->is_mobile = $check_is_mobile;
                break;
            }
        }
    }
    public function __construct($user_agent = ''){
        $this->set_default_language();
        $this->set_user_agent('' . $user_agent);
        $this->set_name_version(self::ROBOT_NAMES(), $this->browser_name, $this->browser_version, 'blue', true, $this->is_mobile());
        if (!$this->is_robot()){
            $this->set_name_version(self::MOBILE_BROWSER_NAMES(), $this->browser_name, $this->browser_version, 'blue', $this->is_robot(), true);
            if (!$this->is_mobile()){
                $this->set_name_version(self::BROWSER_NAMES(), $this->browser_name, $this->browser_version, 'blue', $this->is_robot(), $this->is_mobile());
            }
        }
        $this->set_name_version(self::LAYOUT_NAMES(), $this->layout_name, $this->layout_version, 'green', $this->is_robot(), $this->is_mobile());
        if (!$this->is_robot()){
            if (!$this->is_mobile()){
                $this->set_name_version(self::OS_NAMES(), $this->os_name, $this->os_version, 'red', $this->is_robot(), $this->is_mobile());
            } else {
                $this->set_name_version(self::MOBILE_OS_NAMES(), $this->os_name, $this->os_version, 'red', $this->is_robot(), true);
            }
        }
    }
}
?>
以上程式碼其實是複雜化了,若果只是判斷作業系統名稱、瀏覽器名稱、排版引擎名稱
只需要利用 regex 尋找對應該的名稱就完成操作,例如作業系統
使用 Linux 其 user agent 會使用 x11 或 Linux 的代號,可以利用
/(x11|linux)/i
() 為 capturing group , | 為 or ,最尾的 i 為無視字母大小寫法,若果不打算收集資料可以使用 (?:) 為 non-capturing group
在 PHP 的 preg_match 操作上,capturing group 會將資料傳回給第三個參數,相反 non-capturing group 則不會傳回
善用 non-capturing group 可以避免收集不必要的資料,例如在下的 regex 經常出現如
/(?:(?:(?:linux)|(?:x11))(?:\ ((?:[0-9]+)(?:[\.\-\_][0-9]+)*))?)/i
由於在下的程式碼,還需要分析版本,linux, x11 等資料使用 non-capturing group
而需分析版本的 ((?:[0-9]+)(?:[\.\-\_][0-9]+)*))? 則使用 capturing group
另外還需要注意編程次序
一般程式碼由於會以上至下方式閱讀資料,若果某作業系統、瀏覽器,是另一個作業系統、瀏覽器的子集、衍生版
子集的程式碼必須比其父項編排得前,例如作業系統
Linux Mint 是 Ubuntu 的子集,Ubuntu 是 Debian 的子集,Debian 的 Linux 的子集
程式碼便必須以 Linux Mint, Ubuntu, Debian, Linux 或類似的次序編排
否則父項比子集次序編排為前時,子集將不會被閱讀

2 則留言 :