Geschrieben von

analytics.js

Webtracking

In meinem Beitrag “Wie Google Analytics technisch funktioniert” bin ich auf die 4 Komponenten von Google Analytics eingegangen (Collection, Processing, Configuration und Reporting). Ganz so technisch wurde der Beitrag nicht, gibt jedoch einen groben Überblick. Daher kommt jetzt dieser Beitrag, der die analytics.js-Bibliothek aus technischer Sicht erklären soll.

Was ist analytics.js?

Bei analytics.js handelt es sich um eine JavaScript-Bibliothek von Google, die eine Messung des Verhaltens von Nutzers auf der Website erlaubt. Diese wird von https://www.google-analytics.com/analytics.js geladen. Die gesammelten Daten sind dann in Google Analytics sichtbar (oder auch in BigQuery sofern eine Verknüpfung besteht). analytics.js ist zudem als Bibliothek von Universal Analytics bekannt. Neben dieser Technik existieren noch ga.js (Classic Analytics) und gtag.js (Global Site Tag).

analytics.js-Tracking-Code verstehen

Schauen wir uns zunächst das JavaScript an, welches im Head-Bereich der Website integriert werden muss, damit Universal Analytics Daten sammeln kann:

<!-- Google Analytics -->
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
 
ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');
</script>
<!-- End Google Analytics -->

Die wichtigste Funktionalität ist das Laden der JavaScript-Bibliothek von https://www.google-analytics.com/analytics.js und die Initialisierung der globalen ga-Funktion. Über die ga-Funktion können später Befehle ausgeführt werden, die nach dem fertigen Laden der JS-Bibliothek, starten. Im Code oben werden auch zwei Befehle ausgeführt, einmal um einen Tracker zu erzeugen (create) und einmal um einen Seitenaufruf zu Google Analytics zu schicken (send).

Werfen wir in diesem Teil des Beitrags einen Blick auf den ersten Bereich des Tracking-Codes (Herunterladen der Bibliothek vom Google-Server und Initialisierung des ga-Objekt). Auf die Befehle gehe ich später ein. Hier nochmal der erste Teil des Codes:

