Site icon Anakeyn

Nettoyage du Spam dans Google Analytics avec R – Partie II

Cet article est la suite de l’article Nettoyage du Spam dans Google Analytics avec R – Partie I

Le nettoyage à proprement parlé commence ici !

Code Source R :

Vous pouvez copier/coller les codes sources suivants pour les tester dans votre propre fichier de script R.

Vous pouvez aussi télécharger gratuitement les sources et les fichiers nécessaires dans notre boutique : https://www.anakeyn.com/boutique/produit/script-r-nettoyage-spam-dans-google-analytics/

Nettoyage des langues suspectes.

Nous allons vérifier que les langues sont bien sous la forme « langue-pays » : xxx-xxx, par exemple : fr-FR, fr-BE, es … pour cela on utilise une expression régulière ici : « ^[a-zA-Z]{2,3}([-/][a-zA-Z]{2,3})?$ ».

Nous préparons aussi les données pour affichage.

##########################################################################
#Nettoyage des langues suspectes.
##########################################################################
indexGoodlang <- grep(pattern = "^[a-zA-Z]{2,3}([-/][a-zA-Z]{2,3})?$", gaPVAllYears$language)
gaPVAllYearsCleanLanguage <- gaPVAllYears[indexGoodlang,]
nrow(gaPVAllYearsCleanLanguage) #on supprime environ 6000 lignes 
#nombre de ligne 76733


#verifs
head(gaPVAllYearsCleanLanguage, n=20) #verif
summary(gaPVAllYearsCleanLanguage)
dim(gaPVAllYearsCleanLanguage)
class(gaPVAllYearsCleanLanguage$date)


#creation de la dataframe daily_data par jour
dfDatePV <- as.data.frame(gaPVAllYearsCleanLanguage$date)
colnames(dfDatePV)[1] <- "date"                   #change le nom de la colonne.
daily_data <- dfDatePV  %>%                       #daily_data à partir de dfDatePV
  group_by(date) %>%                              #groupement par date
  mutate(Pageviews = n()) %>%                     #total des pageviews = nombre d'observations / date
  as.data.frame() %>%                             #sur d'avoir une data.frame
  unique() %>%                                    #ligne unique par jour.
  mutate(cnt_ma30 = ma(Pageviews, order=30)) %>%  #variable moyenne mobile (moving average 30 jours)
  mutate(year = format(date,"%Y"))             #creation de la variable year

Il reste 76733 observations, on en a supprimées environ 6000 !

Visualisation après nettoyAGE des Langues Suspectes

#Graphique pages vues par jour
ggplot(daily_data , aes(x=date, y=Pageviews, color=year)) + 
  geom_line() +
  xlab("Année") +
  ylab("Nbre pages vues / jour") +
  labs(title = "L'anomalie de fin 2016 a disparu ",
       subtitle = "suite au nettoyage des langues.",
       caption = "Nombre de pages vues par jour depuis 2011 \n Données nettoyées variable langue",
       color = "Année") 
#sauvegarde du dernier ggplot
ggsave(filename = "PV-s2011-Clean-Lang.jpeg",  dpi="print") #sauvegarde du dernier ggplot.

Pages Vues suite au nettoyage des langues suspectes.

Nettoyage des Ghostames

Il s’agit de sites qui ont placé notre code de suivi Google Analytics sur leur propres pages … On va faire en sorte de ne garder que les sites légitimes. Par ailleurs s’il existe des sous domaines que l’on ne souhaite pas garder il faudra aussi les traiter.

##########################################################################
#nettoyage des hostnames non légitimes (Ghostnames)
##########################################################################
#Pour faciliter la lecture on va créer un vecteur de pattern des sites 
#légitimes : nos sites et les sites de cache comme par exemple webcache.googleusercontent.com
#ou web.archive.org
#on garde ceux qui nous intéressent. 
# remarque adpatez le nom de votre site en remplacment de networking-morbihan.
patternGoodHostname <- c("networking-morbihan\\.com", "translate\\.googleusercontent\\.com", 
                         "webcache\\.googleusercontent\\.com", 
                         "networking-morbihan\\.com\\.googleweblight\\.com", 
                         "web\\.archive\\.org")

