Wed. Sep 11th, 2024

Ofuscar y duplicar datos de Google Analytics


Quizás no lo sabías, pero hay una forma muy útil de… cuenta demo para Google analitico Puede utilizarlo para comprobar cómo funciona Google Analytics en un actual contexto empresarial (los datos son del Tienda de artículos de Google). Sin embargo, puedes acceder a la cuenta sin nada más que solo lectura acceso. Esto es molesto si desea personalizar la configuración.

No te preocupes, ¡tengo una solución para ti! Aprovecha el increíble poder de customTaskpuede crear un duplicado de los datos recopilados en cualquier sitio net donde puede modificar el seguimiento (por ejemplo, a través de Administrador de etiquetas de Google). Mejor aún, los datos serán ofuscado utilizando un diccionario de palabras en inglés (puede editar esta lista) y haciendo un hash de cada cadena en la carga útil de manera predecible contra este diccionario.

Como siempre, puedes encontrar esta solución en mi Herramienta de creación de tareas personalizadas.

Muchísimas gracias a Jaakko Ojalehtomi ilustre Oveja de 8 bits colega desarrollador. Se le ocurrió el algoritmo de reemplazo de cadenas.


X


El boletín informativo de Simmer

Suscríbete a la Boletín informativo de Simmer ¡Para recibir las últimas noticias y contenidos de Simo Ahava en tu bandeja de entrada de correo electrónico!

Cómo configurarlo

Querrá obtener la última versión del código de Herramienta de creación de tareas personalizadas. Vea también las instrucciones para Cómo implementar el customTask.

En Google Tag Supervisor, el Variable de JavaScript personalizada Terminará luciendo algo como esto:

