1 Reaktion

Apache Logfile Analyse mit PHP 5 zur Erstellung von Web-Statistiken Teil 2

Geschätzte Lesedauer:

Im ersten Teil haben wir die Grundlagen für eine Logfile Auswertung mit PHP geschaffen. Nun wollen wir das Script erweitern um eine echte Analyse mit verwertbaren Ergebnissen zu ermöglichen.

Suchmaschinen und Suchanfragen

Als erstes erweitern wir unsere Klasse pxALogLine, so dass sie Anfragen erkennt, die von einer Suchmaschine weitergeleitet wurden. Hierzu wird in der Logdatei ein Feld mit dem sogenannten Referrer. In diesem Feld wird gespeichert, welche Seite zur aktuellen verlinkt hat. Diese Angabe ist nicht zwingend, einige Browser und -Erweiterungen senden diese Informationen nicht mit an den Server, so dass in diesem Fall das Feld leer ist und es scheint, als hätte der User die Seite direkt aufgerufen und wäre nicht über einen Link gekommen. Für unsere Logfile Analyse soll uns das jedoch nicht weiter interessieren.

Am Beispiel der Google Suche zeige ich kurz, wie ein solcher Referrer aufgebaut sein kann. Ich habe die Url der Übersichtlichkeit halber am Ende abgeschnitten:
http://www.google.com/url?sa=t&rct=j&q=firefox%20neuer%20tab%20zeigt%20letzte%20besuchte%20seiten&source=web