indexGoodHostname <- grep(pattern = paste(patternGoodHostname, collapse="|"), gaPVAllYearsCleanLanguage$hostname)
gaPVAllYearsCleanHost <- gaPVAllYearsCleanLanguage[indexGoodHostname,]

#Verifs.
nrow(gaPVAllYearsCleanHost) #76170 #~560 lignes supprimmées 
summary(gaPVAllYearsCleanHost$hostname)  #verif


#on vire un mauvais sous domaine qui restait
patternBadHostname = "loc\\.networking-morbihan\\.com"
indexBadHostname <- -grep(pattern = patternBadHostname, gaPVAllYearsCleanHost$hostname)
gaPVAllYearsCleanHost <- gaPVAllYearsCleanHost[indexBadHostname,]
nrow(gaPVAllYearsCleanHost) #76159 #nombre de lignes  nettoyées 11 



#creation de la dataframe daily_data par jour
dfDatePV <- as.data.frame(gaPVAllYearsCleanHost$date)
colnames(dfDatePV)[1] <- "date"                   #change le nom de la colonne.
daily_data <- dfDatePV  %>%                       #daily_data à partir de dfDatePV
  group_by(date) %>%                              #groupement par date
  mutate(Pageviews = n()) %>%                     #total des pageviews = nombre d'observations / date
  as.data.frame() %>%                             #sur d'avoir une data.frame
  unique() %>%                                    #ligne unique par jour.
  mutate(cnt_ma30 = ma(Pageviews, order=30)) %>%  #variable moyenne mobile (moving average 30 jours)
  mutate(year = format(date,"%Y"))                #creation de la variable year

Il reste maintenant 76159 observations après cette action. Environ 570 suppressions.

Visualisation après nettoyage des GHOSTNAMES

#Graphique pages vues par jour
ggplot(daily_data , aes(x=date, y=Pageviews, color=year)) + 
  geom_line() +
  xlab("Année") +
  ylab("Nbre pages vues / jour") +
  labs(title = "L'évolution du nombre de pages vues ne se voit pas à l'oeil nu ",
       subtitle = "suite au nettoyage des Hostnames par rapport au nettoyage des langues.",
       caption = "Nbre pages vues par jour depuis 2011 - Données net. variable hostname",
       color = "Année")
ggsave(filename = "PV-s2011-Clean-Host.jpeg",  dpi="print") #sauvegarde du dernier ggplot.

Pages vues suite au nettoyage des Hostnames

Nettoyage des browsers suspects

Avant de créer et de personnaliser la pattern de nettoyage des browsers, il est nécessaire de vérifier le contenu de la variable « browser ».

##########################################################################
#nettoyage des browsers suspects - peut contenir des robots crawlers
##########################################################################
#voyons ce qu'il y a dedans 
unique(gaPVAllYearsCleanHost$browser)
plyr::count(as.factor(gaPVAllYearsCleanHost$browser))
#on vire les "curiosités et les bots (cela peut varier selon vos le contenu que vous trouver dans browser)
patternBadBrowser <- c("not set","Google\\.com", "en-us", 
                         "GOOG", "PagePeeker\\.com", 
                         "bot")
indexBadBrowser <- -grep(pattern = paste(patternBadBrowser, collapse="|"), gaPVAllYearsCleanHost$browser)
gaPVAllYearsCleanBrowser <- gaPVAllYearsCleanHost[indexBadBrowser,]

#Verifs
head(gaPVAllYearsCleanBrowser, n=20) #verif
nrow(gaPVAllYearsCleanBrowser) #76126 lignes nettoyées environ 30
unique(gaPVAllYearsCleanBrowser$browser)
plyr::count(as.factor(gaPVAllYearsCleanBrowser$browser))