(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

Betrachtet man den Code ohne Minifizierung, dann ist er etwas leserlicher:

(function(i, s, o, g, r, a, m){
  i['GoogleAnalyticsObject'] = r;
  i[r] = i[r] || function(){
    (i[r].q = i[r].q || []).push(arguments)
  },
  i[r].l =1 * new Date();
  a = s.createElement(o),
  m = s.getElementsByTagName(o)[0];
  a.async = 1;
  a.src = g;
  m.parentNode.insertBefore(a, m)
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');

Auffallend ist sofort, dass es sich um eine Funktion handelt, die zum Zeitpunkt der Definition sofort ausgeführt wird. Das ist dieser Teil:

(function () {
  // Hier kommt die Logik
})();

Man spricht dabei von einer IIFE (Immediately Invoked Function Expression). Die Funktion nimmt 7 Parameter entgegen:

(function (i, s, o, g, r, a, m) {
  // Hier kommt die Logik
})();

Was diese genau bedeuten, erfährt man in der letzten Zeile des Codes:

(function (i, s, o, g, r, a, m) {
  // Hier kommt die Logik
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');

Jedoch werden hier nur 5 Parameter übergeben. Die anderen 2 werden genutzt, um eine Variable zu erstellen ohne das var-Statement später innerhalb der Funktion verwenden zu müssen (spart ein paar Zeichen). In diesem Fall wäre also i gleich das window-Objekt, s gleich das document-Objekt und so weiter. Eigentlich ist es in so einem Fall gängig den ersten Buchstaben als abgekürzten Variablennamen zu wählen – also z.B. w für window und d für document – die Google-Entwickler dahinter haben sich jedoch entschieden das Wort “isogram” zu übergeben.

Um den Code nun weiter besser zu verstehen, können wir also die übergebenen Parameter in die Funktion einsetzen. Das Ergebnis wäre:

(function(){
  window.GoogleAnalyticsObject = 'ga';
  window.ga = window.ga || function(){
    (window.ga.q = window.ga.q || []).push(arguments)
  },
  window.ga.l =1 * new Date();
  var a = document.createElement('script'),
  var m = document.getElementsByTagName('script')[0];
  a.async = 1;
  a.src = '//www.google-analytics.com/analytics.js';
  m.parentNode.insertBefore(a, m)
})();

Gehen wir den Code von oben nach unten durch. Zunächst wird der Name des Google Analytics-Objekts gespeichert:

window.GoogleAnalyticsObject = 'ga';

Im Anschluss wird mit einem if-Statement geprüft, ob das Google Analytics-Objekt nicht (!) definiert wurde:

if (!('ga' in window)){

Falls nicht, dann wird das Objekt definiert:

window.ga = function(){

Dann alle Befehle zur Warteschlange hinzufügen:

window.ga.q.push(arguments);
};

Warteschlange erstellen:

window.ga.q = [];
}

In dieses Array werden dann auch alle Befehle wie “send” abgelegt. Hintergrund ist, dass es sonst passieren kann, dass ein Befehl ausgeführt wird bevor noch die JS-Bibliothek fertig geladen wurde. Die Befehle werden also zwischengespeichert bis die Bibliothek fertig geladen worden ist. Erst dann werden die Befehle ausgeführt. Ab diesem Zeitpunkt wird jede nachfolgende Codezeile sofort ausgeführt. Zum Beispiel wird im ga-Objekt der aktuelle Zeitstempel hinterlegt:

window.ga.l = (new Date()).getTime();

Dann wird das entsprechende Skript-Element erstellt:

var script = document.createElement('script'),
script.src = '//www.google-analytics.com/analytics.js';
script.async = true;

Zum Schluss wird noch das Skript-Element ins Dokument eingefügt:

var firstScript = document.getElementsByTagName('script')[0];
firstScript.parentNode.insertBefore(script, firstScript)
})();

Wie analytics.js arbeitet

Entscheidend ist dabei zunächst die globale Funktion ga. Google Analytics spricht dabei von der “command queue” (Befehlswarteschlange). Befehlswarteschlange deshalb, da die übergebenen Befehle nicht sofort ausgeführt werden. Die Befehle werden in eine Warteschlange gegeben und erst ausgeführt, wenn analytics.js vollständig geladen und bereit ist. Das passiert, indem innerhalb der ga-Funktion die Eigenschaft q hinterlegt wird (da Funktionen auch Objekte sind, können daher Funktionen auch Eigenschaften enthalten). Dabei ist q zunächst ein leeres Array. Das haben wir oben am folgenden Code gesehen:

window.ga.q = [];

Wenn nun vor dem fertigen Laden von analytics.js die ga-Funktion aufgerufen wird, dann wird die Funktion alle übergebenen Befehle auflisten. Wenn wir vom obigen Code als Beispiel ausgehen, wo create (Tracker erstellen) und send (Hit schicken) übergeben worden sind, dann hätte man beim Loggen folgendes Ergebnis:

console.log(ga.q);
 
// Ergebnis:
// [['create', 'UA-XXXXX-Y', 'auto'],
// ['send', 'pageview']]

Hier sieht man die Warteschlange. Wenn nun analytics.js fertig geladen worden ist, dann wird alles in ga.q nacheinander ausgeführt. Nun können auch weitere Befehle zu ga() hinzugefügt werden. Dabei wird immer zunächst der Befehl als String mitgegeben, was eine Methode aus der analytics.js-Bibliothek darstellt. Danach können weitere Parameter mitgegeben werden, die jedoch hinsichtlich Pflichtfeldern von der Methode abhängen. 2 Methoden haben wir schon gesehen: create und send. Weitere Methoden sind in der “ga Object Methods Reference” zu finden. Sollte einmal ein unbekannter Befehl mitgegeben werden, dann wird das von Google Analytics ignoriert.

Bleiben wir nochmal bei den 2 Befehlen create und send:

ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');

Man sieht, dass der create-Befehl neben dem Feld “trackingId” (hier UA-XXXXX-Y) auch das Feld “cookieDomain” (hier auto) entgegennimmt. Optional könnte auch ein Feld “name” mitgegeben werden. Der send-Befehl hat hier nur das Feld “hitType” als “pageview”. Alle Befehle haben gemeinsam, dass sie auch einen fieldsObject-Parameter entgegennehmen können. Die 2 Befehle create und send können demnach auch wie folgt definiert werden:

ga('create', {
  trackingId: 'UA-XXXXX-Y',
  cookieDomain: 'auto'
});
ga('send', {
  hitType: 'pageview'
});

Tracker-Objekte verstehen

Nachdem wir nun wissen, wie analytics.js arbeitet, widmen wir uns der ersten wichtigen Funktionalität zu: Die Erstellung eines Trackers. Tracker-Objekte sammeln und lagern Daten, um sie dann an Google Analytics zu schicken. Eine zentrale Aufgabe also. Um einen Tracker zu erstellen, existiert der Befehl “create”:

ga('create', 'UA-XXXXX-Y', 'auto');

Zudem muss man noch die Tracking-ID mitgeben. Aber auch die Cookie-Domain, um zu bestimmen wie Cookies gespeichert werden. Die Tracking-ID bekommt man, wenn man eine Google Analytics-Property erstellt. Bei der Cookie-Domain empfiehlt Google Analytics den Wert auf “auto” beizubelassen. Damit versucht analytics.js das Cookie auf die höchste Domain-Ebene zu setzen. Dadurch wird z.B. für die Adresse shop.domain.com das Cookie für .domain.com gesetzt, was ein Subdomain-Tracking ermöglicht.

Wurde der Tracker erstellt, dann ist dieser dafür verantwortlich, dass Daten-Dimensionen im Kontext der jeweiligen Seite gesammelt und gelagert werden. Dazu gehören die besuchte URL, Seitentitel oder unter anderem auch Informationen zu Gerät, mit dem der Nutzer auf die Website zugreift.

Als viertes Argument kann auch optional ein eigener Tracker-Name mitgegeben werden. Wird der Name nicht gesetzt, dann ist der Standard-Tracker mit dem Namen “t0” aktiv. Möchte man jedoch einen eigenen Namen definieren, dann wird dies als viertes Argument mitgegeben:

ga('create', 'UA-XXXXX-Y', 'auto', 'meinNeuerTracker');

Wenn man bestimmte Felder für den Tracker setzen möchte, welche für jeden Hit des Trackers mitgeschrieben werden, dann kann noch das fieldsObject gesetzt werden:

ga('create', 'UA-XXXXX-Y', 'auto', 'meinNeuerTracker', {
  userId: '12345'
});

Mittels dem fieldsObject lässt sich aber auch eine alternative Schreibweise festlegen:

ga('create', {
  trackingId: 'UA-XXXXX-Y',
  cookieDomain: 'auto',
  name: 'meinNeuerTracker',
  userId: '12345'
});

Sollten mehrere Tracker auf einer Seite laufen müssen, dann muss min. einer dieser Tracker einen eigenen Namen bekommen:

ga('create', 'UA-XXXXX-Y', 'auto');
ga('create', 'UA-XXXXX-Z', 'auto', 'meinNeuerTracker');

Damit man dann auch separate Befehle auslösen kann, muss bei benannten Trackern der Name des Trackers vor dem Befehl mitgegeben werden:

ga('send', 'pageview');
ga('meinNeuerTracker.send', 'pageview');

Getter und Setter

Nachdem der create-Befehl fertig ausgeführt wurde, kann mittels einem Ready-Callbacks auf den Tracker zugegriffen werden, um Daten und Informationen zu extrahieren oder Daten zu setzen. Wichtig ist, dass das Ready-Callback nachdem dem create-Befehl aufgerufen wird. Nur so stellt man sicher, dass das Ready-Callback erst ausgeführt wird, nachdem create seine Arbeit getan hat. Hier ein Beispiel wie auf den Tracker zugegriffen wird:

ga('create', 'UA-XXXXX-Y', 'auto');
 
ga(function(tracker) {
  console.log(tracker);
});

Um auf Tracker-Objekte und -Informationen zuzugreifen gibt es 2 Methoden: getByName und getAll. Mit getByName greift man auf Tracker zu, wenn man auf einen bestimmten Tracker zugreifen möchte und den Namen dabei kennt:

ga('create', 'UA-XXXXX-Y', 'auto', 'meinNeuerTracker');
 
ga(function() {
  console.log(ga.getByName('meinNeuerTracker'));
});

Mit getAll bekommt man alternativ ein Array mit allen Trackern auf der Seite:

ga('create', 'UA-XXXXX-Y', 'auto', 'meinTracker1');
ga('create', 'UA-XXXXX-Z', 'auto', 'meinTracker2');
 
ga(function() {
  console.log(ga.getAll());
});

Hat man damit eine Referenz zu einem Tracker hergestellt, ist die Frage, wie man auf einzelne Felder zugreifen kann. Das geschieht mit der get-Methode. Hier ein paar Beispiele:

ga('create', 'UA-XXXXX-Y', 'auto');
 
ga(function(tracker) {
  // Zeigt den Namen des Trackers
  console.log(tracker.get('name'));
 
  // Zeigt die Bildschirm-Auflösung
  console.log(tracker.get('screenResolution'));
 
  // Zeigt die aufgerufene URL an
  console.log(tracker.get('location'));
});

Man kann jedoch auch Daten in Tracker-Objekten aktualisieren. Das geschieht mit der set-Methode. Die set-Methode kann innerhalb der ga-Funktion oder auch direkt auf dem Tracker selbst angewandt werden. Möchte man das page-Feld mit der ga-Funktion ändern, dann sieht das wie folgt aus:

ga('set', 'page', '/ueber');

Wie man sieht, wird neben set noch das Feld und der entsprechende Wert gesetzt. Alternativ kann es auch als fieldsObject mitgegeben werden:

ga('set', {
  page: '/ueber'
});

Mit Trackern, die einen selbst definierten Namen besitzen, funktioniert es genauso:

ga('meinTracker.set', 'page', '/ueber');

Neben der ga-Funktion, können die Werte auch direkt am Tracker-Objekt selbst aktualisiert werden:

ga(function(tracker) {
  tracker.set('page', '/ueber');
});

Zu beachten ist, dass sich Tracker nicht selbst aktualisieren. Wenn man bestimmte Änderungen an der Seite – durch den Nutzer ausgelöst – an Google Analytics schicken möchte, dann müssen diese Änderungen auch an den Tracker wieder übergeben werden.

Tasks

Nachdem mittels get- und set-Methode der Tracker mit zusätzlichen Daten angereichert wurde, wird mit der send-Methode der Hit an Google Analytics geschickt:

ga('send', 'pageview');

Damit wird das Tracker-Objekt (und die darin gespeicherten Daten) geschickt, welcher in der Codezeile zuvor “geplant” war (da in der Warteschlange). Die im Tracker gespeicherten Daten werden damit dann geschickt. Unter der Haube passiert aber deutlich mehr. Jedes Mal wenn die send-Methode aufgerufen wird, führt analytics.js einige “Tasks” in immer gleicher Reihenfolge aus. Damit wird der endgültig zu sendende Hit validiert, konstruiert und dann erst geschickt. Folgende Tasks werden der Reihe nach ausgeführt:

Name des TasksBeschreibung
customTaskDieser Task macht standardmäßig nichts. Dieser kann aber mit eigenen Logiken für ein benutzerdefiniertes Verhalten genutzt und modifiziert werden.
previewTaskDamit wird der Request abgebrochen, wenn der Aufruf von Safari über das “Top Sites”-Thumbnail kommt.
checkProtocolTaskAuch hier wird die Anfrage abgebrochen, jedoch wenn das Seitenprotokoll nicht https oder https ist.
validationTaskWenn Pflichtfelder fehlen oder ungültig sind, dann wird die Anfrage ebenfalls abgebrochen.
checkStorageTaskWenn der Tracker von Cookies abhängt, aber der Nutzer Cookies deaktiviert hat, dann wird der Request ebenfalls abgebrochen.
historyImportTaskFalls man zu Universal Analytics von ga.js oder urchin.js migriert, dann stellt dieser Task sicher, dass Informationen aus den alten Trackern importiert werden.
samplerTaskMit der Sample Rate kann festgelegt werden, welcher Prozentteil der Nutzer getrackt werden soll (um z.B. nicht über die Hit-Limitierungen zu kommen). Dieser Task stellt sicher, dass der Hit nicht rausgeht, wenn man über der Schwelle ist.
buildHitTaskDieser Task erstellt den Payload und speichert es im hitPayload-Feld.
sendHitTaskÜberträgt den hitPayload an den Google Analytics-Server.
timingTaskWenn man für Pagespeed-Messung das Sampling eingestellt hat (siteSpeedSampleRate), dann schickt dieser Task die Timing-Daten an Google Analytics.
displayFeaturesTaskWenn man Anzeigenfunktionen in Google Analytics geschickt hat, dann geht von hieraus der Hit an die DoubleClick-Server.

Bei den Tasks handelt es sich um JavaScript-Funktionen, die als Input den “model”-Parameter entgegennehmen. model ist ein Objekt, mit dem man auf alle Felder von analytics.js zugreifen kann. Nun kann auf Tasks zugegriffen (mit der get-Methode) und es können auch Änderungen durchgeführt werden (mit der set-Methode). Dadurch können einzelne Tasks ersetzt, mit eigenen Logiken ergänzt oder vollständig deaktiviert werden.

Um einen Task mit einer anderen Logik zu überschreiben, wird mit der set-Methode der entsprechende Task mit einer Funktion ersetzt:

ga('create', 'UA-XXXXX-Y', 'auto');
ga('set', 'sendHitTask', function(model) {
  // Hier kommt die eigene Logik
});

Ein Task kann aber auch vollständig deaktiviert werden, indem man ihn mit null ersetzt:

ga('create', 'UA-XXXXX-Y', 'auto');
ga('set', 'checkProtocolTask', null); // checkProtocolTask wird deaktiviert
ga('send', 'pageview');

Mit Tasks stellt Google Analytics ein mächtiges Werkzeug zur Verfügung, mit denen man die Daten, die rausgehen, individualisieren kann. Vor allem mit dem customTask lässt sich viel anstellen. Dazu empfehle ich dir den customTask-Guide von Simo.

Daten verschicken

Nachdem die Tasks abgearbeitet wurden, wird der Hit nun endlich verschickt:

ga('send', 'pageview');

Wie oben schon geschrieben: Damit wird das Tracker-Objekt (und die darin gespeicherten Daten) geschickt, welcher in der Codezeile zuvor “geplant” war (da in der Warteschlange). Wichtig ist vor allem (und ist Pflicht), den Hit-Typ mitzugeben. In unserem Fall wird ein Seitenaufruf (pageview) verschickt. Es existieren noch:

  • screenview
  • event
  • transaction
  • item
  • social
  • exception
  • timing

Der Hit geht dann an google-analytics.com/collect mit dem dazugehörigen Payload. Neben dem Hit-Typ kann die send-Methode auch weitere Parameter entgegennehmen. Die Syntax der send-Methode sieht wie folgt aus:

ga('[trackerName.]send', [hitType], [...fields], [fieldsObject]);

Wie man sehen kann, kann vor dem send auch ein spezifischer Tracker-Name definiert werden. Werden in der send-Methode einzelne Felder mitgegeben, dann werden diese für den Hit herangezogen. Falls diese schon im Tracker existieren, dann überschreiben die Felder in der set-Methode die Werte aus dem Tracker. Beispiel:

ga('send', {
hitType: 'event',
eventCategory: 'Video',
eventAction: 'play',
eventLabel: 'intro.mp4'
});

Alternative Schreibweise wäre:

ga('send', 'event', 'Video', 'play', 'intro.mp4');

Außerdem können die Werte auch auf den Tracker direkt gesetzt werden:

ga(function(tracker) {
  tracker.send('event', 'Video', 'play', 'cats.mp4');
});

Beachte bez. Schreibweise und Felder immer die send-Methode-Referenz. Außerdem gibt es verschiedene nützliche Felder – siehe bei Field Reference. Spannend ist vor allem der hitCallback. Dabei handelt es sich um eine Funktion, die aufgerufen wird, sobald ein Hit an Google Analytics rausging. Damit kann man also nochmal reagieren, wenn der Hit abgeschlossen wurde. Der hitCallback wird als Feld mitgegeben:

ga('send', 'pageview', {
  'hitCallback': function() {
    alert('hit sent');
  }
});

hitCallback kann auch bei einem Event gesendet werden. Das kann ggf. zu Problemen führen – vor allem wenn nach dem Event eine neue Seite geladen wird. Beispiel: Klick auf dem Formular-Absenden-Button. Hat man hier ein Event drauf und dann noch ein hitCallback, kann es sein, dass das hitCallback nicht ausgeführt wird. Grund ist, dass die meisten Browser JavaScript nicht mehr ausführen, sobald eine neue Seite anfängt zu laden. Das kann man jedoch mit dem beacon-Feld umgehen. analytics.js schickt Daten normalerweise als image- oder xhr-Request. Es lässt sich aber eine dritte Option definieren: Die Methode navigator.sendBeacon. Unterstützt der Browser dieses Feature, dann kann man das soeben genannte Problem umgehen:

ga('send', 'pageview', {
  'transport': 'beacon',
  'hitCallback': function() {
    alert('hit sent');
  }
});

Plugins nutzen

analytics.js bietet noch viele weitere Funktionen, die nicht standardmäßig integriert sind. Diese Funktionalitäten können aber jederzeit und einfach an analytics.js gedockt werden. Dabei spricht man von Google Analytics-Plugins. Um Plugins zu laden, wird der Befehl require benutzt. Dieser nimmt den Plugin-Namen und weitere Optionen entgegen:

ga('[trackerName.]require', pluginName, [pluginOptions]);

Zum Beispiel kann die Datenerfassung für Werbefunktionen wie folgt aktiviert werden:

ga('myTracker.require', 'displayfeatures', {
  cookieName: 'display_features_cookie'
});

Ist das Plugin angefordert, können die Methoden des Plugins dann in der ga-Funktion genutzt werden:

ga('[trackerName.][pluginName:]methodName', ...args);

Der Aufruf der addProduct-Methode aus dem E-Commerce-Plugin würde dabei wie folgt aussehen:

ga('ec:addProduct', {
  'id': product.id,
  'name': product.name,
  'category': product.category,
  'brand': product.brand,
  'variant': product.variant,
  'price': product.price,
  'quantity': product.qty
});