Ah, la mine d'informations (à portée de clic) qu'est le web! Est-ce que ça ne vous fait pas rêver? Moi, si! et c'est pourquoi je vais aujourd'hui m'aventurer à faire un peu de webmining.

S'il est possible de "scraper" (i.e. "râcler", littéralement) n'importe quel site web avec un package comme rvest (qui permet de "parser" le html, i.e. de récupérer attributs et contenus des différentes sections de ce type de documents), certains packages peuvent être plus précisément adaptés à certains sites en particuler.

Je vais ainsi m'essayer au scraping du site Wikipedia, et m'armer, pour ce faire, du package dédié nommé WikipediR.

library(WikipediR)

Il existe plusieurs sites Wikipedia en fonction de la langue que l'on considère: par exemple 'fr.wikipedia.org' pour des articles en français ou 'en.wikipedia.org' pour des articles en anglais. Notons en outre l'existence d'autres projets (à divers états d'avancement) de la Wikimedia Foundation tels que

  • 'wikiquotes' (qui recense un certain nombre de citations, par sujets, auteurs ou oeuvres),
  • 'wikisource' (bibliothèque de textes libres et gratuits),
  • 'wikidata' (des données)
  • 'wiktionnaire (un dictionnaire universel)
  • 'wikimedia commons' (des ressources multimedias, images notamment)
  • 'wikiversité' (des ressources pédagogiques)
  • 'wikinews' (des actualités)
  • 'wikispecies' (des informations taxonomiques)

Toutes les fonctions de scraping du package wikipediR ont en commun, de ce fait, les arguments suivants:

  • la langue du projet,
  • le projet
  • le domaine (remplaçant à la fois langue et projet).

Information sur une page

Récupérons tout d'abord les informations relatives à une page (disons la page du "Camembert de Normandie", qui, en tant que monument du patrimoine gastronomique français, devrait parler au plus grand nombre -et surtout à ma fidèle lectrice Ginoute-!):

myinfo=page_info(language="fr",
                 project="wikipedia",
                 page="Camembert de Normandie")
print(str(myinfo))

## List of 2
##  $ batchcomplete: chr ""
##  $ query        :List of 1
##   ..$ pages:List of 1
##   .. ..$ 31542:List of 17
##   .. .. ..$ pageid              : int 31542
##   .. .. ..$ ns                  : int 0
##   .. .. ..$ title               : chr "Camembert de Normandie"
##   .. .. ..$ contentmodel        : chr "wikitext"
##   .. .. ..$ pagelanguage        : chr "fr"
##   .. .. ..$ pagelanguagehtmlcode: chr "fr"
##   .. .. ..$ pagelanguagedir     : chr "ltr"
##   .. .. ..$ touched             : chr "2017-05-28T11:03:58Z"
##   .. .. ..$ lastrevid           : int 133969395
##   .. .. ..$ length              : int 66903
##   .. .. ..$ protection          : list()
##   .. .. ..$ restrictiontypes    :List of 2
##   .. .. .. ..$ : chr "edit"
##   .. .. .. ..$ : chr "move"
##   .. .. ..$ talkid              : int 2462365
##   .. .. ..$ fullurl             : chr "https://fr.wikipedia.org/wiki/Camembert_de_Normandie"
##   .. .. ..$ editurl             : chr "https://fr.wikipedia.org/w/index.php?title=Camembert_de_Normandie&action=edit"
##   .. .. ..$ canonicalurl        : chr "https://fr.wikipedia.org/wiki/Camembert_de_Normandie"
##   .. .. ..$ displaytitle        : chr "Camembert de Normandie"
##  - attr(*, "class")= chr "pageinfo"
## NULL

On peut voir ici comment récupérer la longueur de l'article, son url complète, l'identifiant de la dernière révision, etc.

Contenu d'une page

Le contenu d'une page est par ailleurs accessible de la manière suivante:

mycontent=page_content(language="fr",
                       project="wikipedia",
                       page_name="Camembert de Normandie")
print(str(mycontent))

