Chapitre 2 Les données


Avant tout, il nous faut des données. Alors, dans le cadre d’un site web, les premières données faciles à récupérer sont les URL de pages. Pour cette étape, je me sers de screaming frog8, un crawler pour faire l’analyse SEO d’un site web. Je récupère les URL mais aussi d’autres infos comme les titres, etc.

2.2 1er nettoyage

On utilise la fonction read_excel() pour lire le fichier et on sélectionne les variables qui nous intéressent. Je garde spécifiquement les titres et les url sous forme encodée (important pour le scraping plus loin), c’est-à-dire avec un encodage des caractères spéciaux comme les accents, etc.

Petite subtilité, nous avons des URL en http et https. Après analyse, nous pouvons conserver que les adresses en https. Nous avons aussi des URL avec un même titre pour plusieurs pages pour une même contenu (elle se termine avec … | Page 4, … | Page 5, etc.) Nous les filtrons aussi.

title url
adada | Tout sur la communication et la publicité au Luxembourg | Page 8 https://www.adada.lu/page/8/
adada | Tout sur la communication et la publicité au Luxembourg | Page 7 https://www.adada.lu/page/7/
adada | Tout sur la communication et la publicité au Luxembourg | Page 6 https://www.adada.lu/page/6/
adada | Tout sur la communication et la publicité au Luxembourg | Page 5 https://www.adada.lu/page/5/
adada | Tout sur la communication et la publicité au Luxembourg | Page 4 https://www.adada.lu/page/4/

Ajoutons un ID pour chaque rang.

Nous obtenons donc 1942 titres et URL.

2.3 Homogénéisation

Le premier problème rencontré avec les titres est l’homogénéité des mentions et références. Par exemple, l’Agence VOUS est parfois citée sous VOUS, parfois agence VOUS. Pareil pour IDP (ID + P ou Ierace | Dechmann et Partners). Idem pour les clients (BGL = BGL BNP, P&T = Post) et j’en passe. Première étape, homogénéiser les noms et s’assurer que les noms composés restent ensemble lors de la tokenisation9 que nous verrons plus loin.

