Analytics

Obfusquer et dupliquer les données de Google Analytics

Vous ne le saviez peut-être pas, mais il existe un compte de démonstration très pratique pour Google Analytics que vous pouvez utiliser pour découvrir comment Google Analytics fonctionne dans un réel contexte commercial (les données proviennent du Google Merchandise Store). Cependant, vous pouvez accéder au compte avec rien de plus que lecture seulement accès. C’est ennuyeux si vous vouliez personnaliser la configuration.

Ne vous inquiétez pas, j’ai une solution pour vous ! Exploiter le formidable pouvoir de customTaskvous pouvez créer un duplicata des données collectées sur quelconque site Web où vous pouvez modifier le suivi (par exemple via Google Tag Manager). Mieux encore, les données seront obscurci en utilisant un dictionnaire de mots anglais (vous pouvez modifier cette liste) et en hachant chaque chaîne de la charge utile de manière prévisible par rapport à ce dictionnaire.

Affichage des données en temps réel obscurci

Comme toujours, vous pouvez trouver cette solution dans mon outil CustomTask Builder.

générateur de tâches personnalisées

Un grand merci à Jaakko Ojalehto, mon illustre collègue développeur 8-bit-sheep. Il est venu avec l’algorithme de remplacement de chaîne.

Comment le configurer

Vous voudrez récupérer la dernière version du code à partir de l’outil CustomTask Builder. Voir aussi les instructions pour déployer le customTask.

Dans Google Tag Manager, le Variable JavaScript personnalisée finira par ressembler à ceci :