#creation de la dataframe daily_data par jour
dfDatePV <- as.data.frame(gaPVAllYearsCleanBrowser$date)
colnames(dfDatePV)[1] <- "date"                   #change le nom de la colonne.
daily_data <- dfDatePV  %>%                       #daily_data à partir de dfDatePV
  group_by(date) %>%                              #groupement par date
  mutate(Pageviews = n()) %>%                     #total des pageviews = nombre d'observations / date
  as.data.frame() %>%                             #sur d'avoir une data.frame
  unique() %>%                                    #ligne unique par jour.
  mutate(cnt_ma30 = ma(Pageviews, order=30)) %>%  #variable moyenne mobile (moving average 30 jours)
  mutate(year = format(date,"%Y"))                #creation de la variable year

il reste 76126 observations. Ici uniquement une trentaine de repérés.

Visualisation dES pages vues suite au nettoyage des browsers suspects

#Graphique pages vues par jour
ggplot(daily_data , aes(x=date, y=Pageviews, color=year)) + 
  geom_line() +
  xlab("Année") +
  ylab("Nbre pages vues / jour") +
  labs(title = "L'évolution du nombre de pages vues ne se voit pas à l'oeil nu ",
       subtitle = "suite au nettoyage des browsers suspects par rapport aux nettoyages précédents.",
       caption = "Nbre pages vues par jour depuis 2011 - Données net. variable browser",
       color = "Année")
#sauvegarde du dernier ggplot.
ggsave(filename = "PV-s2011-Clean-Browser.jpeg",  dpi="print") #sauvegarde du dernier ggplot.

Pages vues suite au nettoyage des browsers suspects.

Nettoyage des Crawlers Spammers et autres sources de trafic non désirées dans source.

Pour effectuer cette opération vous aurez besoin du fichier « blacklist-source-sites.csv » qui regroupe de nombreux spammeurs. vous pouvez le récupérer sur notre Github à l’adresse
https://github.com/Anakeyn/blacksites/archive/master.zip

Le fichier zip comprend aussi un fichier en.xlsx mais celui-ci sert pour le nettoyage « à la main » via les segments. Dézipper et recopier le fichier « blacklist-source-sites.csv » dans le répertoire courant de votre fichier R

Pour des raisons de mémoire on ne peut pas construire une pattern qui comprend tous les sites à exclure. C’est pourquoi on fait une boucle pour faire une recherche par paquets de 500. Ajustez le paramètre « step » selon vos besoins.

##########################################################################
#nettoyage des Crawlers Spammers et autres sources de trafic non désirées 
#dans source
##########################################################################
gaPVAllYearsCleanSource <- gaPVAllYearsCleanBrowser
#la liste des sites et mots clés non désirés est dans un fichier que 
#nous avons créé.
patternsBadSource <- read.csv("blacklist-source-sites.csv", header=TRUE)
head(patternsBadSource)
#pour des raisons de mémoire on est obligé de faire une boucle avec des paquets de pattern.

blacklistSourceSitesPacks <- vector() 
step <- 500
steps <- seq(1, length(patternsBadSource$blacksites) , by=step)
#steps <- 1 #pour faire des tests
j <- 1
for (i in steps)  {
  if ( i+step < length(patternsBadSource$blacksites))   {
    iMax <-i+step
    
  } 
  else {
    iMax <- length(patternsBadSource$blacksites)
  }
  
  patternBadSourcePack <- paste(paste(patternsBadSource$blacksites[i:iMax], collapse="|"))
  write.table(patternBadSourcePack, file = stri_replace_all_fixed(paste("blacklist-sites-",j,".txt"), " ", ""), row.names = FALSE, col.names=FALSE)
  str(patternBadSourcePack)
  cat("Pattern", patternBadSourcePack, "\n")
  blacklistSourceSitesPacks[j] <-  patternBadSourcePack #pour sauvegarde
  cat("j:", j, "\n")
  #grep ne déecte pas tout, notamment les nombres en notation scientifique planqués 
  #dans les chaines de caractères  :  préférer stri_detect_regex
  #indexBadSource <- -grep(pattern = as.character(patternBadSourcePack), format(gaPVAllYearsCleanSource$source, scientific=FALSE))
  #cat("trouvés:", indexBadSource, "\n")
  indexBadSource <- -which(stri_detect_regex(format(gaPVAllYearsCleanSource$source, scientific=FALSE), as.character(patternBadSourcePack)))
   cat("trouvés avec stri_detect :", indexBadSource, "\n")
    if (length(indexBadSource) > 0) gaPVAllYearsCleanSource <- gaPVAllYearsCleanSource[indexBadSource,]
  j <- j+1
}
#verif de ce que l'on a :
head(plyr::count(as.factor((gaPVAllYearsCleanSource$source)))) 
#Verifs
nrow(gaPVAllYearsCleanSource) #74275 lignes nettoyées environ 1850


