{"id":203,"date":"2011-06-30T00:20:30","date_gmt":"2011-06-29T22:20:30","guid":{"rendered":"https:\/\/sgaul.de\/?p=203"},"modified":"2014-03-13T21:34:55","modified_gmt":"2014-03-13T20:34:55","slug":"browser-und-bots-am-user-agent-string-erkennen","status":"publish","type":"post","link":"https:\/\/sgaul.de\/2011\/06\/30\/browser-und-bots-am-user-agent-string-erkennen\/","title":{"rendered":"Browser und Bots am User-Agent-String erkennen"},"content":{"rendered":"

Wir haben bei MGVmedia so einige Webprojekte angeh\u00e4uft. Viele von denen versuchen herauszufinden, wer oder was ein Besucher ist: Bot oder Browser<\/strong>? Welcher genau? Welche Version<\/strong>? Welches Betriebssystem<\/strong>? Vor allem bei Besucherstatistiken<\/a> muss man es genau wissen. Aus dem User-Agent-String des HTTP-Headers<\/strong> l\u00e4sst sich das feststellen. Da aber jede Software selbst entscheidet, was genau dort drin steht, ist das ein kompliziertes Verfahren, welches man mit der Zeit immer wieder anpassen muss. Bisher haben wir L\u00f6sungen tief in die Projekte integriert, wodurch diese nur sehr schwierig wartbar sind. Zeit sich eine fundiertere L\u00f6sung zu \u00fcberlegen.<\/p>\n

Ein JSON-Web-Service<\/h2>\n

Um eine nachhaltige L\u00f6sung zu erreichen, sind drei wesentliche Komponenten geplant:<\/p>\n

    \n
  1. Ein Server<\/strong>, dem man mittels Get-Request einen User-Agent-String sendet. Dieser antwortet im JSON-Format, ob der String bekannt ist, und, wenn ja, welcher Bot oder Browser sich dahinter verbirgt.<\/li>\n
  2. Eine Client-Bibliothek<\/strong>, welche die Netzwerkkommunikation mit dem Server \u00fcbernimmt und fertige Browser- oder Bot-Objekte zur\u00fcckliefert.<\/li>\n
  3. Ein Testprogramm<\/strong>, welches mit aktuellen User-Agents gef\u00fcttert wird und die Strings ausgibt, welche nicht erkannt werden konnten. Zudem kann es unbekannte Anfragen des Servers sichern und regelm\u00e4\u00dfig per Mail darum bitten, die neuen Strings zuzuordnen.<\/li>\n<\/ol>\n

    Der Server<\/h2>\n

    Das Datenmodell<\/h3>\n\n\n\n\n\n\n\n\n
    Erbt von<\/th>\nKlasse<\/th>\nAttribute<\/th>\n<\/tr>\n
    <\/td>\nAgent<\/td>\nString identifier (der User-Agent-String)<\/td>\n<\/tr>\n
    Agent<\/td>\nBot<\/td>\nString name (menschenlesbarer Name des Bots)<\/td>\n<\/tr>\n
    Bot<\/td>\nEIBot<\/td>\nString substring (markanter Teil des UA-Strings)<\/td>\n<\/tr>\n
    Agent<\/td>\nBrowser<\/td>\nString name (menschenlesbarer Name des Browsers), String version (Version des Browsers)<\/td>\n<\/tr>\n
    Browser<\/td>\nEIBrowser<\/td>\nString substring (markanter Teil des UA-Strings)<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n

    Der \u201eSchnelltest\u201c<\/h3>\n

    Jeder bekannte Bot bekommt ein Objekt, in dem sein lesbarer Name steht:<\/p>\n

    $known_bots = array(\r\n    new EIBot('Googlebot', 'Googlebot'),\r\n    new EIBot('Google Feedfetcher', 'Feedfetcher-Google'),\r\n    new EIBot('Bing Bot', 'http:\/\/www.bing.com\/bingbot.htm'),\r\n    \/\/ ...\r\n);<\/pre>\n

    Wie jeder Agent hat ein Bot eine Methode match<\/span>, welche einen UA-String als Argument nimmt und entscheidet, ob der User-Agent-String zu dem Objekt passt oder nicht. So kann bei einer Anfrage jedes bekannte Bot-Objekt gefragt werden. Passt keines, handelt es sich um keinen bekannten Bot.<\/p>\n

    EI<\/strong> steht f\u00fcr \u201eeasily identifiable<\/strong>\u201c: Der EIBot-Konstruktor nimmt den lesbaren Namen des Bots und einen markanten Teil des User-Agent-Strings, welcher den Bot definitiv identifiziert. In der match<\/span>-Methode kann etwa in PHP mit dem effizienten strpos<\/span> entschieden werden, ob der Substring enthalten ist und der String \u201ematcht\u201c.<\/p>\n

    public function match($userAgentString) {\r\n    return (strpos($userAgentString, $this->substring) !== false);\r\n}<\/pre>\n

    Mit den Browsern funktioniert es genauso: Viele sollen mittels EI-Objekten schnell gefunden werden. Ob man aufgrund schnell wechselnder Versionsnummer noch einen RegexBrowser<\/span> erg\u00e4nzt, muss die Erfahrung zeigen.<\/p>\n

    Bei Browsern zeigt der \u201eSchnelltest\u201c oft ein Problem auf: Bots tarnen sich gern als Browser<\/strong>, und so l\u00e4sst sich von \u201eFirefox\/3.6\u201c im UA-String nicht zwangsweise auf Firefox schlie\u00dfen. In aller Regel ist aber zus\u00e4tzlich eine Internet-Adresse oder Bot-Kennung erhalten, welche diesen Bot \u201everr\u00e4t\u201c. Bei der Frage, ob es sich um einen Browser handelt, sollte also gepr\u00fcft werden, ob der UA-String einen Browser repr\u00e4sentiert und<\/em> ob er keinen Bot repr\u00e4sentiert.<\/p>\n

    function query_browser($user_agent_string) {\r\n    \/\/ Lots of bots look like browsers - let's check that first\r\n    if (query_bot($user_agent_string)) return null;\r\n    \/\/ ...<\/pre>\n

    Die Client-Bibliothek<\/h2>\n

    Die Client-Bibliothek verf\u00fcgt \u00fcber das selbe Model. Allerdings geht diese nur von Bot<\/span> bzw. Browser<\/span> aus. Ob es sich um einen EIBot<\/span> oder eine andere Spezialisierung handelt, ist an dieser Stelle uninteressant. Zudem bietet der Client zwei Funktionen: query_bot($ua_string)<\/span> und query_browser($ua_string)<\/span>. Beide geben, wenn verf\u00fcgbar, das entsprechende Objekt zur\u00fcck. Ansonsten ist die R\u00fcckgabe null.<\/p>\n

    Der Test<\/h2>\n

    Eine erste Umsetzung zeigt das Testskript, welches ein Array mit User-Agents nimmt, versucht diese zu erkennen und ensprechende R\u00fcckmeldung gibt:<\/p>\n

    \"\"<\/a>
    Testergebnisse<\/figcaption><\/figure>\n

    Testwerte lassen sich einfach aus PHP-My-Admin exportieren<\/strong>. Hierf\u00fcr einfach die entsprechende Spalte selektieren und das Ergebnis als PHP-Array exportieren.<\/p>\n

    Weitere Entwicklung<\/h2>\n

    Die oben skizzierte Idee war schnell umgesetzt. Sicher fehlt es noch an Feinschliff, aber es scheint zu funktionieren und sehr schnell zu sein (der Testfall, welcher bereits die JSON-Kommunikation nutzt, braucht f\u00fcr 1000 User-Agents circa drei Sekunden). Folgende Punkte sind noch offen:<\/p>\n