Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
JavaScript Variable für Google Tag Manager zur Ermittlung eines Bot Markers (zur Verwendung als Benutzerdefinierte Dimension)
function(){
//----------------------------------------------------------------
// Erkennung von Crawlern anhand User-Agent oder Feature-Detection
//----------------------------------------------------------------
//Soll ein einmal bestandender Test innerhalb der Session reichen? Dann hier einschalten
var cache2Session = true;
// --- Ende Setup ---
//Bei mehreren Aufrufen auf einer Seite das letzte Ergebnis wiederverwenden
if ((typeof(window.gtmCachedBotScore) !== 'undefined')) return window.gtmCachedBotScore;
//Bei aktivem Session-Caching vorhandenen Wert prüfen und zurückgeben
cache2Session = cache2Session && (window.sessionStorage != undefined);
if (cache2Session == true) {
var sessionCachedBotScore = sessionStorage.getItem("sessionCachedBotScore");
if (sessionCachedBotScore == "OK") return "OK";
}
//-----------------------------------------------------
// Prüfung auf User-Agents bekannter rendernder Crawler
//-----------------------------------------------------
var agentString = window.navigator.userAgent;
var bots_ua = new Array(
"Googlebot|GoogleBot",
"Google Search Console",
"Chrome-Lighthouse",
"DuckDuckBot",
"JobboerseBot",
"woobot",
"PingdomPageSpeed",
"PagePeeker",
"Refindbot",
"HubSpot",
"Investment Crawler",
"BingPreview",
"Baiduspider",
"Sogou",
"SISTRIX",
"facebookexternalhit",
"Site-Shot",
"wkhtmltoimage"
);
var rs = "";
for (i=0;i<bots_ua.length;i++) {
var bt = bots_ua[i];
if (RegExp(bt).test(agentString)) {rs = bt; break;}
}
if (rs) {
rs = rs.split("|")[0].toUpperCase()+"_UA";
} else {
//Crawler, Spider, Bot im Agent String suchen
if (/crawler/.test(agentString.toLowerCase())) rs = "POTENTIAL_CRAWLER_UA";
else if (/spider/.test(agentString.toLowerCase())) rs = "POTENTIAL_SPIDER_UA";
else if (/bot/.test(agentString.toLowerCase()) &&
!/cubot/.test(agentString.toLowerCase())) rs = "POTENTIAL_BOT_UA";
}
//------------------------------------------------------------
// Headless Browser anhand User-Agent oder Merkmalen erkennen
//------------------------------------------------------------
//Genutzte Quellen:
// - https://github.com/antoinevastel/fpscanner/blob/master/src/fpScanner.js
// - https://github.com/infosimples/detect-headless
//Zu Wirksamkeit siehe: https://de.slideshare.net/SergeyShekyan/shekyan-zhang-owasp
function iFrameChrome() {
//Test kann nur durchgefuehrt werden, wenn schon ein body existiert, sonst per Rueckgabewert uebergehen
if (!document.body) return 'too_early';
var iframe = document.createElement('iframe');
iframe.srcdoc = 'blank page';
document.body.appendChild(iframe);
var result = typeof iframe.contentWindow.chrome;
iframe.remove();
return result;
}
if (rs == "") {
var canvas = document.createElement('canvas');
var gl = canvas.getContext('webgl');
var vendor = '';
var renderer = '';
try {
var canvas = document.createElement('canvas');
var gl = canvas.getContext('webgl');
var debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
} catch (e) { }
var iScreenAvailWidth = window.screen.availWidth;
var iScreenAvailHeight = window.screen.availHeight;
var iScreenWidth = window.screen.width;
var iScreenHeight = window.screen.height;
// Querformat Test
if((iScreenAvailWidth > iScreenAvailHeight && iScreenWidth < iScreenHeight) || (iScreenAvailWidth < iScreenAvailHeight && iScreenWidth > iScreenHeight)) {
var iScreenAvailWidth = window.screen.availHeight;
var iScreenAvailHeight = window.screen.availWidth;
}
if (/HeadlessChrome/.test(agentString)) rs = "HEADCHR_UA";
else if (/MSIE 5.0/.test(agentString)) rs = "IE5_UA";
else if (/PhantomJS/.test(agentString)) rs = "PHANTOM_UA";
else if ((/Chrome/.test(agentString)) && (navigator.webdriver)) rs = "WEBDRIVER";
//Der folgende "iframe"-Test generiert "In App Browser-Treffer", die offenbar von FB und Instagram etc. stammen.
//User oder Preview? Unklar. Siehe https://firmhouse.com/blog/filtering-facebook-search-spiders-bots-and-other-automated-requests-fb_iab/
//Hier klingt es nach Bot, aber mir fehlt der Glaube an die Herleitung und Tests haben reale Besuche als false Positive enthalten,
//daher die Zusatzprüfung auf FB_IAB im User Agent
else if ((/Chrome/.test(agentString)) && (!/FB_IAB/.test(agentString)) && (iFrameChrome() === 'undefined')) rs = "HEADCHR_IFRAME";
else if (!/Trident|MSIE|Edge/.test(agentString) && (navigator.languages === "")) rs = "PHANTOM_LANGUAGE";
else if (window.external && window.external.toString && window.external.toString().indexOf('Sequentum') > -1) rs = "SEQUENTUM";
else if (iScreenAvailWidth > iScreenWidth && iScreenAvailHeight > iScreenHeight) rs = "PHANTOM_WINDOW_HEIGHT";
//Hier sind Fehltreffer nicht ausgeschlossen, da dieses Ergebnis sich innerhalb einer Sitzung auch mit "OK" abwechseln kann.
//Daher ist oben das Caching auf Sessionbasis vorgesehen, sobald alle Tests einmal mit "OK" bestanden wurden
else if ((window.outerHeight === 0) && (window.outerWidth === 0)) rs = "NO_OUTER_DIMENSION";
else if ((window.screen.height <= 100) && (window.screen.width <= 100)) rs = "DEVICE_TOO_SMALL";
else if (('_Selenium_IDE_Recorder' in window) || ('callSelenium' in window) || ('_selenium' in window) ||
('__webdriver_script_fn' in document) || ('__driver_evaluate' in document) ||
('__webdriver_evaluate' in document) || ('__selenium_evaluate' in document) || ('__fxdriver_evaluate' in document) ||
('__driver_unwrapped' in document) || ('__webdriver_unwrapped' in document) ||
('__selenium_unwrapped' in document) || ('__fxdriver_unwrapped' in document) ||
('__webdriver_script_func' in document) || (document.documentElement.getAttribute("selenium") !== null) ||
(document.documentElement.getAttribute("webdriver") !== null) ||
(document.documentElement.getAttribute("driver") !== null)) rs = "SELENIUM_DRIVER";
else if (('callPhantom' in window) || ('_phantom' in window) || ('phantom' in window)) rs = "PHANTOM_PROPERTIES";
else if ('__stopAllTimers' in window) rs = "JSDOM_PROPERTIES";
else if ((/Firefox/.test(agentString)) && (!/Seamonkey/.test(agentString)) && (window.mozPaintCount === 'undefined')) rs = "FF_NO_PAINTCOUNT";
else if(vendor == "Brian Paul" && renderer == "Mesa OffScreen") rs = "HEADCHR_WEBGL";
else rs = "OK";
}
window.gtmCachedBotScore = rs;
if ((cache2Session == true) && (rs == "OK")) sessionStorage.setItem("sessionCachedBotScore", "OK");
return rs;
}
@mbaersch