#creation de la dataframe daily_data par jour
dfDatePV <- as.data.frame(gaPVAllYearsCleanSource$date)
colnames(dfDatePV)[1] <- "date"                   #change le nom de la colonne.
daily_data <- dfDatePV  %>%                       #daily_data à partir de dfDatePV
  group_by(date) %>%                              #groupement par date
  mutate(Pageviews = n()) %>%                     #total des pageviews = nombre d'observations / date
  as.data.frame() %>%                             #sur d'avoir une data.frame
  unique() %>%                                    #ligne unique par jour.
  mutate(cnt_ma30 = ma(Pageviews, order=30)) %>%  #variable moyenne mobile (moving average 30 jours)
  mutate(year = format(date,"%Y"))                #creation de la variable year


Nous avons maintenant 74275 observations. Environ 1850 ont été écartées.

ViSUALISATION SUITE au nettoyage des crawlers spammers

Pages vues suite au nettoyage de la source

Nettoyage des fausses pages référentes dans fullReferrer

Il s’agit ici de fausses pages référentes mais sur des sites légitimes. Nous avons créé un liste dans un fichier « blacklist-fullRefferer-Page.csv » que vous pouvez aussi récupérer dans le fichier .zip précédent.

##########################################################################
#nettoyage des fausses pages référentes dans fullReferrer
##########################################################################
gaPVAllYearsCleanFullReferrer <- gaPVAllYearsCleanSource
patternsBadFullReferrer <- read.csv("blacklist-fullRefferer-Page.csv", header=TRUE)
indexBadFullReferrer <- -grep(pattern = paste(patternsBadFullReferrer$Blackpages, collapse="|"), gaPVAllYearsCleanFullReferrer$fullReferrer)
gaPVAllYearsCleanFullReferrer <- gaPVAllYearsCleanFullReferrer[indexBadFullReferrer,]
#Verifs
nrow(gaPVAllYearsCleanFullReferrer) #73829 lignes nettoyées environ 450  

#creation de la dataframe daily_data par jour
dfDatePV <- as.data.frame(gaPVAllYearsCleanFullReferrer$date)
colnames(dfDatePV)[1] <- "date"                   #change le nom de la colonne.
daily_data <- dfDatePV  %>%                       #daily_data à partir de dfDatePV
  group_by(date) %>%                              #groupement par date
  mutate(Pageviews = n()) %>%                     #total des pageviews = nombre d'observations / date
  as.data.frame() %>%                             #sur d'avoir une data.frame
  unique() %>%                                    #ligne unique par jour.
  mutate(cnt_ma30 = ma(Pageviews, order=30)) %>%  #variable moyenne mobile (moving average 30 jours)
  mutate(year = format(date,"%Y"))                #creation de la variable year

Nous avons maintenant 73829 observations. Environ 450 ont été trouvées avec cette méthode.

VisUAlisation DES PAGES VUES SUITE AU NETTOYAGE DES PAGES REFERENTES SUSPECTES

#Graphique pages vues par jour
ggplot(daily_data , aes(x=date, y=Pageviews, color=year)) + 
  geom_line() +
  xlab("Année") +
  ylab("Nbre pages vues / jour") +
  labs(title = "L'évolution du nombre de pages vues ne se voit pas à l'oeil nu ",
       subtitle = "suite au nettoyage des pages référentes suspectes par rapport aux nettoyages précédents.",
       caption = "Nbre pages vues par jour depuis 2011 - Données net. variable fullReferrer",
       color = "Année")
#sauvegarde du dernier ggplot.
ggsave(filename = "PV-s2011-Clean-FullReferrer.jpeg",  dpi="print")  

Pages vues après nettoyage des pages référentes suspectes.