## List of 1
##  $ parse:List of 4
##   ..$ title : chr "Camembert de Normandie"
##   ..$ pageid: int 31542
##   ..$ revid : int 133969395
##   ..$ text  :List of 1
##   .. ..$ *: chr "<div class=\"homonymie\"><a href=\"/wiki/Aide:Homonymie\" title=\"Aide:Homonymie\"><img alt=\"Page d'aide sur l'homonymie\" src"| __truncated__
##  - attr(*, "class")= chr "pcontent"
## NULL

Voyez notamment cette partie:

mycontent$parse$text$`*`

On a ici le contenu (en html "brut") de la page wiki qui nous intéresse. Pour récupérer des éléments de ce html, on pourrait ainsi utiliser le package rvest cité plus haut (cela fera sans doute l'objet d'un prochain billet).

Mais pour le moment, intéressons-nous à des informations plus globales quant à la page wikipedia qui nous occupe ici...

Pages et catégories

Pages depuis catégories

On peut lister l'ensemble des pages de la catégorie "Fromage" depuis le site Wikipedia (France) à l'aide de la fonction pages_in_category() (je charge le package dplyr pour nettoyer un peu ma table en sortie, avant affichage...): On peut lister l'ensemble des pages de la catégorie "Fromage" depuis le site Wikipedia (France) à l'aide de la fonction pages_in_category() (je charge le package dplyr pour nettoyer un peu ma table en sortie, avant affichage...):

mypages=pages_in_category(language="fr",
                          project="wikipedia",
                          categories="Fromage",
                          clean_response=TRUE)
library(dplyr)
print(select(mypages,title))

##                                                  title
## 1                                              Fromage
## 2                                   Fromage industriel
## 3                                             Bergkäse
## 4                             Savencia Fromage & Dairy
## 5                                Brevibacterium linens
## 6                                     Cloche à fromage
## 7  Confrérie des chevaliers du taste-fromage de France
## 8                Cooper's Hill Cheese-Rolling and Wake
## 9                                    Couteau à fromage
## 10                                Fromage à pâte molle
## 11                                   Fromage de jument
## 12                                     Fromage fermier
## 13                                        Fromage fumé
## 14                                        Fromage râpé
## 15                                Fromage triple-crème
## 16                                     Fromage végétal
## 17                                           Fromarsac
## 18                                     Groupe Savencia
## 19                                 Histoire du fromage
## 20                         International Cheese Awards
## 21                                 Laiteries Triballat
## 22                                     Maître fromager
## 23                            Musée du fromage cheddar
## 24              Olomoucké syrecky (Olomoucké tvaružky)
## 25                         Paradoxe du fromage à trous
## 26                                           Pivní sýr
## 27                                   Propionibacterium
## 28                                              Sirene
## 29                                         Smažený sýr
## 30                                Substitut de fromage
## 31                           Types de pâtes de fromage
## 32                                     Tyrosémiophilie
## 33                 Catégorie:Wikipédia:ébauche fromage
## 34                    Catégorie:Fabrication du fromage
## 35                               Catégorie:Fromage AOP
## 36                     Catégorie:Fromage de lactosérum
## 37                           Catégorie:Fromage fermier
## 38                               Catégorie:Fromage IGP
## 39                        Catégorie:Fromage industriel
## 40                           Catégorie:Fromage laitier
## 41                         Catégorie:Fromager français
## 42                          Catégorie:Fromage par lait
## 43                         Catégorie:Liste de fromages
## 44                  Catégorie:Fromage par type de pâte
## 45                          Catégorie:Fromage par pays
## 46              Catégorie:Spécialité à base de fromage

Alternativement, si l'on s'intéresse par exemple au domaine Wikimedia Commons (qui pourrait vous fournir un tas de belles images de fromages), on peut procéder comme suit:

mypages=pages_in_category(domain="commons.wikimedia.org/",
                          categories="Cheese",
                          clean_response=TRUE)
print(head(select(mypages,title)))

