码桶

发现社区成员的开源项目

geekssl /

wstats

公开
main
wstats/app/Support/UaParser.php
UaParser.php4.8 KB
<?php
/**
 * 轻量 UA 解析(浏览器/系统/设备)。覆盖主流客户端,解析失败回退 'Unknown'。
 * 命中顺序:先移动端特征,再浏览器关键字。
 */
declare(strict_types=1);

namespace Wstat\Support;

class UaParser
{
    public static function parse(string $ua): array
    {
        $ua = trim($ua);
        if ($ua === '') {
            return ['browser' => '', 'os' => '', 'device' => '', 'mobile' => false];
        }
        $u = strtolower($ua);

        /* ---- 设备 ---- */
        $device = 'desktop';
        if (strpos($u, 'bot') !== false || strpos($u, 'spider') !== false || strpos($u, 'crawler') !== false) {
            $device = 'bot';
        } elseif (strpos($u, 'ipad') !== false || strpos($u, 'tablet') !== false || strpos($u, 'kindle') !== false) {
            $device = 'tablet';
        } elseif (preg_match('/mobile|android|iphone|ipod|phone|iemobile|opera mini/i', $u)) {
            $device = 'mobile';
        }

        /* ---- 浏览器 ---- */
        $browser = 'Unknown';
        $map = [
            'edg/'            => 'Edge',
            'edgios/'         => 'Edge',
            'opr/'            => 'Opera',
            'opera'           => 'Opera',
            'micromessenger'  => 'WeChat',
            'weibo'           => 'Weibo',
            'bingbot'         => 'BingBot',
            'baiduspider'     => 'BaiduBot',
            'googlebot'       => 'GoogleBot',
            'sogou'           => 'SogouSpider',
            '360spider'       => '360Spider',
            'qqbrowser'       => 'QQBrowser',
            'ucbrowser'       => 'UCBrowser',
            'quark'           => 'Quark',
            'chrome'          => 'Chrome',
            'crios'           => 'Chrome',
            'safari'          => 'Safari',
            'firefox'         => 'Firefox',
            'fxios'           => 'Firefox',
            'msie'            => 'IE',
            'trident'         => 'IE',
            'samsungbrowser'  => 'Samsung',
        ];
        foreach ($map as $key => $name) {
            if (strpos($u, $key) !== false) {
                $browser = $name;
                break;
            }
        }
        // Firefox 出现在 Googlebot 等中顺序已处理;Safari 需排除 Chrome 内核干扰
        if ($browser === 'Safari' && (strpos($u, 'chrome/') !== false || strpos($u, 'crios') !== false)) {
            $browser = strpos($u, 'crios') !== false ? 'Chrome' : $browser;
        }

        /* ---- 操作系统 ---- */
        $os = 'Unknown';
        $osMap = [
            'windows nt 11' => 'Windows 11',
            'windows nt 10' => 'Windows 10',
            'windows nt 6.3' => 'Windows 8.1',
            'windows nt 6.1' => 'Windows 7',
            'android'        => 'Android',
            'iphone'         => 'iOS',
            'ipad'           => 'iOS',
            'ipod'           => 'iOS',
            'mac os x'       => 'macOS',
            'macintosh'      => 'macOS',
            'linux'          => 'Linux',
            'ubuntu'         => 'Ubuntu',
            'windows phone'  => 'Windows Phone',
        ];
        foreach ($osMap as $key => $name) {
            if (strpos($u, $key) !== false) {
                $os = $name;
                break;
            }
        }

        $mobile = $device === 'mobile' || $device === 'tablet';

        return ['browser' => self::clean($browser), 'os' => self::clean($os), 'device' => $device, 'mobile' => $mobile];
    }

    /** 判定是否爬虫/自动化工具(采集端过滤用;大小写不敏感,UA 为空不算) */
    public static function isBot(string $ua): bool
    {
        $ua = strtolower(trim($ua));
        if ($ua === '') {
            return false;
        }
        // 常见搜索引擎爬虫、离线工具、HTTP 客户端库、无头浏览器、压测工具
        $needles = [
            'bot', 'spider', 'crawl', 'slurp', 'curl', 'wget', 'python', 'java/',
            'okhttp', 'go-http-client', 'httpclient', 'libwww', 'http_request',
            'axios/', 'node-fetch', 'undici', 'scrapy', 'phantomjs', 'headless',
            'selenium', 'puppeteer', 'playwright', 'postman', 'insomnia',
            'lighthouse', 'apachebench', 'jmeter', 'wrk', 'loadrunner',
            'bytespider', 'petalbot', 'yisou', 'ahrefs', 'semrush', 'mj12',
            'dotbot', 'feedly', 'weborama', 'sprinklr', 'dataforseo',
        ];
        foreach ($needles as $n) {
            if (strpos($ua, $n) !== false) {
                return true;
            }
        }
        // 无 UA / 纯状态描述(如 Mozilla 空壳)不算 bot;以 IP 协议词开头的原始 HTTP 客户端判 bot
        return (bool) preg_match('#^(?:[a-z0-9]+/[0-9][0-9.]*\s*$)#', $ua);
    }

    private static function clean(string $s): string
    {
        return mb_substr($s, 0, 39);
    }
}