crawl3 <- crawl2 %>%
  mutate_if(is.character, tolower) %>% 
  mutate(title = str_replace(title, pattern = "\\|[:space:]page[:space:].{1,2}", replacement = "")) %>% 
  mutate(title = str_replace_all(title, "\\([:digit:]\\)", "")) %>% 
  mutate(title = str_trim(title)) %>% 
  mutate(title = str_replace_all(title, "id\\+p", "idp")) %>% 
  mutate(title = str_replace_all(title, "ierace \\| dechmann \\+ partners", "idp")) %>% 
  mutate(title = str_replace_all(title, "ierace dechmann + partners", "idp")) %>% 
  mutate(title = str_replace_all(title, "agence vous", "agence_vous")) %>% 
  mutate(title = str_replace_all(title, "\\(l\\’agence\\) vous", "agence_vous")) %>% 
  mutate(title = str_replace_all(title, "concept factory", "concept_factory")) %>% 
  mutate(title = str_replace_all(title, "plan k", "plan_k")) %>% 
  mutate(title = str_replace_all(title, "plank", "plan_k")) %>% 
  mutate(title = str_replace_all(title, "binsfed", "binsfeld")) %>% 
  mutate(title = str_replace_all(title, "bunker palace", "bunker_palace")) %>% 
  mutate(title = str_replace_all(title, "l’alac", "l_alac")) %>% 
  mutate(title = str_replace_all(title, "l[:space:]alac", "l_alac")) %>% 
  mutate(title = str_replace_all(title, "[:space:]alac[:space:]", "l_alac")) %>% 
  mutate(title = str_replace_all(title, "fish and chips", "fish_and_chips")) %>% 
  mutate(title = str_replace_all(title, "101 studios", "101_studios")) %>% 
  mutate(title = str_replace_all(title, "101studios", "101_studios")) %>% 
  mutate(title = str_replace_all(title, "push the brand", "push_the_brand")) %>% 
  mutate(title = str_replace_all(title, "graphisterie générale", "graphisterie_générale")) %>% 
  mutate(title = str_replace_all(title, "human made", "human_made")) %>% 
  mutate(title = str_replace_all(title, "rose de claire", "rose_de_claire")) %>% 
  mutate(title = str_replace_all(title, "skill lab", "skill_lab")) %>% 
  mutate(title = str_replace_all(title, "maison moderne", "maison_moderne")) %>% 
  mutate(title = str_replace_all(title, "joe la pompe", "joe_la_pompe")) %>% 
  mutate(title = str_replace_all(title, "lemon event", "lemon_event")) %>% 
  mutate(title = str_replace_all(title, "groupe get", "groupe_get")) %>% 
  mutate(title = str_replace_all(title, "angels events agency", "angels_events_agency")) %>% 
  mutate(title = str_replace_all(title, "mad about soul", "mad_about_soul")) %>% 
  mutate(title = str_replace_all(title, "mad about", "mad_about")) %>% 
  mutate(title = str_replace_all(title, "keep contact", "keep_contact")) %>% 
  mutate(title = str_replace_all(title, "e-connect", "e_connect")) %>% 
  mutate(title = str_replace_all(title, "mikado publicis", "mikado_publicis")) %>% 
  mutate(title = str_replace_all(title, "mikado$", "mikado_publicis")) %>% 
  mutate(title = str_replace_all(title, "mikado[:space:]", "mikado_publicis ")) %>% 
  mutate(title = str_replace_all(title, "mikado,", "mikado_publicis,")) %>% 
  mutate(title = str_replace_all(title, "studio polenta", "studio_polenta")) %>% 
  mutate(title = str_replace_all(title, "piranha et petits poissons rouges", "piranha")) %>% 
  mutate(title = str_replace_all(title, "vidale & gloesener", "vidale_gloesener")) %>% 
  mutate(title = str_replace_all(title, "vidale-gloesener", "vidale_gloesener")) %>% 
  mutate(title = str_replace_all(title, "vidale gloesener", "vidale_gloesener")) %>% 
  mutate(title = str_replace_all(title, "quattro creative", "quattro_creative")) %>% 
  mutate(title = str_replace_all(title, "quattro[:space:]", "quattro_creative ")) %>% 
  mutate(title = str_replace_all(title, "shine a light", "shine_a_light ")) %>% 
  mutate(title = str_replace_all(title, "neon marketing technology", "neon")) %>% 
  mutate(title = str_replace_all(title, "intrépide studio", "intrepide_studio")) %>% 
  mutate(title = str_replace_all(title, "ing luxembourg", "ing_luxebourg")) %>% 
  mutate(title = str_replace_all(title, "[:space:]ing", " ing_luxebourg")) %>% 
  mutate(title = str_replace_all(title, "p&t", "post")) %>% 
  mutate(title = str_replace_all(title, "post luxembourg", "post")) %>% 
  mutate(title = str_replace_all(title, "bernard-massard", "bernard massard")) %>% 
  mutate(title = str_replace_all(title, "pall center", "pall")) %>% 
  mutate(title = str_replace_all(title, "vdl", "ville de luxembourg")) %>% 
  mutate(title = str_replace_all(title, "seat luxembourg", "losch")) %>% 
  mutate(title = str_replace_all(title, "volkswagen luxembourg", "losch")) %>% 
  mutate(title = str_replace_all(title, "audi luxembourg", "losch")) %>% 
  mutate(title = str_replace_all(title, "ministère du développement durable et des infrastructures", "mddi")) %>% 
  mutate(title = str_replace_all(title, "auchan cloche d or", "auchan")) %>% 
  mutate(title = str_replace_all(title, "détecteurs de fumée", "cgdis")) %>% 
  mutate(title = str_replace_all(title, "kpmg luxembourg", "kpmg")) %>% 
  mutate(title = str_replace_all(title, "[:space:]join[:space:]", " join luxembourg ")) %>% 
  mutate(title = str_replace_all(title, "^join[:space:]", "join luxembourg ")) %>% 
  mutate(title = str_replace_all(title, "mercedes-benz", "mercedes")) %>% 
  mutate(title = str_replace_all(title, "[:space:]bil[:space:]", " bil luxembourg ")) %>% 
  mutate(title = str_replace_all(title, "[:space:]leo[:space:]", " leo luxembourg ")) %>% 
  mutate(title = str_replace_all(title, "spuerkees", "bcee"))

