{"word_count": 2205, "character_count": 14691}
En préambule, je remercie chaleureusement Sylvain Roquebert et Théo Bourgeron qui m'ont aidé dans l'écriture du code présenté dans ce billet.Dans un de mes précédents billets, j'étais allé récupérer "à la main" des données sur le site Rapgenius : à partir d'une recherche de l'expression "je fais du rap", j'avais copié-collé tous les résultats. C'était très fastidieux, et je me suis dit qu'il serait bien d'essayer d'automatiser cette tâche. Après plusieurs jours de sueur (c'est-à-dire beaucoup plus de temps que si j'avais fait un relevé à la main...), j'ai réussi à mettre en place un modèle qui me semble applicable pour toute recherche de paroles sur Rapgenius. En l'état, le code que je vais présenter ici permet de récupérer dans un fichier CSV (donc lisible par Excel) les vingt mots précédant et les vingt mots suivant un mot ou une expression clé (20 étant un chiffre arbitraire, on peut choisir combien on veut), dans tous les morceaux de rap recensés sur rapgenius. Dans le tableau final, on a également le titre du morceau et le nom de l'artiste qui rappe. J'envisage d'essayer d'introduire d'autres données dans ce tableau, comme l'année du morceau (mais je crois qu'il n'est pas dans les données Rapgenius). Par ailleurs, il est aussi possible, après modifications du code, d'aller interroger les annotations des morceaux. Enfin, il me semble important de noter tout de suite que Rapgenius est en fait le site Genius, et que les résultats des requêtes peuvent être issus d'autres parties du site (je n'ai pas encore regarder si la structure des données de Genius permet de faire cette distinction), ce qui n'est pas un problème ici car nous allons nous intéresser aux occurrences de l'expression "rap conscient".Ce tutoriel est écrit dans le but d'être utilisable par quelqu'un qui ne connait pas du tout R. En effet, je pense que faire des recensions d'expression à partir de Rapgenius peut faire l'objet de deux utilisations :Une utilisation qualitative : l'intérêt ici est de ne pas faire des copier-coller pendant des heures, et le but est d'avoir rapidement un tableau final avec tous les usages de l'expression qui nous intéresse.Une utilisation quantitative : une fois le tableau constitué, et si celui-ci est d'une taille assez grande, il est possible d'utiliser les outils de l'analyse textuelle (pour, par exemple, faire des études de cooccurrences de termes).Dans ce billet nous allons d'abord voir comment arriver à ce qui permettrait la première utilisation, les outils quantitatifs feront l'objet d'un prochain billet.Normalement, pour que le script marche, il suffit de faire copier-coller de ce qui est en dessous, en changeant juste quelques choses :Son identifiant Rapgenius (token)Sa requêteEventuellement certains paramètres (par exemple le nombre de résultats)Pour commencer, R fonctionne avec des paquets spécifiques pour utiliser certaines fonctions : il faut donc commencer par télécharger les paquets dont on va se servir ici. Pour installer un paquet la commande est : install.packages("nom du paquet"). Puis il faut l'exécuter avec la commande : library("nom du paquet")library(xml2) library(rvest) library(utils) library(rjson) library(bitops) library(RCurl)S'il est relativement facile d'utiliser les données de Genius, c'est que le site a mis en place depuis quelques mois une API (Application programming interface), c'est-à-dire une interface de programmation permettant à ceux qui le demandent d'avoir accès aux métadonnées du site. Ainsi, la première chose à faire est de s'inscrire à cette API, ici : https://genius.com/api-clients/new Une fois le formulaire rempli (nom d'utilisateur, mot de passe, rien de plus classique), il faut récupérer un code d'authentification qui s'appelle "token" et qui permet de faire des recherches dans leur base de données. Une fois ce token récupéré, on peut faire notre recherche, et récupérer les résultats dans R. Pour cela, il faut récupérer l'URL de la recherche, comme ceci :token&#60;-"XXXXXXXX" #le token d'autorisation de l'API rapgenius (mettre le sien : c'est très long, genre 50 caractères) urlr&#60;-"https://api.genius.com/search?q="   #l'URL qui permet de faire des recherches sur rapgeniusnresult&#60;- "&#38;per_page=117" # le nombre de résultats qu'on veut (par défaut, cela affiche 20 résultats), là j'ai mis le max de ce qui m'intéresse (après 117, il n'y a plus d'occurrences de "rap conscient")request&#60;-URLencode(("rap+conscient"))  #la recherche qu'on veut mener ici "rap conscient"R a stocké dans quatre objets les éléments de l'URL de recherche qui était dans notre navigateur. Il faut maintenant les coller ensemble en un seul objet, comme ceci :urlf&#60;-paste(urlr,request,nresult,"&#38;access_token=",token,sep="") # on met ensemble pour avoir le vrai URL&#160;La fonction "paste" est très utile ici : elle permet de coller des chaines de caractères dans l'ordre qu'on lui indique. Ici, R crée l'objet urlf qui est le composé des objets urlr/request/nresult/&#38;access_token=/token.Le paramètre sep="" signifie qu'en chaque objet il ne met aucun caractère.Puis on récupère ce qui est contenu dans la page correspondante à cet URL :json&#60;- getURL(urlf) # on récupère la requête sous format jsonresul&#60;- fromJSON(json) # on la décodeL'objet "resul" est un objet R de type list dont il faut maintenant extraire les informations pour les mettre dans une base de données.On crée alors une dataframe vide :TEXT&#60;-data.frame() #on crée une base de données vide pour le moment&#160;Il s'agit alors de remplir cette dataframe. Il faut récupérer dans la liste "resul" les informations que nous voulons : titre du morceau, artiste et lyrics. Cependant, les lyrics ne sont pas directement disponible dans la liste "resul" que nous avons constitué : ce qui l'est, c'est l'URL du morceau où il y a écrit "rap conscient". Il faut donc récupérer cet URL, puis récupérer le code source HTML de cette page internet, trouver la balise dans laquelle il y a les paroles et les récupérer.Ces opérations doivent être menées pour tous les résultats de notre requête (dans notre cas 117 fois) : au lieu de taper 117 fois des variantes d'un même code, on va se servir d'une boucle, c'est-à-dire qu'on va faire un certain nombre d'opérations identiques pour chaque résultat de notre requête. R n'est pas très performant dans l'exécution des boucles, cette opération peut être longue, voire faire planter le logiciel. Dans ce cas, il vaut mieux faire la boucle en plusieurs fois (par tranche de vingt par exemple). La boucle commence par for qui indique de combien à combien va varier le "i" qu'on va mettre en paramètre dans notre expression. Puis la boucle fait les opérations indiquées entre les deux accolades {...}for (i in 1:length(resul$response$hits)) { #length : le nombre de résultats qu'on veut (pour le faire en plusieurs fois, c'est ici qu'il faut changer : for (i in 1:20) par exemple) url&#60;-(resul$response$hits[[i]]$result$url) #pour chaque résultat, on récupère l'URL du morceauhtml&#60;- read_html(url, encoding="UTF-8") #on le lit en UTF-8lyricshtml&#60;-html_node(html, "div.lyrics") #on recherche le bon "noeud" celui où il y a les paroles  txt&#60;-html_text(lyricshtml) # on transforme ce qu'il y a dans le noeud pour que ce soitTEXT[i,1]&#60;- resul$response$hits[[i]]$result$primary_artist$name #le nom de l'artiste dans la première TEXT[i,2]&#60;- resul$response$hits[[i]]$result$title #le titre du morceau dans la deuxièmeTEXT[i,3]&#60;-txt #le texte dans la troisième colonneTEXT[i,3]&#60;-iconv(TEXT[i,3], from="UTF-8", to="ASCII//TRANSLIT") #on enlève les accents (sinon il y a un problème d'encodage : au lieu d'accent, on a des caractères bizarres)TEXT[i,2]&#60;-iconv(TEXT[i,2], from="UTF-8", to="ASCII//TRANSLIT") # idem TEXT[i,1]&#60;-iconv(TEXT[i,1], from="UTF-8", to="ASCII//TRANSLIT") # idem }#On nomme les colonnes de notre dataframe colnames(TEXT)&#60;- c("Artiste", "Titre", "Texte")Bon, on a maintenant dans notre base de données les textes entiers des morceaux contenant "rap conscient". Il s'agit alors de ne garder dans ces textes que la partie qui nous intéresse. Ici, j'ai décidé de garder les quarante mots autour de "rap conscient". Pour pouvoir faire cela, il faut d'abord localiser l'expression "rap conscient" dans chaque texte (ce qui nécessite donc encore l'usage d'une boucle). Une fonction - nommée ici "trouver_positions" a donc été créée pour cela :#Fonction pour extraire les mots autour de la requête trouver_positions = function(mots,mot1,mot2) {   position=matrix(ncol=1,nrow=0)   occurrences=grep(mot1,mots[[1]]) # trouver les positions des occurrences de "rap" dans le textefor(i in 1:length(occurrences)){ # pour chaque occurrence de rap if(grepl(mot2,mots[[1]][occurrences[i]+1])){ # on regarde si le mot suivant est "conscient" (mot2)       position=rbind(occurrences[i],position) # si oui, on ajoute la position de rap dans la liste  }   }   return(position) }Il faut alors appliquer cette fonction à tous nos textes, qui sont ici présents dans la troisième colonne de notre dataframe (noté TEXT[i,3] : la i-ème ligne et 3ème colonne de la dataframe "TEXT"). Une fois que nous avons la position de "rap conscient", on sélectionne les vingt mots avant et après cette position et on les colle, avec la même fonction "paste" que tout à l'heure, et on met cela dans notre tableau!for (i in 1:length(resul$response$hits)) { #idem : changer la longueur si ça galère (faire en plusieurs fois)     mots=tolower(TEXT[i,3]) #en minuscule pour pas avoir de pb     mots=strsplit(mots," ") # dès qu'il y a un espace c'est un nouveau mot     mot1="rap"     mot2="conscient"     position=trouver_positions(mots,mot1,mot2) #donne la position de l'expression "rap conscient" (si elle existe)          if (is.na(position[1])) { #si la position n'existe pas (NA), c'est à dire qu'il n'y a pas l'expression "rap conscient"       TEXT [i,3]&#60;- NA  #alors on met NA dans la case (en vrai il faudrait la virer à la fin)     } else { #sinon              if (position&#62;20) { #sinon rap conscient n'est pas dans les vingts premiers mots         avant&#60;- paste(mots[[1]][(position[1]-20):(position[1])], collapse=" ") #20 mots avant       }   else { #Si c'est dans les vingt premiers mots on lui demande de partir du premier mot du morceau         avant&#60;- paste(mots[[1]][(position[1]-position[1]+1):(position[1])], collapse=" ")       }        apres&#60;- paste(mots[[1]][(position[1]+1):(position[1]+20)], collapse=" ") #20 mots après (on peut modifier 20 et 20 pour avoir plus ou moins large)    Phrase&#60;- paste(avant, apres, collapse= " ") #on colle ce qu'il y a avant et après       TEXT[i,3]&#60;- Phrase # et on le met dans la base de données     }          TEXT[i,3]&#60;-gsub("[\r\n]", " / ", TEXT[i,3])  # on remplace les sauts de ligne par des / (dépend de comment on le lit mais sur excell c'est plus lisible) (changer en fonction)   }Quelques précisions concernant cette boucle. De façon brute, elle renvoyait souvent des messages d'erreurs, pour deux raisons, ce qui nécessite une précision du code :La position "n-20" n'existe pas (c'est-à-dire que "rap conscient" est dans les 20 premiers mots du texte) : dans ce cas, on dit à R de commencer au premier mot du texte"rap conscient" n'existe pas. Plusieurs cas sont possibles : soit l'expression n'est pas exactement la même (ici on a par exemple : "rap (saut de ligne) conscient" et "R.A.P. conscient"), soit l'expression n'existe tout simplement pas dans le texte et Rapgenius a sorti ce résultat pour plusieurs raisons (soit il est présent dans les annotations, soit "rap" et "conscient" sont présents à des endroits différents du texte). Dans ce cas, on dit à R de renvoyer "NA" (No Answer) dans la case, et il faudrait enlever cette ligne de la base de données.Par ailleurs, pour des questions de lisibilité, la dernière ligne du code précédent visait à remplacer les "" - le codage R pour dire "saut de ligne" - par des /, qui permettent de lire à la fin tout le texte dans Excel sur une seule ligne, tout en gardant l'information du retour à la ligne.Enfin, il n'y a plus qu'à sauvegarder notre base de données en CSV, afin de pouvoir la lire sur Excel.setwd("C:/Users/User-pc/Documents/Sociologie/Quanti/Rapgenius") #le répertoire de sauvegarde write.csv2(TEXT, file="rapconscient.csv")  #on l'écrit ici en CSV (peut-être qu'il y a mieux)#FIN!!!!!!!!!!!On détermine donc d'abord un répertoire de sauvegarde, avant de l'écrire sous forme d'un tableau lisible en dehors de R.Jetons quand même un oeil au tableau final. Voilà les cinq premières lignes de ce qu'on a réussi à obtenir :ArtisteTitreTexteYoussouphaEntourageun mauvais messager/ j'viens cracher mes etats d'ames, et mes cas d'conscience/ y'a que les connards qui vont croire que c'est du rap conscient/ pourtant, j'n'ai pas confiance, meme en mes propres dires/ entre rires de deprime et larmes de crocodiles / pas de gros deal, queNekfeuPoint d'interrogationrien / n'attends pas que la francaise des jeux te seche les yeux/ j'ai des poumons d'or sous mon torse, ouais/ pourquoi faire du rap conscient quand tout l'monde dort ? // [couplet 2 : alpha wann &#38; nekfeu] / allez tous balayer devant chez vous/ vous etes dans laRohffTestamentquand un frere est en train de precher/ rien que ca jure hakdin islam, bourre ou perche/ "generation sacrifiee" a devierge ton rap conscient, engage/ j'en ai perdu mon sang pendant que les autres se cachaient/ metisse par le mal et le bien, je renieYoussouphaMannschafta qui j'fais grave confiance/ a c'niveau-la, j'ai pas de morale ni de cas d'conscience/ nique l'auto-tune, nique le trap, nique le rap conscient/ il faut que tes gars s'concentrent, il faut que tes garces contemplent/ cash comptant, boug toujours independant, mais regarde c'qu'on tente/ queDisizRap Geniusnoirs comme claude gueant, dans ton lit pas pour un job/ j'suis trop sear, j'suis get busy, j'suis un in-rapuptible/ fuck le rap conscient, le rap bling-bling, vive le rap utile !/ black, j'ai la plume, j'suis trop sindanu kasongo / hahaha, j'vais tous vous buterBoobaRTCcendrier /j'ai like des gros tarpes sur instagram j'me suis fait griller/ des meufs shab les calendriers, tous avons braque ou deale/ le rap conscient, qu'il nique sa mere pendant que son pere mange du sanglier/ jamais en gardav' sans nier, j'commettais des crimes pourCe code est encore sûrement améliorable, mais en l'état il fonctionne, j'essaierai d'en poster des versions plus abouties!N'hésitez pas à me donner vos suggestions, remarques etc. Et si quelque chose n'est pas compréhensible, ou si le code ne marche pas quand vous essayer de l'utiliser, vous pouvez me contacter (corentin.roquebert@gmail.com).