##                        title
## 1                     Cheese
## 2  Category:Cheese by colour
## 3   Category:Cheese by shape
## 4 Category:Cheese by country
## 5 Category:Cheese-based food
## 6    Category:Organic cheese

Notez que le recours à l'argument clean_response=TRUE permet d'obtenir en sortie un résultat tabulaire relativement clair, plutôt qu'une liste...

Catégories depuis pages

Inversement, il est possible de récupérer les catégories associées à une page à l'aide de la fonction categories_in_page():

mycategories=categories_in_page(language="fr",
                              project="wikipedia",
                              pages="Camembert de Normandie",
                              clean_response=TRUE)[[1]]
print(select(mycategories$categories,title))

##                                        title
## 1  Catégorie:Appellation fromagère française
## 2              Catégorie:Camembert (fromage)
## 3                      Catégorie:Fromage AOC
## 4                      Catégorie:Fromage AOP
## 5              Catégorie:Fromage au lait cru
## 6         Catégorie:Fromage au lait de vache
## 7             Catégorie:Fromage de Normandie
## 8                  Catégorie:Fromage fermier
## 9                  Catégorie:Fromage laitier
## 10         Catégorie:Symbole de la Normandie

Liens

Le package wikipediR permet également de lister, pour une page données, les informations relatives

  • aux liens de cette page vers d'autres pages du même domaine (page_links())
  • aux liens de cette page vers des pages externes (page_external_links())
  • aux liens depuis d'autres pages du même domaine vers cette page (page_backlinks)

Liens vers d'autres pages wiki:

mylinks=page_links("fr",
                   "wikipedia",
                   page="Camembert de Normandie",
                   clean_response=TRUE)[[1]]$links
mylinks=do.call(rbind,mylinks)
print(mylinks)

##       ns  title                            
##  [1,] "0" "1791"                           
##  [2,] "0" "1890"                           
##  [3,] "0" "1916"                           
##  [4,] "0" "1982"                           
##  [5,] "0" "1996"                           
##  [6,] "0" "2013"                           
##  [7,] "0" "AOC"                            
##  [8,] "0" "Abondance (fromage)"            
##  [9,] "0" "Acide gras insaturé"            
## [10,] "0" "Acides gras saturés"            
## [11,] "0" "Affinage du fromage"            
## [12,] "0" "Alpha-tocophérol"               
## [13,] "0" "Années 1950"                    
## [14,] "0" "Années 1960"                    
## [15,] "0" "Appellation d'origine"          
## [16,] "0" "Appellation d'origine contrôlée"
## [17,] "0" "Appellation d'origine protégée" 
## [18,] "0" "Banon (fromage)"                
## [19,] "0" "Basse-Normandie"                
## [20,] "0" "Beaufort (fromage)"             
## [21,] "0" "Bernières-d'Ailly"              
## [22,] "0" "Bleu d'Auvergne"                
## [23,] "0" "Bleu de Gex"                    
## [24,] "0" "Bleu des Causses"               
## [25,] "0" "Bleu du Vercors-Sassenage"      
## [26,] "0" "Bretonne pie noir"              
## [27,] "0" "Brevibacterium linens"          
## [28,] "0" "Brie (région)"                  
## [29,] "0" "Brie de Meaux"                  
## [30,] "0" "Brie de Melun"                  
## [31,] "0" "Brillat-savarin (fromage)"      
## [32,] "0" "Brocciu"                        
## [33,] "0" "Cabécou"                        
## [34,] "0" "Cahier des charges"             
## [35,] "0" "Caillé"                         
## [36,] "0" "Calcium"                        
## [37,] "0" "Calmann-Lévy"                   
## [38,] "0" "Calvados (département)"         
## [39,] "0" "Camembert"                      
## [40,] "0" "Camembert (Orne)"               
## [41,] "0" "Camembert (fromage)"            
## [42,] "0" "Camembert au calvados"          
## [43,] "0" "Camembert fermier"              
## [44,] "0" "Cantal (fromage)"               
## [45,] "0" "Chabichou du Poitou"            
## [46,] "0" "Chaource (fromage)"             
## [47,] "0" "Charolais (fromage)"            
## [48,] "0" "Chevrotin"                      
## [49,] "0" "Cholestérol"                    
## [50,] "0" "Comté (fromage)"