operate () {
  // customTask Builder by Simo Ahava
  //
  // Extra details about customTask: https://www.simoahava.com/analytics/customtask-the-guide/
  //
  // Change the default values for the settings under.

  // obfuscate: Obfuscates your entire hit payload (utilizing a dictionary of phrases constantly) and dispatches it to the trackingId you present.
  // https://bit.ly/2RectUl
  var obfuscate = {
    tid: 'UA-12345-1',
    dict: ('tumble', 'noble', 'flourish', 'abandon', 'liberal', 'group', 'battle', 'collar', 'tiger', 'stun', 'grace', 'useful resource', 'phantom', 'think about', 'data', 'corridor', 'candy', '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','support','av','aiid','ec','ea','el','ti','ta','in','ic','iv','prd{1,3}id','prd{1,3}nm','prd{1,3}br','prd{1,3}ca','prd{1,3}va','prd{1,3}cc','prd{1,3}cdd{1,3}','tcc','pal','col','ild{1,3}nm','ild{1,3}pid{1,3}id','ild{1,3}pid{1,3}nm','ild{1,3}pid{1,3}br','ild{1,3}pid{1,3}ca','ild{1,3}pid{1,3}va','ild{1,3}pid{1,3}cdd{1,3}','promod{1,3}id','promod{1,3}nm','promod{1,3}cr','promod{1,3}ps','sn','sa','st','utc','utv','utl','exd','cdd{1,3}','xid','exp','_utmz'),
    priceParams: ('tr','ts','tt','ip','prd{1,3}pr','idd{1,3}pid{1,3}pr'),
    priceModifier: Math.random(),
    medium: ('natural', 'referral', 'social', 'cpc'),
    replaceString: operate
    init: operate(){var c=();obfuscate.dict.forEach(operate
  };

  // DO NOT EDIT ANYTHING BELOW THIS LINE
  if (typeof obfuscate === 'object' && typeof obfuscate.init === 'operate') obfuscate.init();

  var readFromStorage = operate (key) {
    if (!window.Storage) {
      // From: https://stackoverflow.com/a/15724300/2367037
      var worth = '; ' + doc.cookie;
      var elements = worth.cut up('; ' + key + '=');
      if (elements.size === 2) {
        return elements.pop().cut up(';').shift();
      }
    } else {
      return window.localStorage.getItem(key);
    }
  };

  var writeToStorage = operate (key, worth, expireDays) {
    if (!window.Storage) {
      var expiresDate = new Date();
      expiresDate.setDate(expiresDate.getDate() + expireDays);
      doc.cookie = key + '=' + worth + ';expires=' + expiresDate.toUTCString();
    } else {
      window.localStorage.setItem(key, worth);
    }
  };

  var globalSendHitTaskName   = '_ga_originalSendHitTask';

  return operate (customTaskModel) {

    window(globalSendHitTaskName) = window(globalSendHitTaskName) || customTaskModel.get('sendHitTask');

    customTaskModel.set('sendHitTask', operate (sendHitTaskModel) {

      var originalSendHitTaskModel = sendHitTaskModel,
          originalSendHitTask      = window(globalSendHitTaskName),
          canSendHit               = true;

      attempt {

        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(operate(strParam) {
            var regexParam = new RegExp('(?&)' + strParam + '=(^&)+', 'g');
            var paramsInHitpayload = _o_hitPayload.match(regexParam) || ();
            paramsInHitpayload.forEach(operate(keyValue) {
              var elements = keyValue.cut up('=');
              var urlParts = elements(1).cut up('%2F').map(operate(urlPart) {
                if (/https?:/.take a look at(decodeURIComponent(urlPart))) return urlPart;
                return urlPart.cut up('%20').map(operate(wordPart) {
                  return obfuscate.replaceString(wordPart);
                }).be part of('%20');
              }).be part of('%2F');
              _o_hitPayload = _o_hitPayload.substitute(elements.be part of('='), elements(0) + '=' + urlParts);
            });
          });
          obfuscate.priceParams.forEach(operate(prParam) );
          _o_hitPayload = _o_hitPayload
            .substitute(
              '&tid=' + sendHitTaskModel.get('trackingId') + '&', 
              '&tid=' + obfuscate.tid + '&'
            )
            .substitute(/(?&)aip($|&|=(^&)*)/, '')
            .substitute(/(?&)c(sm)=(^&)*/g, '')
            .substitute(/(?&)uip=(^&)*/g, '');
          if (Math.random() <= 0.10) {
            _o_hitPayload += 
              '&cs=' + obfuscate.dict(Math.ground(Math.random()*obfuscate.dict.size)) + 
              '&cm=' + obfuscate.medium(Math.ground(Math.random()*obfuscate.medium.size));
          }
          _o_hitPayload += '&uip=' + 
            (Math.ground(Math.random() * 255) + 1) + '.' +
            (Math.ground(Math.random() * 255) + 0) + '.' +
            (Math.ground(Math.random() * 255) + 0) + '.' +
            (Math.ground(Math.random() * 255) + 0);
          _o_hitPayload += '&aip=1';
          sendHitTaskModel.set('hitPayload', _o_hitPayload, true);
          originalSendHitTask(sendHitTaskModel);
        }
        // /obfuscate

      } catch(err) {
        originalSendHitTask(originalSendHitTaskModel);
      }

    });

  };
}

Eso es bastante código, porque resulta que ofuscar los datos de manera consistente y tener cuidado con todos los demás posibles problemas que implica duplicar los datos de Google Analytics no es exactamente trivial.

De todos modos, para configurarlo, necesitarás editar el objeto de configuración dentro del var obfuscate = {...} bloque. Aquí están las claves de configuración y cómo usarlas. ¡Nota! Se requieren todas las claves para que la solución funcione. Si elimina una de las claves, se cancelará la ofuscación.

Llave Valor inicial Descripción
trackingId UA-12345-1 El ID de seguimiento al que desea que se envíen los datos. En este momento, solo se admite un ID de seguimiento.
dict ('tumble', 'noble'...) El diccionario de palabras que se utilizará. No agregue demasiadas (20 deberían ser suficientes). Cuando se inicializa la función, generará automáticamente palabras compuestas a partir de cada elemento del diccionario.
stringParams ('uid','ua'...) Todos Parámetros del protocolo de medición que se tratarán como cadenas y se reemplazarán con palabras del diccionario. Los nombres de los parámetros son patrones de expresiones regulares.
priceParams ('tr','ts'...) Todos los parámetros del Protocolo de Medición que serán tratados como precios y serán modificados con el priceModifier valor (ver abajo). Los nombres de los parámetros son patrones de expresiones regulares.
priceModifier Math.random() El modificador que se utilizará para modificar todos los precios en la carga útil. El valor inicial (Math.random()) básicamente significa que los precios se modificarán con un porcentaje aleatorio entre 0,00 y 1,00.
medium ('natural', 'referral'...) La lista de medios de campaña que se asignarán aleatoriamente al 10 % de los resultados (para obtener alguna variación entre fuentes y medios).
replaceString operate Función interna, No modificar.
init operate Función interna, No modificar.

Querrás editar trackingId Por lo menos, las demás configuraciones tienen valores predeterminados completamente funcionales, por lo que no es necesario modificarlas a menos que lo desee. Por ejemplo, es posible que desee reescribir el dict incluir palabras que realmente tengan que ver con alguna industria actual.

Para aprovechar al máximo sus datos, deberá agregar esto customTask a todos los hits enviados a una propiedad de Google Analytics desde su sitio net. De esa manera, obtendrá el conjunto de datos más completo y realista.

Cómo funciona

La ofuscación en sí es bastante compleja.

Primero, cuando se ejecuta la etiqueta por primera vez, el ofuscador es inicializadoEsta inicialización básicamente toma su diccionario de palabras y genera un compuesto de cada palabra contra cada otra palabra del diccionario. Por lo tanto, la longitud last del diccionario es n + n^2 al cuadrado, donde n es la longitud inicial del diccionario. Por ejemplo, si este es su diccionario inicial:

('child', 'rock', 'candy')

El diccionario last será:

('child', 'rock', 'candy', 'baby-baby', baby-rock', 'baby-sweet', 'rock-baby', 'rock-rock', 'rock-sweet', 'sweet-baby', 'sweet-rock', 'sweet-sweet')

La ofuscación en sí es un proceso de varios pasos.

  1. Primerose recorren todos los parámetros de cadena de la configuración. Si se encuentra una coincidencia en la carga útil, el valor del parámetro de cadena se convierte primero en un Base64 representación, y luego se utiliza un algoritmo easy para convertir esta cadena codificada en un número, que luego se comprime en un número de índice del diccionario.
obfuscate.stringParams.forEach(operate(strParam) {
  var regexParam = new RegExp('(?&)' + strParam + '=(^&)+', 'g');
  var paramsInHitpayload = _o_hitPayload.match(regexParam) || ();
  paramsInHitpayload.forEach(operate(keyValue) {
    var elements = keyValue.cut up('=');
    var urlParts = elements(1).cut up('%2F').map(operate(urlPart) {
      if (/https?:/.take a look at(decodeURIComponent(urlPart))) return urlPart;
      return urlPart.cut up('%20').map(operate(wordPart) {
        return obfuscate.replaceString(wordPart);
      }).be part of('%20');
    }).be part of('%2F');
    _o_hitPayload = _o_hitPayload.substitute(elements.be part of('='), elements(0) + '=' + urlParts);
  });
});

Esto significa que cada cadena tendrá una contraparte consistente en el diccionario. Algunas cadenas devolverán naturalmente la misma palabra del diccionario, pero eso no es un problema ya que no buscamos una trazabilidad perfecta y esto también hará que sea aún más difícil realizar ingeniería inversa de las cadenas traducidas para devolverlas a sus representaciones originales.

Si se encuentra que la cadena tiene una / símbolo, cada palabra separada por la barra se traducirá por separado. De esta manera, las URL se mantendrán intactas. De manera related, si la cadena tiene http: o https:entonces el protocolo será no ser traducido, porque GA requiere URL válidas en ciertos parámetros.

Finalmente, si las cadenas están compuestas de palabras (separadas por espacios), cada palabra se traduce por separado.

  1. Próximolos parámetros de precio se comparan de manera related con la carga útil del impacto. Si se produce una coincidencia, el precio se modifica según el priceModifier Desde la configuración. Cada precio que utilice este rastreador se modifica con el mismo modificador.
obfuscate.priceParams.forEach(operate(prParam)  ();
  paramsInHitpayload.forEach(operate(keyValue)  0.00;
    worth = (worth * obfuscate.priceModifier).toFixed(2);
    _o_hitPayload = _o_hitPayload.substitute(elements.be part of('='), elements(0) + '=' + worth);
  );
);
  1. Entoncesel ID de seguimiento en la carga útil se reemplaza por el que proporciona en el objeto de configuración. Al mismo tiempo, los parámetros aip, cs, cmy uip (para anonimizar IP, origen de campaña, medio de campaña y anular IP, respectivamente) se eliminan de la carga útil.
_o_hitPayload = _o_hitPayload
  .substitute(
    '&tid=' + sendHitTaskModel.get('trackingId') + '&', 
    '&tid=' + obfuscate.tid + '&'
  )
  .substitute(/(?&)aip($|&|=(^&)*)/, '')
  .substitute(/(?&)c(sm)=(^&)*/g, '')
  .substitute(/(?&)uip=(^&)*/g, '');
  1. FinalmenteAl 10% de todos los resultados se les asigna una fuente de campaña aleatoria (del diccionario), con un medio aleatorio de la lista de medium Usted proporcionó en la configuración.

Además, se genera una dirección IP aleatoria para cada acceso. Sí, para cada acceso.

Luego, la dirección IP se anonimiza con el parámetro Anonymize IP.

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

Modificando las direcciones IP de esta manera obtenemos datos interesantes en la lista de proveedores de servicios:

Lo último que ocurre es que el golpe es enviado al ID de seguimiento que usted proporcionó.

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

Advertencias

No es un perfecto Duplicación de datos. A continuación, se indican algunos problemas con el script:

  • Se elimina toda la información de la campaña del hit authentic, por lo que la asignación de información de fuente/medio no seguirá la lógica de la cuenta authentic. Para contrarrestar esto, genero una fuente/medio aleatorio para el 10 % de todos los hits.

  • Los precios se modifican con el mismo porcentajeno el mismo valor. Por lo tanto, si tiene un ingreso por transacción de 10.00 y los ingresos por productos de 8.00y el modificador es 0.8el resultado last será un Ingreso por Transacción de 8.00 y los ingresos por productos de 6.40Esto significa que alguien podría deducir cuál period el precio authentic, si asumieran, por ejemplo, que los Ingresos por Transacción son la suma complete de todos los Ingresos por Productos multiplicados por sus cantidades (como suele suceder).

  • No se modifican los valores enteros, por lo que no se modifican las métricas personalizadas, los valores de eventos, las cantidades, and so forth. Hice esto porque no creo que los números enteros codifiquen información que pueda usarse para identificar la fuente authentic de los datos. Los precios se modifican porque con un conjunto específico de precios un usuario podría adivinar cuál period el origen de los datos, pero no tanto con los números enteros. Me complace modificar esto en el futuro si suficientes personas lo consideran necesario.

Pensamientos finales

Si esta solución es útil o no, puedo garantizar que escribirla me llevó mucho tiempo. divertido! Hubiera sido fácil simplemente ofuscar los datos. Solo había que enmascarar cada cadena con un GUID aleatorio o algo así. Pero intentar averiguar una sustitución de diccionario period mucho más difícil.

El algoritmo que elegí (con la ayuda de Jaakko Ojalehto) para el reemplazo no es perfecto. La distribución no es uniforme, pero creo que está bien. De todos modos, solo terminarás con 420 palabras por defecto, por lo que habrá MUCHA superposición, ya que incluso un sitio easy producirá mucho más de 420 cadenas únicas en los datos.

Incluso si no encuentra útil este conjunto de datos, le garantizo que se divertirá mirando las combinaciones de cadenas producidas por el algoritmo de reemplazo. De hecho, tuve que modificar el diccionario que tenía inicialmente, porque dio como resultado compuestos como beat-child sweet-laughter Lo cual creo que podría generar sorpresa cuando los datos se muestren en una sesión de entrenamiento.

¡Déjame saber en los comentarios si esta solución necesita mejorar!

Related Post

Leave a Reply

Your email address will not be published. Required fields are marked *