Ouch! C’était long mais voilà une bonne chose de faite.

2.4 Vous avez dit VOUS ?

J’adore le nom de notre agence: VOUS. Mais vous voyez le second problème?
Comment identifier les titres avec le terme vous ne correspondant pas à l’agence? Et bien là aussi pas de solution miracle, il me faudra lire chaque titre et classifier le sens manuellement… Voyons ce que cela donne sur la table 2.1.

Table 2.1: Titre de page après correction
title
médecins du monde luxembourg oppose deux réalités dans sa campagne d été signée agence_vous
agence_vous joue avec les codes de la série got pour la chambre des métiers
foyer lance l assurance modulable mozaïk avec agence_vous
join luxembourg déploie sa première campagne presse avec agence_vous.
l agence_vous devient membre officiel de confrad

Cela semble parfait.

2.5 Scraping du contenu

Avant de pouvoir faire une analyse, nous avons besoin de récupérer le contenu de chaque page. Comme pour l’extraction des données CIM10, nous allons utiliser le package rvset et la fonction safely du package purrr pour envelopper (wrapping) la fonction de lecture de l’HTML. Cette procédure permet de capturer d’éventuelles erreurs sans stopper le processus de lecture. En effet, si l’algorithme tente de lire le contenu d’une page qui n’existe plus (lien mort donnant une erreur 40411), le processus s’arrêterait alors qu’ici nous aurons une notification. La fonction nous donnera une liste en sortie.

Voilà. Après quelques minutes de patiente, l’entièreté du contenu est récupérée et pèse 5,2 Mb.

2.6 Fonctions d’extraction

Pour récupérer les catégories wordpress12 définies existantes, nous allons créer une fonction spécifique. Nous ferons de même pour la date de publication, les lovers, les commentaires et la date des commentaires.

Nous pouvons procéder à l’extraction de notre liste de contenu (content). Nous pourrions avoir un arrêt de la fonction en cas de contenu vide. Pour éviter cela, nous envelopperons les fonctions sous la fonction safely également que nous exécuterons sur le résultat de l’extraction de données vu précédemment.

2.8 Préparation des 5 listes

Avant de rassembler le tout dans une seule table, nous allons nettoyer chaque liste, ajouter un index (rowid) et transformer la liste en table via la fonction enframe avec la bonne classe (chr, POSIXct, etc).

#> Observations: 1,942
#> Variables: 2
#> $ rowid    <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 1…
#> $ category <chr> "category-marques", "category-crea", "category-crea", "categ…
#> Observations: 1,942
#> Variables: 2
#> $ rowid    <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 1…
#> $ pub_date <dttm> 2019-06-13 06:00:00, 2018-04-18 11:00:00, 2019-08-14 16:00:…
#> Observations: 3,198
#> Variables: 2
#> $ rowid  <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,…
#> $ lovers <chr> NA, "will de Luxembourg", NA, NA, NA, NA, NA, NA, NA, "David",…

Nous avons ici 3,198 observations. La raison est que nous avons plusieurs contributeurs (lovers) par pages.

#> Observations: 3,198
#> Variables: 2
#> $ rowid    <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 1…
#> $ comments <chr> NA, "Belle réalisation, c’est frais, c’est jeune, c’est symp…
#> Observations: 3,198
#> Variables: 2
#> $ rowid         <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, …
#> $ comments_date <dttm> NA, 2018-04-23 17:57:00, NA, NA, NA, NA, NA, NA, NA, 2…

Sur cette dernière table, nous transformons les valeurs en POSIXct.

2.9 Joindre en 1 seule table