This comment has been minimized.

Copy link
Owner Author

@mbaersch mbaersch commented Apr 14, 2019

Dient zur Markierung von Hits oder Sitzungen zur groben Untersuchung potentieller Bot-Treffer in Google Analytics in einer Benutzerdefinierten Dimension. Eine ausführliche Anleitung findet sich im Blog unter https://www.markus-baersch.de/blog/headless-browser-in-google-analytics-erkennen/

Verwendung:

  1. Neue JavaScript Variable in Google Tag Manager anlegen und obigen Code per Copy & Paste einfügen
  2. In GA Benutzerdefinierte Dimension für "Bot Marker" auf Hit- oder Sitzungsebene anlegen
  3. In den Google Analytics Settings oder direkt beim Tag den Index der gewünschten Benutzerdefinierten Dimension angeben und JS-Variable als Wert nutzen

Implementiert nur ausgewählte (einfache) Tests aus den o. a. Beispielquellen für einen ersten Überblick. Kann durch die übrigen Tests der im Script verlinkten Lösungen ergänzt werden; ersetzt aber auch dann nicht einen wirkungsvolleren serverseitigen Schutz, der z. B. auch das Timing und andere Merkmale der Requests berücksichtigen kann.

Zur weiteren Analyse ist es sinnvoll, sich auch den User Agent in einer Dimension zu speichern. Dieser steckt in der JS-Variable window.navigator.userAgent und kann so einfach im GTM eingelesen und ebenso als Dimension verwendet werden. Auf diese Weise können auch die dabei beobachteten erkannten Bots / Spider / Crawler oder anderere Renderer in die Liste im oberen Teil eingetragen werden, so dass die Liste stets verbessert werden kann.