Nettoyage des pages d’administration

Il s’agit ici des pages d’administration du site qui n’ont pas vocation à être comptés. Attention ces pages dépendent de votre CMS (Content Management System). Pour nous il s’agit de WordPress.

##########################################################################
#nettoyage des pages d'administration dans pagePath
##########################################################################
gaPVAllYearsCleanPagePath <-  gaPVAllYearsCleanFullReferrer

patternBadPagePath <- c("/wp-login\\.php", "/wp-admin/", "/cron/", "/?p=\\d")
indexBadPagePath  <- -grep(pattern = paste(patternBadPagePath, collapse="|"), gaPVAllYearsCleanPagePath$pagePath)
gaPVAllYearsCleanPagePath  <- gaPVAllYearsCleanPagePath [indexBadPagePath,]

#verifs
nrow(gaPVAllYearsCleanPagePath) #73301 environ 530 lignes de nettoyées.
summary(gaPVAllYearsCleanPagePath$pagePath)  #verif


#creation de la dataframe daily_data par jour
dfDatePV <- as.data.frame(gaPVAllYearsCleanPagePath$date)
colnames(dfDatePV)[1] <- "date"                   #change le nom de la colonne.
daily_data <- dfDatePV  %>%                       #daily_data à partir de dfDatePV
  group_by(date) %>%                              #groupement par date
  mutate(Pageviews = n()) %>%                     #total des pageviews = nombre d'observations / date
  as.data.frame() %>%                             #sur d'avoir une data.frame
  unique() %>%                                    #ligne unique par jour.
  mutate(cnt_ma30 = ma(Pageviews, order=30)) %>%  #variable moyenne mobile (moving average 30 jours)
  mutate(year = format(date,"%Y"))                #creation de la variable year

Il reste 73301 observations. Cette partie en a repéré environ 530 lignes.

Visualisation suite à la suppression des pages d’administration.

#Graphique pages vues par jour
ggplot(daily_data , aes(x=date, y=Pageviews, color=year)) + 
  geom_line() +
  xlab("Année") +
  ylab("Nbre pages vues / jour") +
  labs(title = "L'évolution du nombre de pages vues ne se voit pas à l'oeil nu ",
       subtitle = "suite au nettoyage des pages d'administration par rapport aux nettoyages précédents.",
       caption = "Nbre pages vues par jour depuis 2011 - Données net. variable pagePath",
       color = "Année")
ggsave(filename = "PV-s2011-Clean-PagePath.jpeg",  dpi="print") #sauvegarde du dernier ggplot. 

Pages vues suite à la suppression des pages d’administration.

Nettoyage des pages dont l’entrée sur le site s’est faite via l’administration, variable landingPagePath

Comme précédemment, la liste des pages dépend de votre CMS. pour nous il s’agit de WordPress.

##########################################################################
#nettoyage des pages dont l'entrée sur le site s'est faite 
#via l'administration, variable landingPagePath
##########################################################################

gaPVAllYearsCleanLandingPagePath <-  gaPVAllYearsCleanPagePath
patternBadLandingPagePath <- c("/wp-login\\.php", "/wp-admin/", "/cron/", "/?p=\\d")
indexBadLandingPagePath  <- -grep(pattern = paste(patternBadLandingPagePath, collapse="|"), gaPVAllYearsCleanLandingPagePath$landingPagePath)
gaPVAllYearsCleanLandingPagePath  <- gaPVAllYearsCleanLandingPagePath [indexBadLandingPagePath,]

#verifs
nrow(gaPVAllYearsCleanLandingPagePath) #72822 environ 500 lignes de nettoyées.
summary(gaPVAllYearsCleanLandingPagePath$landingPagePath)  #verif


#creation de la dataframe daily_data par jour
dfDatePV <- as.data.frame(gaPVAllYearsCleanLandingPagePath$date)
colnames(dfDatePV)[1] <- "date"                   #change le nom de la colonne.
daily_data <- dfDatePV  %>%                       #daily_data à partir de dfDatePV
  group_by(date) %>%                              #groupement par date
  mutate(Pageviews = n()) %>%                     #total des pageviews = nombre d'observations / date
  as.data.frame() %>%                             #sur d'avoir une data.frame
  unique() %>%                                    #ligne unique par jour.
  mutate(cnt_ma30 = ma(Pageviews, order=30)) %>%  #variable moyenne mobile (moving average 30 jours)
  mutate(year = format(date,"%Y"))                #creation de la variable year