Nous allons à présent rassembler l’ensemble des tables en une seule. Pour rappel, nous avons 5 listes de 2 longueurs (1,942 et 3,198). J’utilise 2 fonctions: bind_cols pour les listes de même longueur et left_join pour joindre les URL de page, les titres (table nommée crawl), la date de publication, les catégories ensemble.

Vérifions.

#> Observations: 3,198
#> Variables: 8
#> $ rowid         <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, …
#> $ pub_date      <dttm> 2019-06-13 06:00:00, 2018-04-18 11:00:00, 2019-08-14 1…
#> $ category      <chr> "category-marques", "category-crea", "category-crea", "…
#> $ title         <chr> "kpmg plage 2019: kpmg persiste et signe avec takaneo",…
#> $ lovers        <chr> NA, "will de Luxembourg", NA, NA, NA, NA, NA, NA, NA, "…
#> $ comments      <chr> NA, "Belle réalisation, c’est frais, c’est jeune, c’est…
#> $ comments_date <dttm> NA, 2018-04-23 17:57:00, NA, NA, NA, NA, NA, NA, NA, 2…
#> $ url           <chr> "https://www.adada.lu/2019/06/kpmg-plage-2019-kpmg-pers…

2.10 Enrichissement des données

Nous arrivons à la dernière étape de cette première partie et pas la moindre. Nous allons ajouter une colonne pour les agences mentionnées dans le titre de l’article, une colonne pour les clients et une colonne pour les personnes référencées. Ici pas de recette miracle, j’ai répertorié l’ensemble manuellement malgré une tentative infructueuse d’identification d’entités avec l’API NLP13 de Google (Nous reviendrons plus tard sur l’API).

J’ai donc enregistré 3 vecteurs pour les agences, le noms et prénoms et les clients 2.2.

Table 2.2: 3 vecteurs manuels : agences, people et clients
agence
advantage
pointcomm
a3com
apart
takaneo
noosphere
binsfeld
vanksen
piranha
accentaigu
addedvalue
comed
mikado_publicis
bunker_palace
moskito
concept_factory
bizart
mad_about_soul
mad_about
dotcom
people
alain cunisse
amandine verplaetse
aurélien luiselli
caroline bourdeaux
céline mazzilli
claire ramos
claude nesser
fabien rodrigues
ghislain giraudet
cédric evrard
flavio da costa
frank kaiser
hélène cherry
helmut mertens
jacky beck
adrien schuster
jean huot
alexandra sora
jennifer müller
julien renault
client
bofferding
enovos
goedert
bernard massard
ceetrus
cloche d or
iprn luxembourg
raiffeisen
cap futur du lycée technique d esch/alzette
domaines vinsmoselle
luxembourg space agency
fischer
foyer
pall center
rosport
home & living expo 2019
lcto
leo luxembourg
cobolux
bgl bnp paribas

2.11 Labellisation

Pour labéliser un titre avec le nom de l’agence, nous allons identifier la présence de celle-ci avec la fonction str_which. Si une mention est trouvée, la fonction mutate enregistre comme valeur dans une nouvelle colonne res la référence de l’agence sous forme d’index (l’index correspondant à la position de l’agence trouvée dans notre vecteur agency). Si plusieurs agences sont trouvées dans le titre, nous aurons une liste d’index pour ce titre particulier. C’est pour cette raison que la fonction unnest est présente dans notre manipulation. Nous créons ensuite une nouvelle colonne avec comme sélecteur du nom notre colonne res. Cela peut sembler complexe, mais avec un peu de pratique cela va vous paraître évident. Nous répéterons cette procédure pour les personnes et clients.

2.12 La table finalisée

Nous avons une table avec 11 variables. Voici un extrait de chacune d’elle.