La fonction page_links() (avec clean_response=TRUE) renvoie une liste qui comprend autant d'éléments que de pages indiquées en argument d'entrée (donc ici, la liste n'a qu'un seul élément correspondant à la page "camembert de Normandie"). On récupère l'élément "links".

Cet élément est lui-même une liste, comprenant autant d'éléments qu'il y a de liens en sortie. Pour mettre le tout sous forme de tableau, on passe l'ensemble de ces éléments comme arguments à la fonction rbind() (c'est à ça que sert la deuxième commande, commençant par do.call()).

(Eh oui! le webscraping c'est sympa, mais ça demande d'être capable de manipuler des listes car on manipule des informations imbriquées, et de tailles variables...)

Liens vers des pages externes

myexternallinks=page_external_links("fr",
                                    "wikipedia",
                                    page="Camembert de Normandie", 
                                    clean_response=TRUE)[[1]]$extlinks
print(data.frame(myexternallinks))

##                                                                                                             myexternallinks
## 1  http://alimentation.gouv.fr/camembert-de-normandie
## 2  http://books.google.fr/books?id=617vdru4xjkC&printsec=frontcover&hl=fr&source=gbs_ge_summary_r&cad=0#v=onepage&q&f=false
## 3  http://www.afssa.fr/TableCIQUAL/index.htm
## 4  http://www.agro-media.fr/actualite/leaop-camembert-de-normandie-en-guerre-contre-les-industriels-5791.html
## 5  http://www.cnrtl.fr/lexicographie/maturation
## 6  http://www.cra-normandie.fr/pac_lait/mono.pdf
## 7  http://www.fromage-normandie.com/fr/camembert-normandie/presentation-camembert.html
## 8  http://www.fromage-normandie.com/fr/camembert-normandie/lien-terroir.html
## 9  http://www.fromageriegillot.fr
## 10 http://www.inao.gouv.fr/public/produits/detailProduit.php?ID_PRODUIT=3270

Liens depuis d'autres pages wiki:

On peut également lister les backlinks i.e. les liens depuis d'autres pages, vers la page du Camembert de Normandie:

mybacklinks=page_backlinks("fr",
                           "wikipedia",
                           page="Camembert de Normandie",
                           clean_response=TRUE)
mybacklinks=do.call(rbind, mybacklinks)
print(head(mybacklinks))

##      pageid ns  title                                  
## [1,] "393"  "0" "Basse-Normandie"                      
## [2,] "1095" "0" "France"                               
## [3,] "1100" "0" "Liste de fromages français"           
## [4,] "2663" "0" "Reblochon"                            
## [5,] "3752" "0" "28 avril"                             
## [6,] "7674" "1" "Discussion:Liste de fromages français"

Un exemple d'application

Pour finir, voici un exemple d'application (un peu sommaire certes -j'espère que la confrérie du camembert ne me fustigera pas pour mes méthodes discutables et mes conclusions hâtives!-), mais qui vous permettra peut-être un peu de vous "projeter" dans les applications possibles du package WikipediR)...

Mettons que je m'intéresse à l'ensemble des fromages AOC, et que mon hypothèse (chauvine) de travail est que le camembert de Normandie est celui qui a la plus grande importance patrimoniale. Pour corroborer mon hypothèse, je vais m'intéresser aux longueurs des articles wikipedia associés

pages_fromages_AOC= pages_in_category("fr",
                                      "wikipedia",
                                      categories="Fromage AOC", 
                                      clean_response=TRUE)
print(data.frame(pages_fromages_AOC$title))