Was zeigt uns diese Url? Zuerst einmal ist klar, dass der Link von Google kam. Suchmaschinen sind in der Regel so freundliche und senden auch die Suchanfrage mit, die der User verwendet hat. Je nach Suchmaschine steht diese hinter einem anderen Parameter in der Url. Bei Google heißt dieser Parameter „q“ für Query, zu deutsch Anfrage.
In diesem Fall enthält der Parameter firefox%20neuer%20tab%20besuchte%20seiten.
Die %20 Angabe sind kodierte Leerzeichen, so dass die Suchanfrage demnach firefox neuer tab besuchte seiten gewesen ist.
Zu beachten ist auch, dass nicht jede Suchmaschine diese Angabe im Referrer mitgiebt, beispielsweise ist die Angabe leer, wenn ein User von der SSL Suchseite von Google kommt (https://www.google.com).

Aus diesen Informationen generieren wir uns jetzt Reguläre Ausdrücke für jede Suchmaschine, die uns bekannt ist (kann natürlich jederzeit ergänzt werden). Wie auch schon im letzten Teil muss ich darauf hinweisen, dass ich Reguläre Ausdrücke an dieser Stelle nicht ausführlich behandeln kann.
Für die Google Suchmaschine verwenden wir: /^(\w+)\.google\.(\w{2,5})(\.\w{2,3})?$/i
Dieser Ausdruck trifft auf alle Subdomains und Toplevel Domains von google zu, wie zum Beispiel: plus.google.com, www.google.de, xyz.google.co.uk.

Die Informationen für die Suchmaschinen, sowie die Angabe bei welchem Parameter die Suchanfrage zu finden ist, speichern wir in einem Array für die spätere Suche.

[pastacode lang=“php“ message=“PHP“ highlight=““ provider=“manual“]

private $_search_engines = array('Google' => array('regex' => '/^(\w+)\.google\.(\w{2,5})(\.\w{2,3})?$/i',
                                                       'param' => 'q'),
                                     'IAC Search' => array('regex' => '/^(\w+)\.search-results\.(\w{2,5})$/i',
                                                           'param' => 'q'),
                                     'Babylon' => array('regex' => '/^search\.babylon\.com$/i',
                                                        'regex_path' => '/\/web\/(.*)$/'),
                                     'Web.de' => array('regex' => '/^suche\.web\.de$/i',
                                                       'param' => 'su'),
                                     'AOL Alice' => array('regex' => '/^alicesuche\.aol\.de$/i',
                                                       'param' => 'q'),
                                     'GMX.de' => array('regex' => '/^suche\.gmx\.(de|net|com)$/i',
                                                       'param' => 'su'),
                                     'ICQ Search' => array('regex' => '/^search\.icq\.com$/i',
                                                       'param' => 'q'),
                                     'Ask.com' => array('regex' => '/^\w+\.ask\.com$/i',
                                                       'param' => 'q'),
                                     'ISearch' => array('regex' => '/^isearch\.avg\.com$/i',
                                                       'param' => 'q'),
                                     'Incredimail Search' => array('regex' => '/^search\.incredimail\.com$/i',
                                                       'param' => 'q'),
                                     'Conduit Search' => array('regex' => '/^search\.conduit\.com$/i',
                                                       'param' => 'q'),
                                     'T-Online' => array('regex' => '/^suche\.t-online\.de$/i',
                                                         'param' => 'q'),
                                     'Yahoo' => array('regex' => '/^(\w+\.)?search\.yahoo\.com$/i',
                                                         'param' => 'p'),
                                     'Bing' => array('regex' => '/^(\w+)\.bing\.com$/i',
                                                     'param' => 'q')
                                    );

[/pastacode]

Diese Liste ist mit Sicherheit nicht vollständig, aber ein guter Anfang um die wichtigsten Suchanbieter zu ermitteln.
Am Beispiel der Suchmaschine Babylon sehen wir direkt, dass nicht bei jeder Suchmaschine die Anfrage in einem Parameter zu finden ist, sondern sich auch in der URL verstecken kann.

Um die gesammelten Angaben nun nutzen zu können fügen wir unserer Klasse noch eine Methode hinzu

[pastacode lang=“php“ message=“PHP“ highlight=““ provider=“manual“]

private function _checkSearchEngine($ref_domain = '', $ref_url = '', $ref_params = '') {
    foreach($this->_search_engines as $engine_name => $edata) {
        // laufe durch das Suchmaschinen Array
        if(preg_match($edata['regex'], $ref_domain)) {
            // wenn der reguläre Ausdruck zutrifft speichere Suchmaschinennamen
            $this->_referrer_search = $engine_name;
             
            if($edata['regex_path']) {
                // Wenn wir nicht einen parameter suchen, sondern die Anfrage in der URL steht
                if(preg_match($edata['regex_path'], $ref_url, $qmatches)) {
                    $query = $qmatches[1];
                    if($query) {
                        // Anfrage gefunden, speichern
                        $query = urldecode($query);
                        $this->_search_string = $query;
                    }
                }
            } else {
                // Suchanfrage steht in einem Parameter
                $params = array();
                parse_str($ref_params, $params);
                $query = $params[$edata['param']];
                if($query) {
                    // Anfrage gefunden, dekodieren und speichern
                    $query = stripslashes(urldecode($query));
                    $this->_search_string = $query;
                }
            }
        }
    }
}

[/pastacode]

Im Grunde macht diese Methode nichts Anderes, als jede unserer definierten Suchmaschinen-Angaben zu prüfen und die URL daraufhin zu untersuchen. Die gefundenen Informationen werden dann in zwei Eigenschaften der Klasse gespeichert (_referrer_search und _search_string).

Hier noch einmal die Klasse mit den zusätzlichen Eigenschaften und der Methode:

[pastacode lang=“php“ message=“PHP“ highlight=““ provider=“manual“]

class pxALogLine {
    private $_is_valid = false;
    private $_search_string = '';
    private $_referrer_search = '';
    private $_info = array('ip' => '',
                           'time' => '',
                           'timestamp' => '',
                           'method' => '',
                           'url' => '',
                           'orig_url' => '',
                           'http_version' => '',
                           'code' => '',
                           'size' => '',
                           'referrer' => '',
                           'user_agent' => '',
                           'browser' => '',
                           'browser_version' => '',
                           'browser_comment' => '',
                           'browser_os' => '',
                           'crawler_hit' => false);
    private $_search_engines = array('Google' => array('regex' => '/^(\w+)\.google\.(\w{2,5})(\.\w{2,3})?$/i',
                                                       'param' => 'q'),
                                     'IAC Search' => array('regex' => '/^(\w+)\.search-results\.(\w{2,5})$/i',
                                                           'param' => 'q'),
                                     'Babylon' => array('regex' => '/^search\.babylon\.com$/i',
                                                        'regex_path' => '/\/web\/(.*)$/'),
                                     'Web.de' => array('regex' => '/^suche\.web\.de$/i',
                                                       'param' => 'su'),
                                     'AOL Alice' => array('regex' => '/^alicesuche\.aol\.de$/i',
                                                       'param' => 'q'),
                                     'GMX.de' => array('regex' => '/^suche\.gmx\.(de|net|com)$/i',
                                                       'param' => 'su'),
                                     'ICQ Search' => array('regex' => '/^search\.icq\.com$/i',
                                                       'param' => 'q'),
                                     'Ask.com' => array('regex' => '/^\w+\.ask\.com$/i',
                                                       'param' => 'q'),
                                     'ISearch' => array('regex' => '/^isearch\.avg\.com$/i',
                                                       'param' => 'q'),
                                     'Incredimail Search' => array('regex' => '/^search\.incredimail\.com$/i',
                                                       'param' => 'q'),
                                     'Conduit Search' => array('regex' => '/^search\.conduit\.com$/i',
                                                       'param' => 'q'),
                                     'T-Online' => array('regex' => '/^suche\.t-online\.de$/i',
                                                         'param' => 'q'),
                                     'Yahoo' => array('regex' => '/^(\w+\.)?search\.yahoo\.com$/i',
                                                         'param' => 'p'),
                                     'Bing' => array('regex' => '/^(\w+)\.bing\.com$/i',
                                                     'param' => 'q')
                                    );
 
    public function __construct($log_line = '') {
        // Regulären Ausdruck definieren
        $pattern = '/^([^ ]+) ([^ ]+) ([^ ]+) \[([^\]]+)\] "(.*?) (.*?) (.*?)" ([0-9\-]+) ([0-9\-]+) "(.*)" "(.*)"$/';
        // Übergebenen String durchsuchen und bei nicht zutreffendem Ausdruck abbrechen
        if(!preg_match($pattern, $log_line, $matches)) return;
         
        // Felder füllen
        $this->_info['ip'] = $matches[1];
        $this->_info['time'] = $matches[4];
        $this->_info['timestamp'] = strtotime($matches[4]);
        $this->_info['method'] = $matches[5];
        $this->_info['orig_url'] = $matches[6];
        $this->_info['url'] = $matches[6];
        $this->_info['http_version'] = $matches[7];
        $this->_info['code'] = $matches[8];
        $this->_info['size'] = $matches[9];
        $this->_info['referrer'] = $matches[10];
        $this->_info['user_agent'] = $matches[11];
        $this->_info['referrer_domain'] = '';
        $this->_info['file_ext'] = '';
        $this->_info['page_title'] = '';
        $this->_info['hits_only'] = false;
        $this->_info['crawler_hit'] = false;
         
        $this->_is_valid = true;
 
        // URL in ihre Bestandteile zerlegen
        $url = parse_url($this->_info['referrer']);
        if(!$url) $url = array();
        $this->_info['referrer_domain'] = $url['host'];
        $this->_checkSearchEngine($this->_info['referrer_domain'], $url['path'], $url['query']);
    }
     
    private function _checkSearchEngine($ref_domain = '', $ref_url = '', $ref_params = '') {
        foreach($this->_search_engines as $engine_name => $edata) {
            // laufe durch das Suchmaschinen Array
            if(preg_match($edata['regex'], $ref_domain)) {
                // wenn der reguläre Ausdruck zutrifft speichere Suchmaschinennamen
                $this->_referrer_search = $engine_name;
                 
                if($edata['regex_path']) {
                    // Wenn wir nicht einen parameter suchen, sondern die Anfrage in der URL steht
                    if(preg_match($edata['regex_path'], $ref_url, $qmatches)) {
                        $query = $qmatches[1];
                        if($query) {
                            // Anfrage gefunden, speichern
                            $query = urldecode($query);
                            $this->_search_string = $query;
                        }
                    }
                } else {
                    // Suchanfrage steht in einem Parameter
                    $params = array();
                    parse_str($ref_params, $params);
                    $query = $params[$edata['param']];
                    if($query) {
                        // Anfrage gefunden, dekodieren und speichern
                        $query = stripslashes(urldecode($query));
                        $this->_search_string = $query;
                    }
                }
            }
        }
    }
 
    public function isValid() {
        return $this->_is_valid;
    }
     
}

[/pastacode]

Zum Testen bedienen wir uns wieder unseres Beispielaufrufs aus Teil 1:

[pastacode lang=“php“ message=“PHP“ highlight=““ provider=“manual“]

// hier Einbinden der Klasse von oben
$logzeile = 'aaa.bbb.ccc.ddd - - [10/Apr/2012:06:56:48 +0200] "GET /mozilla/firefox/2012/04/04/mozilla-blockt-altere-java-versionen-in-firefox/ HTTP/1.1" 200 14759 "http://www.google.at/url?sa=t&rct=j&q=firefox%20java%20plugin&source=web&cd=5&ved=0CFgQFjAE&url=http%3A%2F%2Fwww.soeren-hentzschel.at%2Fmozilla%2Ffirefox%2F2012%2F04%2F04%2Fmozilla-blockt-altere-java-versionen-in-firefox%2F&ei=hL2DT9OxN4ap4gTx4ZDUBw&usg=AFQjCNHN8mJQ50bjEgOqo9GXOq_xTfz5jQ&sig2=QIGNicLoQqy32xHOmaM74w&cad=rjt" "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko/20100101 Firefox/11.0"';
  
$parser = new pxALogLine($logzeile);
if($parser->isValid()) {
    print "Gültige Logzeile erkannt.\n";
    var_dump($parser);
} else {
    print "Scheint keine gültige Logzeile zu sein.\n";
}

[/pastacode]

Die Ausgabe kürze ich im Vergleich zu Teil 1 ein wenig, da wir das meiste ja schon kennen. Schön zu sehen ist, dass die Eigenschaften _referrer_search und _search_string nun gefüllt sind und schöne Informationen zur Suche enthalten:

[pastacode lang=“bash“ message=“Ausgabe“ highlight=““ provider=“manual“]

Gültige Logzeile erkannt.
object(pxALogLine)#1 (5) {
  ["_is_valid":"pxALogLine":private]=>
  bool(true)
  ["_search_string":"pxALogLine":private]=>
  string(19) "firefox java plugin"
  ["_referrer_search":"pxALogLine":private]=>
  string(6) "Google"
  ["_info":"pxALogLine":private]=>
  array(20) {
    ["ip"]=>
    string(15) "aaa.bbb.ccc.ddd"
    ["time"]=>
    string(26) "10/Apr/2012:06:56:48 +0200"
    ["timestamp"]=>
    int(1334033808)
    ["method"]=>
    string(3) "GET"
    ["url"]=>
    string(76) "/mozilla/firefox/2012/04/04/mozilla-blockt-altere-java-versionen-in-firefox/"
    ["orig_url"]=>
    string(76) "/mozilla/firefox/2012/04/04/mozilla-blockt-altere-java-versionen-in-firefox/"
    ["http_version"]=>
    string(8) "HTTP/1.1"
    ["code"]=>
    string(3) "200"
    ["size"]=>
    string(5) "14759"
    ["referrer"]=>
    string(324) "http://www.google.at/url?sa=t&rct=j&q=firefox%20java%20plugin&source=web&cd=5&ved=0CFgQFjAE&url=http%3A%2F%2Fwww.soeren-hentzschel.at%2Fmozilla%2Ffirefox%2F2012%2F04%2F04%2Fmozilla-blockt-altere-java-versionen-in-firefox%2F&ei=hL2DT9OxN4ap4gTx4ZDUBw&usg=AFQjCNHN8mJQ50bjEgOqo9GXOq_xTfz5jQ&sig2=QIGNicLoQqy32xHOmaM74w&cad=rjt"
    ["user_agent"]=>
    string(65) "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko/20100101 Firefox/11.0"
  }

[/pastacode]

Das war es dann auch für diesen Teil. Im nächsten Teil werden wir uns daran machen Crawler und Bots zu erkennen und zu filtern.

Dieser Artikel wurde von Marius Burkard verfasst.

Marius Burkard ist Diplom-Wirtschaftsinformatiker und arbeitet seit 2006 als selbstständiger Software-Entwickler und Linux-Server-Administrator mit der Firma pixcept KG. Er ist unter anderem mitverantwortlich für die Projekte Was-lese-ich.de und ISPProtect.

1 Kommentar - bis jetzt!

Eigenen Kommentar verfassen
  1. Lukas Hitziger
    schrieb am :

    Super Artikel Marius!

    Zufälligerweise habe ich vor wenigen Wochen erst nach genau so einer Lösung gesucht, ohne Erfolg.
    Dein Artikel wird mir da sehr weiterhelfen 🙂
    Freu mich schon auf den nächsten Teil!

    Beste Grüße,
    Lukas

Und jetzt du! Deine Meinung?

Erforderliche Felder sind mit einem Asterisk (*) gekennzeichnet. Die E-Mail-Adresse wird nicht veröffentlicht.
  1. Nach Absenden des Kommentar-Formulars erfolgt eine Verarbeitung der von Ihnen eingegebenen personenbezogenen Daten durch den datenschutzrechtlich Verantwortlichen zum Zweck der Bearbeitung Ihrer Anfrage auf Grundlage Ihrer durch das Absenden des Formulars erteilten Einwilligung.
    Weitere Informationen