Wenn einzelne Marker sicher genug als Bots identifiziert sind und nennenswerte Hits verbrauchen, können diese entweder

a. gefiltert
b. in eine andere Property gesendet oder
c. als blockierender Trigger für GA Tags im GTM genutzt werden

um die Hits zu separieren oder zu vermeiden. Auch die Vergabe einer UserId für bekannte Crawler ist ein Weg, diese genauer zu beobachten...

@mbaersch

This comment has been minimized.

Copy link
Owner Author

@mbaersch mbaersch commented Jul 18, 2019

Updatehinweis: "PHANTOM_WINDOW_HEIGHT" als Test nun reaktiviert - Danke an Wolfram Bartke für die Korrektur und Verbesserung!

@mbaersch

This comment has been minimized.

Copy link
Owner Author

@mbaersch mbaersch commented Jul 22, 2019

Neue Tests:

  • mozPaintCount als Eigenschaft für Firefox
  • "Pflicht-Fonterkennung" für iOS Geräte (Implementierung testweise)
  • JSDOM Erkennung anhang window-Eigenschaften (Implementierung testweise)
@mbaersch

This comment has been minimized.

Copy link
Owner Author

@mbaersch mbaersch commented Jul 22, 2019

Neuer Test: HEADCHR_WEBGL - WebGL Versionsinfo-Prüfung (gem. https://antoinevastel.com/bot%20detection/2017/08/05/detect-chrome-headless.html)

@mbaersch

This comment has been minimized.

Copy link
Owner Author

@mbaersch mbaersch commented Jul 23, 2019

Fonterkennung wieder deaktiviert - wenn er wieder genutzt werden soll, dann muss das für ausgewählte Geräte passieren. Alte iOS Geräte verfügen nicht über den geprüften Font.

@mbaersch

This comment has been minimized.

Copy link
Owner Author

@mbaersch mbaersch commented Jul 23, 2019

Caching für mehrere Aufrufe auf einer Seite und optionales Caching in der Session für einen einmal bestandenen Test. Zweck der Speicherung in der Session ist das Verhindern von False Positives, da zumindest lt. Beobachtung auf Hitlevel innerhalb einer Session das Ergebnis zwischen "OK" und "NO_OUTER_DIMENSION" schwanken kann.

@mbaersch

This comment has been minimized.

Copy link
Owner Author

@mbaersch mbaersch commented Oct 24, 2019

Chrome-Lighthouse als bekannten User Agent hinzugefügt.

@mbaersch

This comment has been minimized.

Copy link
Owner Author

@mbaersch mbaersch commented Nov 12, 2019

Der Test zu HEADCHR_IFRAME wird nun nur noch ausgeführt, wenn bereits ein body-Element existiert. Bisherige Fehltreffer sollten damit ausbleiben.

@Pazekal90

This comment has been minimized.

Copy link

@Pazekal90 Pazekal90 commented Jan 22, 2020

Danke für das Script. !!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.