##     pages_fromages_AOC.title
## 1    Liste des AOC laitières
## 2        Abondance (fromage)
## 3            Banon (fromage)
## 4         Beaufort (fromage)
## 5             Berner Alpkäse
## 6            Bleu d'Auvergne
## 7                Bleu de Gex
## 8           Bleu des Causses
## 9  Bleu du Vercors-Sassenage
## 10  Bloderkäse-Sauerkäse AOC
## 11             Brie de Meaux
## 12             Brie de Melun
## 13                   Brocciu
## 14    Camembert de Normandie
## 15          Cantal (fromage)
## 16       Chabichou du Poitou
## 17        Chaource (fromage)
## 18       Charolais (fromage)
## 19                 Chevrotin
## 20           Comté (fromage)
## 21      Crottin de Chavignol
## 22                  Emmental
## 23        Époisses (fromage)
## 24 Formaggio d'alpe ticinese
## 25           Fourme d'Ambert
## 26      Fourme de Montbrison
## 27           Glarner Alpkäse
## 28            Gruyère suisse
## 29        Laguiole (fromage)
## 30         Langres (fromage)
## 31                  L'Etivaz
## 32         Livarot (fromage)
## 33       Mâconnais (fromage)
## 34       Maroilles (fromage)
## 35       Mont d'Or (fromage)
## 36         Morbier (fromage)
## 37         Munster (fromage)
## 38      Neufchâtel (fromage)
## 39               Ossau-iraty
## 40                  Pélardon
## 41                   Picodon
## 42             Pont-l'évêque
## 43     Pouligny-saint-pierre
## 44        Raclette (fromage)
## 45                 Reblochon
## 46       Rigotte de Condrieu
## 47      Rocamadour (fromage)
## 48       Roquefort (fromage)
## 49            Saint-nectaire
## 50  Sainte-maure-de-touraine

Ici j'ai récupéré l'ensemble des titres de pages correspondant à la catégorie "Fromage AOC". Je vais maintenant parcourir ces pages une à une et enregistrer leur longueur dans la variable longueur_pages

data=data.frame(title=pages_fromages_AOC$title,
                longueur_pages=rep(NA,nrow(pages_fromages_AOC)),
                stringsAsFactors=FALSE)
for (i in 1:nrow(data)){
  info=page_info("fr",
                 "wikipedia",
                 page=data$title[i])
  data$longueur_pages[i]=info$query$pages[[1]][["length"]]
}

data=arrange(data,longueur_pages) %>% mutate(ID=length(title):1)
print(head(data))

##                      title longueur_pages ID
## 1          Glarner Alpkäse           1078 50
## 2      Mâconnais (fromage)           1704 49
## 3           Berner Alpkäse           1893 48
## 4 Bloderkäse-Sauerkäse AOC           2762 47
## 5        Langres (fromage)           3386 46
## 6                Chevrotin           3686 45

Enfin, pour le plaisir des yeux, je produis un graphique pour représenter ces résultats:

library(ggplot2)
p=ggplot(data, aes(x=ID, y=longueur_pages))+
  geom_bar(aes(fill=title),stat="identity",show.legend=FALSE)+
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  scale_x_continuous(labels=data$title, breaks=data$ID)
p

Damned! Il semble qu'un (ou plusieurs) contributeur(s) de la page relative au Laguiole ait été particulièrement inspiré (mettant à mal mon hypothèse quant à la longueur de la page "Camembert de Normandie" qui devait excéder toutes les autres...)

A la réflexion, je devrais peut-être utiliser une autre métrique que la longueur de la page pour caractériser l'"importance patrimoniale" de mes fromages... Peut-être le nombre de liens depuis les pages ou vers les pages pourrait-il mieux caractériser cette importance?

(Voyez la "démarche scientifique": je vais m'acharner jusqu'à réussir à prouver mon hypothèse ;-))

data=data.frame(data,
                nb_pages_vers=NA,
                nb_pages_depuis=NA)