#> Observations: 3,839
#> Variables: 11
#> $ rowid         <int> 1, 2, 3, 4, 5, 6, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1…
#> $ pub_date      <dttm> 2019-06-13 06:00:00, 2018-04-18 11:00:00, 2019-08-14 1…
#> $ category      <chr> "category-marques", "category-crea", "category-crea", "…
#> $ title         <chr> "kpmg plage 2019: kpmg persiste et signe avec takaneo",…
#> $ lovers        <chr> NA, "will de Luxembourg", NA, NA, NA, NA, NA, NA, NA, N…
#> $ comments      <chr> NA, "Belle réalisation, c’est frais, c’est jeune, c’est…
#> $ comments_date <dttm> NA, 2018-04-23 17:57:00, NA, NA, NA, NA, NA, NA, NA, N…
#> $ url           <chr> "https://www.adada.lu/2019/06/kpmg-plage-2019-kpmg-pers…
#> $ agency        <chr> "takaneo", "betocee", "agence_vous", "binsfeld", "plan_…
#> $ client        <chr> "kpmg", "rosport", "médecins du monde", "enovos", "bell…
#> $ people        <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
Table 2.3: Extrait 1 - Id, date de publication, catégorie
rowid pub_date category
827 2010-04-01 20:00:00 category-communiques
858 2018-05-17 07:00:00 category-crea
1635 2015-03-31 21:00:00 category-crea
477 2014-01-26 13:00:00 category-crea
1801 2011-05-12 10:00:00 category-crea
1877 NA category-crea
218 2017-11-23 07:00:00 category-communiques
516 2017-03-28 16:00:00 category-crea
759 2017-02-01 09:00:00 category-crea
1229 2012-03-23 12:00:00 category-crea
Table 2.3: Extrait 2 - id, Titre de page
rowid title
1436 luxembourg marketing & communication awards 2012 : présentation du nouveau règlement
1003 vanksen remporte 3 prix aux marketing & communication awards 2013
769 wili signe l identité corporate du « innovation hub » de dudelange
751 5 à sec fait une nouvelle fois appel à ribs
1264 a3com signe la communication de la 1ere édition du prix de musique quattropole
1607 bofferding au cœur du tour de france avec nvision
861 nouvelle adresse, nouveau départ pour atypical et kreutz & friends
1687 le mddi et la sécurité routière lancent une grande campagne de sensibilisation pour les pneus hiver avec mikado_publicis
1444 immotop.lu confie sa communication à l agence antidote
1611 emile weber promet des vacances surprenantes avec agence_vous
Table 2.3: Extrait 3 - id, date de commentaire, agence, client, people
rowid comments_date agency client people
323 2019-03-01 15:46:00 NA mudam NA
1687 2011-10-19 20:42:00 mikado_publicis sécurité routière NA
43 NA NA mudam NA
915 NA binsfeld battin NA
1846 2012-06-10 22:45:00 comed luxtram NA
53 2015-09-27 13:13:00 human_made NA NA
1165 2012-03-19 08:05:00 NA NA NA
1238 2012-02-16 07:46:00 NA axa NA
972 2011-08-03 22:13:00 concept_factory total luxembourg NA
1942 2011-06-22 13:41:00 h2a luxsecurity NA
Table 2.3: id, lovers, commentaire
rowid lovers comments
819 georgette y fait chaud ici non?
1265 NA NA
477 Jeff Déjà v(o)u(s) http://www.youtube.com/watch?v=316AzLYfAzw
480 NA NA
309 Lucas « On connaît assez bien le marché de la pub ici. » Bravo  o /
639 Jean Ben oui, la campagne Rosport à le goût de déja écrit et la campagne autopolis ressemble à une voiture d’occasion. Dans ce genre de concours il faut avoir un jury qui a une bonne culture publicitaire.
1079 castrol 3 prix.. et le même soir… Quel talent ! mieux que Cannes Lions.. vous êtes plus forts que Burnett, Y&R. Je m’incline et vous prie d’accepter mes excuses. Mais je suis en droit de m’interroger ; Comment allez-vous gérer ce succès ? gardez bien à l’esprit que dans notre profession, le succès commercial est bien le seul qui vaille. Les succès d’estime ne conduisent jamais leur bénéficiaire qu’aux épinards sans beurre. SIgné la Conne !
1224 NA NA
59 NA NA
1229 Priscillia Beau visuel

Bravo! Vous êtes arrivé à la fin de cette première partie.