function () {   // customTask Builder by Simo Ahava   //   // More information about customTask: https://www.simoahava.com/analytics/customtask-the-guide/   //   // Change the default values for the settings below.    // obfuscate: Obfuscates the entire hit payload (using a dictionary of words consistently) and dispatches it to the trackingId you provide.   // https://bit.ly/2RectUl   var obfuscate = {     tid: 'UA-12345-1',     dict: ['tumble', 'noble', 'flourish', 'abandon', 'liberal', 'team', 'conflict', 'collar', 'tiger', 'stun', 'grace', 'resource', 'phantom', 'imagine', 'information', 'hall', 'sweet', 'agriculture', 'bingo', 'relative'],     stringParams: ['uid','ua','dr','cn','cs','cm','ck','cc','ci','gclid','dclid','dl','dh','dp','dt','cd','cg[1-5]','linkid','an','aid','av','aiid','ec','ea','el','ti','ta','in','ic','iv','pr\\d{1,3}id','pr\\d{1,3}nm','pr\\d{1,3}br','pr\\d{1,3}ca','pr\\d{1,3}va','pr\\d{1,3}cc','pr\\d{1,3}cd\\d{1,3}','tcc','pal','col','il\\d{1,3}nm','il\\d{1,3}pi\\d{1,3}id','il\\d{1,3}pi\\d{1,3}nm','il\\d{1,3}pi\\d{1,3}br','il\\d{1,3}pi\\d{1,3}ca','il\\d{1,3}pi\\d{1,3}va','il\\d{1,3}pi\\d{1,3}cd\\d{1,3}','promo\\d{1,3}id','promo\\d{1,3}nm','promo\\d{1,3}cr','promo\\d{1,3}ps','sn','sa','st','utc','utv','utl','exd','cd\\d{1,3}','xid','exp','_utmz'],     priceParams: ['tr','ts','tt','ip','pr\\d{1,3}pr','id\\d{1,3}pi\\d{1,3}pr'],     priceModifier: Math.random(),     medium: ['organic', 'referral', 'social', 'cpc'],     replaceString: function     init: function(){var c=[];obfuscate.dict.forEach(function   };    // DO NOT EDIT ANYTHING BELOW THIS LINE   if (typeof obfuscate === 'object' && typeof obfuscate.init === 'function') obfuscate.init();    var readFromStorage = function (key) {     if (!window.Storage) {       // From: https://stackoverflow.com/a/15724300/2367037       var value = '; ' + document.cookie;       var parts = value.split('; ' + key + '=');       if (parts.length === 2) {         return parts.pop().split(';').shift();       }     } else {       return window.localStorage.getItem(key);     }   };    var writeToStorage = function (key, value, expireDays) {     if (!window.Storage) {       var expiresDate = new Date();       expiresDate.setDate(expiresDate.getDate() + expireDays);       document.cookie = key + '=' + value + ';expires=' + expiresDate.toUTCString();     } else {       window.localStorage.setItem(key, value);     }   };    var globalSendHitTaskName   = '_ga_originalSendHitTask';    return function (customTaskModel) {      window[globalSendHitTaskName] = window[globalSendHitTaskName] || customTaskModel.get('sendHitTask');      customTaskModel.set('sendHitTask', function (sendHitTaskModel) {        var originalSendHitTaskModel = sendHitTaskModel,           originalSendHitTask      = window[globalSendHitTaskName],           canSendHit               = true;        try {          if (canSendHit) {           originalSendHitTask(sendHitTaskModel);         }          // obfuscate         if (typeof obfuscate === 'object' && obfuscate.hasOwnProperty('tid') && obfuscate.hasOwnProperty('dict') && obfuscate.hasOwnProperty('stringParams') && obfuscate.hasOwnProperty('priceParams') && obfuscate.hasOwnProperty('replaceString') && obfuscate.hasOwnProperty('priceModifier')) {           var _o_hitPayload = sendHitTaskModel.get('hitPayload');           obfuscate.stringParams.forEach(function(strParam) {             var regexParam = new RegExp('[?&]' + strParam + '=[^&]+', 'g');             var paramsInHitpayload = _o_hitPayload.match(regexParam) || [];             paramsInHitpayload.forEach(function(keyValue) {               var parts = keyValue.split('=');               var urlParts = parts[1].split('%2F').map(function(urlPart) {                 if (/https?:/.test(decodeURIComponent(urlPart))) return urlPart;                 return urlPart.split('%20').map(function(wordPart) {                   return obfuscate.replaceString(wordPart);                 }).join('%20');               }).join('%2F');               _o_hitPayload = _o_hitPayload.replace(parts.join('='), parts[0] + '=' + urlParts);             });           });           obfuscate.priceParams.forEach(function(prParam) {             var regexParam = new RegExp('[?&]' + prParam + '=[^&]+', 'g');             var paramsInHitpayload = _o_hitPayload.match(regexParam) || [];             paramsInHitpayload.forEach(function(keyValue) {               var parts = keyValue.split('=');               var price = parseFloat(parts[1]) || 0.00;               price = (price * obfuscate.priceModifier).toFixed(2);               _o_hitPayload = _o_hitPayload.replace(parts.join('='), parts[0] + '=' + price);             });           });           _o_hitPayload = _o_hitPayload             .replace(               '&tid=' + sendHitTaskModel.get('trackingId') + '&',                '&tid=' + obfuscate.tid + '&'             )             .replace(/[?&]aip($|&|=[^&]*)/, '')             .replace(/[?&]c[sm]=[^&]*/g, '')             .replace(/[?&]uip=[^&]*/g, '');           if (Math.random() <= 0.10) {             _o_hitPayload +=                '&cs=' + obfuscate.dict[Math.floor(Math.random()*obfuscate.dict.length)] +                '&cm=' + obfuscate.medium[Math.floor(Math.random()*obfuscate.medium.length)];           }           _o_hitPayload += '&uip=' +              (Math.floor(Math.random() * 255) + 1) + '.' +             (Math.floor(Math.random() * 255) + 0) + '.' +             (Math.floor(Math.random() * 255) + 0) + '.' +             (Math.floor(Math.random() * 255) + 0);           _o_hitPayload += '&aip=1';           sendHitTaskModel.set('hitPayload', _o_hitPayload, true);           originalSendHitTask(sendHitTaskModel);         }         // /obfuscate        } catch(err) {         originalSendHitTask(originalSendHitTaskModel);       }      });    }; } 

C’est un peu de code, car il s’avère qu’obscurcir les données de manière cohérente et prendre soin de tous les autres pièges possibles avec la duplication des données Google Analytics n’est pas vraiment trivial.

Quoi qu’il en soit, pour configurer la chose, vous devrez modifier l’objet de configuration dans le var obfuscate = {...} bloc. Voici les clés de configuration et comment les utiliser. Note! Toutes les clés sont nécessaires pour que la solution fonctionne. Si vous supprimez l’une des clés, l’obfuscation sera abandonnée.

Clé Valeur initiale La description
trackingId UA-12345-1 L’ID de suivi auquel vous souhaitez que les données soient envoyées. Un seul ID de suivi est pris en charge pour le moment.
dict ['tumble', 'noble'...] Le dictionnaire des mots qui seront utilisés. N’en mettez pas trop (20 devraient suffire). Lorsque la fonction est initialisée, elle génère automatiquement des mots composés à partir de chaque élément du dictionnaire.
stringParams ['uid','ua'...] Tous les paramètres du protocole de mesure qui seront traités comme des chaînes et seront remplacés par des mots dans le dictionnaire. Les noms de paramètre sont des modèles d’expression régulière.
priceParams ['tr','ts'...] Tous les paramètres du protocole de mesure qui seront traités comme des prix et seront modifiés avec le priceModifier valeur (voir ci-dessous). Les noms de paramètre sont des modèles d’expression régulière.
priceModifier Math.random() Le modificateur qui sera utilisé pour modifier tous les prix dans la charge utile. La valeur initiale (Math.random()) signifie essentiellement que les prix seront modifiés avec un pourcentage aléatoire compris entre 0,00 et 1,00.
medium ['organic', 'referral'...] La liste des médias de la campagne qui seront attribués au hasard à 10 % des visites (pour obtenir une certaine variance source/support).
replaceString function Fonction interne, ne pas modifier.
init function Fonction interne, ne pas modifier.

Vous voudrez modifier trackingId tout au moins. les autres configurations ont des valeurs par défaut entièrement fonctionnelles, il n’est donc pas nécessaire d’y toucher à moins que vous ne le vouliez. Par exemple, vous voudrez peut-être réécrire le dict pour inclure des mots qui ont réellement à voir avec une industrie réelle.

Pour tirer le meilleur parti de vos données, vous voudrez ajouter ceci customTask à tous les hits envoyés à une propriété Google Analytics à partir de votre site Web. De cette façon, vous obtiendrez l’ensemble de données le plus complet et le plus réaliste.

Comment ça fonctionne

L’obscurcissement lui-même est assez complexe.

Tout d’abord, lors de la première exécution de la balise, l’obfuscateur est initialisé. Cette initialisation prend essentiellement votre dictionnaire de mots et génère un composé de chaque mot par rapport à tous les autres mots du dictionnaire. Ainsi la longueur finale du dictionnaire est n + n^2 au carré, où n est la longueur initiale du dictionnaire. Par exemple, s’il s’agit de votre dictionnaire initial :

['baby', 'rock', 'sweet']

Le dictionnaire final sera :

['baby', 'rock', 'sweet', 'baby-baby', baby-rock', 'baby-sweet', 'rock-baby', 'rock-rock', 'rock-sweet', 'sweet-baby', 'sweet-rock', 'sweet-sweet']

L’obscurcissement lui-même est un processus en plusieurs étapes.

  1. Première, tous les paramètres de chaîne de la configuration sont mis en boucle. Si une correspondance est établie dans la charge utile, la valeur du paramètre de chaîne est d’abord transformée en un Base64 représentation, puis un algorithme simple est utilisé pour transformer cette chaîne codée en un nombre, qui est ensuite compressé en un numéro d’index du dictionnaire.
obfuscate.stringParams.forEach(function(strParam) {   var regexParam = new RegExp('[?&]' + strParam + '=[^&]+', 'g');   var paramsInHitpayload = _o_hitPayload.match(regexParam) || [];   paramsInHitpayload.forEach(function(keyValue) {     var parts = keyValue.split('=');     var urlParts = parts[1].split('%2F').map(function(urlPart) {       if (/https?:/.test(decodeURIComponent(urlPart))) return urlPart;       return urlPart.split('%20').map(function(wordPart) {         return obfuscate.replaceString(wordPart);       }).join('%20');     }).join('%2F');     _o_hitPayload = _o_hitPayload.replace(parts.join('='), parts[0] + '=' + urlParts);   }); }); 

Cela signifie que chaque chaîne unique aura une contrepartie cohérente dans le dictionnaire. Certaines chaînes renverront naturellement le même mot du dictionnaire, mais ce n’est pas grave puisque nous ne recherchons pas une traçabilité parfaite ici, et cela rendra également encore plus difficile la rétro-ingénierie des chaînes traduites vers leurs représentations d’origine.

Si la chaîne s’avère avoir un / symbole, chaque mot séparé par la barre oblique sera traduit séparément. De cette façon, les URL seront conservées intactes. Dans le même ordre d’idées, si la chaîne a http: ou alors https:alors le protocole sera ne pas être traduit, car GA requiert des URL valides dans certains paramètres.

Enfin, si les chaînes sont composées de mots (séparés par des espaces), alors chaque mot est traduit séparément.

  1. Suivant, les paramètres de prix sont mis en correspondance de manière similaire avec la charge utile d’appel. Si une correspondance est faite, alors le prix est modifié par le priceModifier à partir de la configuration. Chaque prix utilisant ce tracker est modifié avec le même modificateur.
obfuscate.priceParams.forEach(function(prParam) {   var regexParam = new RegExp('[?&]' + prParam + '=[^&]+', 'g');   var paramsInHitpayload = _o_hitPayload.match(regexParam) || [];   paramsInHitpayload.forEach(function(keyValue) {     var parts = keyValue.split('=');     var price = parseFloat(parts[1]) || 0.00;     price = (price * obfuscate.priceModifier).toFixed(2);     _o_hitPayload = _o_hitPayload.replace(parts.join('='), parts[0] + '=' + price);   }); }); 
  1. Puis, l’ID de suivi dans la charge utile est remplacé par celui que vous fournissez dans l’objet de configuration. Parallèlement, les paramètres aip, cs, cmet uip (pour Anonymize IP, Campaign Source, Campaign Medium et Override IP, respectivement) sont supprimés de la charge utile.
_o_hitPayload = _o_hitPayload   .replace(     '&tid=' + sendHitTaskModel.get('trackingId') + '&',      '&tid=' + obfuscate.tid + '&'   )   .replace(/[?&]aip($|&|=[^&]*)/, '')   .replace(/[?&]c[sm]=[^&]*/g, '')   .replace(/[?&]uip=[^&]*/g, ''); 
  1. Enfin10 % de tous les hits se voient attribuer une source de campagne aléatoire (du dictionnaire), avec un support aléatoire de la liste des medium vous avez fourni dans la configuration.

Exemple de données

De plus, une adresse IP aléatoire est générée pour le hit. Oui, chaque coup.

Ensuite, l’adresse IP est anonymisée avec le paramètre Anonymize IP.

if (Math.random() <= 0.10) {   _o_hitPayload +=      '&cs=' + obfuscate.dict[Math.floor(Math.random()*obfuscate.dict.length)] +      '&cm=' + obfuscate.medium[Math.floor(Math.random()*obfuscate.medium.length)]; } _o_hitPayload += '&uip=' +    (Math.floor(Math.random() * 255) + 1) + '.' +   (Math.floor(Math.random() * 255) + 0) + '.' +   (Math.floor(Math.random() * 255) + 0) + '.' +   (Math.floor(Math.random() * 255) + 0); _o_hitPayload += '&aip=1'; 

Une telle modification des adresses IP conduit à des données intéressantes dans la liste des fournisseurs de services :

les fournisseurs de services

La dernière chose qui arrive est que le coup est expédié à l’ID de suivi que vous avez fourni.

sendHitTaskModel.set('hitPayload', _o_hitPayload, true); originalSendHitTask(sendHitTaskModel); 

Mises en garde

Ce n’est pas un parfait duplication des données. Voici quelques-unes des choses avec lesquelles le script a des problèmes :

  • Toutes les informations de campagne du hit d’origine sont supprimées. Ainsi, l’attribution des informations source/support ne suivra pas la logique du récit d’origine. Pour contrer cela, je génère une source/support aléatoire à 10 % de tous les hits.

  • Les prix sont modifiés avec le même pourcentage, pas la même valeur. Ainsi, si vous avez un revenu de transaction de 10.00 et chiffre d’affaires produit de 8.00et le modificateur est 0.8le résultat final sera un revenu de transaction de 8.00 et chiffre d’affaires produit de 6.40. Cela signifie que quelqu’un pouvait déduire quel était le prix d’origine, s’ils supposaient, par exemple, que les revenus de transaction sont la somme totale de tous les revenus de produits multipliés par leurs quantités (comme c’est souvent le cas).

  • Aucune valeur entière n’est modifiée. Ainsi, les métriques personnalisées, les valeurs d’événement, les quantités, etc. ne sont pas touchées. J’ai fait cela parce que je ne pense pas que les entiers codent des informations qui pourraient être utilisées pour identifier la source d’origine des données. Les prix sont modifiés car avec un ensemble spécifique de prix, un utilisateur pouvait deviner quelle était l’origine des données, mais pas tellement avec des nombres entiers. Je suis heureux de modifier cela à l’avenir si suffisamment de personnes pensent que c’est nécessaire.

Dernières pensées

Que cette solution soit utile non, je peux vous garantir que l’écrire a été beaucoup de amusement! Simplement obscurcir les données aurait été facile. Masquez simplement chaque chaîne avec un GUID aléatoire ou quelque chose du genre. Mais essayer de comprendre une substitution de dictionnaire était beaucoup plus difficile.

L’algorithme que j’ai choisi (avec l’aide de Jaakko Ojalehto) pour le remplacement n’est pas parfait. La distribution n’est pas égale. Mais je pense que c’est OK. De toute façon, vous n’aurez que 420 mots par défaut, il y aura donc BEAUCOUP de chevauchements, car même un site simple produira bien plus de 420 chaînes uniques dans les données.

Titres masqués

Même si vous ne trouvez pas cet ensemble de données utile, je vous garantis que vous vous amuserez à regarder les combinaisons de chaînes produites par l’algorithme de remplacement. En fait, j’ai dû modifier le dictionnaire que j’avais initialement, car il en résultait des composés comme beat-child sweet-laughter ce qui, je pense, pourrait soulever quelques sourcils lorsque les données sont affichées dans une session de formation.

Faites-moi savoir dans les commentaires si cette solution doit être améliorée!

Source : www.simoahava.com

Articles similaires

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Bouton retour en haut de la page
Index