for (i in 1:nrow(data)){
    mybacklinks=page_backlinks("fr",
                               "wikipedia",
                               page=data$title[i],
                               limit=500,
                               clean_response=TRUE)
    data$nb_pages_depuis[i]=length(mybacklinks)
    mylinks=page_links("fr",
                       "wikipedia",
                       page=data$title[i],
                       limit=500,
                       clean_response=TRUE)[[1]]$links
    data$nb_pages_vers[i]=length(mylinks)
}
print(head(arrange(data,desc(nb_pages_vers))))

##                  title longueur_pages ID nb_pages_vers nb_pages_depuis
## 1          Ossau-iraty          41561  6           500             500
## 2      Comté (fromage)          61808  3           500             500
## 3   Laguiole (fromage)         122767  1           467             308
## 4  Abondance (fromage)          20546  9           390             315
## 5   Beaufort (fromage)          47885  4           362             369
## 6 Crottin de Chavignol          16008 13           354             262

print(head(arrange(data,desc(nb_pages_depuis))))

##                    title longueur_pages ID nb_pages_vers nb_pages_depuis
## 1            Ossau-iraty          41561  6           500             500
## 2        Comté (fromage)          61808  3           500             500
## 3    Roquefort (fromage)          45640  5           237             439
## 4     Beaufort (fromage)          47885  4           362             369
## 5              Reblochon          12973 17           230             329
## 6 Camembert de Normandie          66903  2           172             329

Une fois de plus, je ne parviens pas ici à faire valoir mon point de vue sur le camembert. Est-ce parce que j'ai tort (?!), ou bien est-ce à cause de l'existence d'une page "camembert" parallèle à la page "camembert de Normandie", ou encore est-ce lié au caractère quelque peu "ubiquiste"" du camembert (qui en ferait un fromage un peu moins "patrimonialement connecté" sur Wikipedia?). Je ne saurais le dire, en l'état, et pour tout vous dire je vais laisser cette question en suspens car ce billet est déjà très long!.

En revanche, je ne résiste pas à la tentation de vous montrer que à l'étranger au moins, le camembert est bien le fromage-ambassadeur de la France:

pages_French_cheeses=pages_in_category("en",
                                       "wikipedia",
                                       categories="French cheeses", clean_response=TRUE)
data=data.frame(title=pages_French_cheeses$title,
                longueur_pages=rep(NA,nrow(pages_French_cheeses)),
                nb_pages_vers=NA,
                nb_pages_depuis=NA,
                stringsAsFactors=FALSE)
for (i in 1:nrow(data)){
  info=page_info("en",
                 "wikipedia",
                 page=data$title[i])
  data$longueur_pages[i]=info$query$pages[[1]][["length"]]
  mybacklinks=page_backlinks("en",
                             "wikipedia",
                             page=data$title[i],
                             limit=500,
                             clean_response=TRUE)
    data$nb_pages_depuis[i]=length(mybacklinks)
    mylinks=page_links("en",
                       "wikipedia",
                       page=data$title[i],
                       limit=500,
                       clean_response=TRUE)[[1]]$links
    data$nb_pages_vers[i]=length(mylinks)
}

data=arrange(data,desc(longueur_pages)) %>% mutate(ID=length(title):1)
print(head(data))

##                    title longueur_pages nb_pages_vers nb_pages_depuis ID
## 1 List of French cheeses          10595           216             500 50
## 2                   Brie          10560           124             319 49
## 3       Bleu des Causses           9333           244             128 48
## 4              Camembert           8826           118             211 47
## 5           Comté cheese           6947           102             103 46
## 6  Époisses de Bourgogne           5846           113             112 45

What??? Oh, non!! le fromage français qui suscite le plus d'explications sur Wikipedia en anglais, ce n'est pas le camembert, mais le brie!!

Décidemment, la rédaction de ce billet m'a réservé bien des désillusions (sur le monde qui ne se conforme pas à mes hypothèses, sur mon acharnement qui se révèle vain, sur le camembert qui n'écrase pas tout sur son passage).

J'espère tout de même que ce billet permettra à certains d'entre vous d'envisager de travailler avec les connaissances collaboratives complexes, mais riches, qu'offrent Wikipedia et consorts!