Il reste 72822 observations. Environ 500 lignes ont été trouvées durant cette dernière étape.

Visualisation des pages vues nettoyées.

#Graphique pages vues par jour
ggplot(daily_data , aes(x=date, y=Pageviews, color=year)) + 
  geom_line() +
  xlab("Année") +
  ylab("Nbre pages vues / jour") +
  labs(title = "Le maximum de pages vues par jour est maintenant autour de 200 (vs 300) ",
       subtitle = "suite au nettoyage des pages d'administration référentes \n par rapport aux nettoyages précédents.",
       caption = "Nbre pages vues par jour depuis 2011 - Données net. variable landingPagePath",
       color = "Année")
ggsave(filename = "PV-s2011-Clean-LandingPagePath.jpeg",  dpi="print") #sauvegarde du dernier ggplot. 

Pages vues Nettoyées

Au départ nous avions 82559 observation et après nettoyage 72821, ce qui fait près de 10000 et environ 15% du total ce qui n’est pas négligeable !!!

Comparatifs des années sur le jeu de données nettoyée

Pour finir nous allons comparer les différents trafics selon les années sur le même graphique. Nous en profiterons aussi pour sauvegarder nos données dans un fichier dfPageViews.csv que nous pourrons réutiliser par la suite pour d’autres investigations.

##########################################################################
# Jeu de données nettoyé
##########################################################################
#install.packages("lubridate")  #si vous ne l'avez pas
library (lubridate) #pour yday
#nom de sauvegarde plus facile à retenir :
dfPageViews <- gaPVAllYearsCleanLandingPagePath 
#Sauvegarde  pour SAS si besoin. Bon c'est le même que le précédent ...
#sauvegarde en csv avec ;
write.csv2(dfPageViews, file = "dfPageViews.csv",  row.names=FALSE) 

#visualisation comparatif des années 
dfDatePV <- as.data.frame(dfPageViews$date)
colnames(dfDatePV)[1] <- "date"
daily_data <- dfDatePV  %>%                       #daily_data à partir de dfDatePV
  group_by(date) %>%                              #groupement par date
  mutate(Pageviews = n()) %>%                     #total des pageviews = nombre d'observations / date
  as.data.frame() %>%                             #sur d'avoir une data.frame
  unique() %>%                                    #ligne unique par jour.
  mutate(cnt_ma30 = ma(Pageviews, order=30)) %>%  #variable moyenne mobile (moving average 30 jours)
  mutate(year = format(date,"%Y")) %>%               #creation de la variable year
  mutate(dayOfYear = yday(date))                  #creation de la variable dayOfYear

#comparatifs 2011 - 2018
ggplot() +
  geom_line(data = daily_data, aes(x = dayOfYear, y = cnt_ma30, col=year))  +
  xlab("Numéro de Jour dans l'année") +
  ylab("Nbre pages vues / jour en moyenne mobile ") +
  labs(title = "Les données présentent une saisonnalité : ",
       subtitle = "Le trafic baisse en général en été.",
       caption = "Comparatif Nbre pages vues par jour  par an moy. mob. 30 jours \n Données nettoyées",
       color = "Année")
#sauvegarde du dernier ggplot.
ggsave(filename = "PV-Comparatif-mm30.jpeg",  dpi="print") 

Pages vues par an en moyenne mobile sur 30 jours

Merci pour votre attention. Nous prévoyons de reprendre cet article en Python. Par ailleurs les données nettoyées du site serons utilisées dans des articles futurs pour quelques exemples d’investigations.

Vous pouvez retrouver le code source en entier ainsi que les fichiers nécessaires sur notre Github à l’adresse https://github.com/Anakeyn/CleanSpamGAwR

N’hésitez pas à laisser vos avis, conseils etc en commentaires,

A Bientôt,

Pierre

Quitter la version mobile