Nous avons l’habitude de manipuler des données contenues dans des fichiers ou système bien structuré et adapté à cet effet. Oui heureusement, nous avons des packages qui ont été bien conçus spécialement pour faire la partie “désagréable” du boulot. Maintenant imaginons que l’on veuille extraire un tableau contenu sur une page web comme celui de la bourse de Casablanca:
Malheureusement ou heureusement pour nous, s’il existe des packages comme le fameux quantmod très utiliser par les quants pour télécharger notamment avec la fonction getSymbols(), les cours boursiers depuis la plateforme de Yahoofinance ou Googlefinance…etc, il n’en existe pas pour la bourse de Casablanca et nous allons à travers cet tutoriel concevoir une fonction qui pourra :
- – Naviguer sur le site en cochant les différentes options que l’utilisateur aura entré
- – Extraire le tableau des données affiché sur le site et le préparer pour son utilisation avec R (comme on peut voir l’image au début de cet article présente les cours de la société ATTIJARIWAFABANK)
Sommaire
- 1 Etape I : RSelenium, package pour la navigation web
- 2 Etape II: Interaction avec le site web
- 3 Etape III: Parsage de la page web avec XML et formatage des données en data.frame
- 4 Etape IV: Conversion des données et formatage en série temporelle xts :
- 5 Etape V: Réutilisation du code et la fonction getSymbolsCasaBourse()
Selenium, est un projet visant à automatiser la navigation web et en l’occurence sa version R, nommée RSelenium permet de naviguer via des commandes R. Bien que l’intérêt de ce package va bien au delà de la simple navigation web, nous allons l’utiliser pour communiquer avec notre site cible.
Ainsi, pour fonctionner, nous avons besoin d’un navigateur web firefox, Google Chrome…etc. Mais malheureusement ces derniers ne fonctionnent souvent pas. Mais nous avons mieux, nous allons opter pour une navigation fantome ( dans le sens où tout se passera en background et nous ne verrons rien apparaitre pendant la navigation autrement ce serait embêtant). Pour ce faire, nous allons utiliser le navigateur PhantomJS, on peut le télécharger ici. Après téléchargement, il suffit de décompresser le dossier dans un endroit facilement accessible. Ensuite on aura besoin du chemin d’accès( C:/Users/danam/Documents/phantomjs-2.1.1-windows/bin/phantomjs.exe) à l’exécutable phantomjs.exe logé dans le dossier bin .
1 2 3 4 5 6 7 |
require(RSelenium) e <- list(phantomjs.binary.path = 'C:/Users/danam/Documents/phantomjs-2.1.1-windows/bin/phantomjs.exe') remDr <- remoteDriver(browserName = 'phantomjs', extraCapabilities = e) remDr$open(silent = TRUE) url <- 'http://www.casablanca-bourse.co.ma/bourseweb/Negociation-Historique.aspx?Cat=24&IdLink=302' remDr$navigate(url) remDr$screenshot(display = TRUE) |
En supposant, que l’on a déjà installé RSelenium, on le charge dans l’environnement de travail (ligne 1) et pour démarrer la navigation à distance il est nécessaire d’établir le branchement avec phantomjs(ligne 3). C’est sur la base de ce branchement à travers l’objet remDr que nous allons pouvoir accéder à une plateforme web à distance. La fonction navigate(), va nous permettre d’accéder au site web dont le lien est fourni en argument et la dernière fonction screenshot(), permet d’avoir un aperçu de la page web :
Si l’on obtient une image similaire, c’est dire que tout se passe bien !
Etape II: Interaction avec le site web
Pour interagir avec le site web notamment entrer le nom de la société dont nous voulons obtenir l’historique des cours, la période et valider le formulaire nous devons à chaque fois référencer l’élément de la page web. Explication : Conformement aux standards (DOM), dans une page HTML par exemple, on peut interagir avec ses composants à travers un nom, un id ou encore à travers un selecteur de style CSS. Voyons plutôt :
Nous allons séléctionner le nom de la société dont nous voulons obtenir l’historique dans la liste déroulante (voir l’image ci-dessus) pour celà nous allons devons fournir l’id de l’objet liste déroulante à la fonction findElement(). Pour obtenir l’id il suffit de cliquer droit sur la liste déroulante et ensuite de choisir inspecter l’élément, une fénêtre s’ouvrira à côté avec le code source de l’élément dont voici l’extrait:
1 2 3 4 5 6 |
<select name="HistoriqueNegociation1$HistValeur1$DDValeur" id="HistoriqueNegociation1_HistValeur1_DDValeur" class="input" style="width:256px;"> <option selected="selected" value="">Choisissez une valeur</option> <option value="9000 ">DOUJA PROM ADDOHA</option> <option value="11200 ">ALLIANCES</option> <option value="11700 ">AFRIC INDUSTRIES SA</option> </select> |
On voit le nom et l’id, nous prendrons l’id qui vaut “HistoriqueNegociation1_HistValeur1_DDValeur” qu’il faut passer dans la fonction findElement() comme ceci:
1 2 3 4 |
comboElm <- remDr$findElement(using = 'id', value='HistoriqueNegociation1_HistValeur1_DDValeur') comboElm$sendKeysToElement(list('ALLI')) remDr$screenshot(display = TRUE) # obtenir un aperçu |
Une fois l’élément liste déroulante retrouver, il reste à lui envoyer le nom de la société “ALLIANCE” (abrégé “ALLI” par ce que c’est une liste déroulante les premières lettres suffisent pour faire la sélection mais il faudrait s’assurer qu’aucun autre nom ne commence pas par “ALLI” sinon il faudrait écrire le nom en toute lettre) à travers la fonction sendKeysToElement(). En aperçu nous avons :
Maintenant que l’on a compris le principe, on peut réitérer le processus aux autres éléments. Ici, au lieu spécifier directement la période de l’historique dont nous avons besoin(option 1 en rouge) nous allons exploiter la limitation des données à un historique de 3 ans seulement en notre faveur. Nous allons donc choisir de télécharger les donnes sur 3 ans(voir illustration pas trop top ci-dessous) :
Nous allons d’abord chercher les id respectifs du bouton radio “Par mensualité”, de la liste déroulante et du bouton “Valider”. Ensuite nous cliquer sur le bouton radio et sélectionner “3 ans” dans la liste déroulante à droite et en fin valider en envoyant la commande de la touche “Entrer” au bouton “valider”( un peu comme quand l’on fini de remplir un formulaire on tape “Entrer” pour valider le tout) :
1 2 3 4 5 6 7 8 9 10 |
radioElm <- remDr$findElement(using = 'id', value = 'HistoriqueNegociation1_HistValeur1_RBSearchDate') radioElm$click() comoElmdate <- remDr$findElement(using = 'id' , value = 'HistoriqueNegociation1_HistValeur1_DDuree') comoElmdate$sendKeysToElement(list('3 ans')) submitbn <- remDr$findElement(using = 'id', value = 'HistoriqueNegociation1_HistValeur1_Image1') submitbn$sendKeysToElement(list(key = 'enter')) remDr$screenshot(display = TRUE) |
Si tout se passe bien nous allons voir afficher ce qui suit (il faudrait avoir une bonne connectivité où laisser s’écouler quelques fractions de secondes pour lancer la dernière commande sinon on aura une image de page en chargement et par aiIleurs, il va falloir zoomer pour bien voir comme ci-dessous) :
Etape III: Parsage de la page web avec XML et formatage des données en data.frame
Maintenant, Nous allons, d’abord obtenir le code source de la page que nous avons précédemment obtenue. Nous allons ensuite le parser avec la fonction htmlParse() vers un type de document objet R, hybride(car héritant à la fois des objets "HTMLInternalDocument" "XMLInternalDocument" "XMLAbstractDocument" ) compatible au format XML et dont on peut scruter les nœuds avec la syntaxe XPath(c’est un langage (non XML) pour localiser une portion d’un document XML).
1 2 3 4 |
page <- remDr$getPageSource() require(XML) doc <-htmlParse(unlist(page)) node <- getNodeSet(doc, path = '//*[@id="arial11bleu"]') |
Nous allons d’abord retrouver la syntaxe XPath(chemin de localisation) du tableau en cliquant droit sur ce dernier et ensuite sur son code source (voir image ci-dessous) on copie le XPath (encerclé en rouge).C’est avec ce chemin de localisation de notre tableau que nous allons extraire le table de la page entière. Heureusement ce chemin ne change pas( à moins ce que le site soit complètement refait). Ce chemin est ensuite passé à la fonction getNodeSet() qui extrait un nœud d’une page XML(D’un point de vue formel, un document XML est un arbre, articulant différents types de nœuds (texte, éléments, attributs, commentaires…)).
Nous avons réduit la page à son nœud où se trouve le tableau, maintenant, nous allons extraire notre tableau vers un objet data.frame. Mais d’abord voyons le structure de l’objet node :
1 2 3 4 5 6 |
> str(node) List of 3 $ :Classes 'XMLInternalElementNode', 'XMLInternalNode', 'XMLAbstractNode' <externalptr> $ :Classes 'XMLInternalElementNode', 'XMLInternalNode', 'XMLAbstractNode' <externalptr> $ :Classes 'XMLInternalElementNode', 'XMLInternalNode', 'XMLAbstractNode' <externalptr> - attr(*, "class")= chr "XMLNodeSet" |
On voit bien que c’est une liste et notre tableau est contenu dans le dernier élément du tableau 3(en effet en affichant un par un chaque élément du tableau vous verrez que les 2 premiers sont les entêtes qui précèdent le tableau). L’extraction du tableau vers le data.frame passe par la fonction readHTMLTable():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
> raw_data.frame <- readHTMLTable(node[[3]], stringsAsFactor = F) > raw_data.frame V1 V2 V3 V4 V5 V6 V7 V8 V9 V10 V11 V12 1 2 SÃ<83>©ance DÃ<83>©signation Premier Cours Dernier Cours + haut du jours + bas du jour 3 4 16/11/2016 ALLIANCES 88,42 87,50 89,00 86,50 5 15/11/2016 ALLIANCES 90,00 89,10 90,00 88,00 6 14/11/2016 ALLIANCES 90,20 90,00 92,00 90,00 7 11/11/2016 ALLIANCES 90,24 90,00 90,50 89,00 8 10/11/2016 ALLIANCES 90,00 90,27 91,99 89,11 9 09/11/2016 ALLIANCES 90,10 90,50 91,88 90,10 10 08/11/2016 ALLIANCES 90,00 90,01 92,00 90,00 11 07/11/2016 ALLIANCES 89,00 90,00 90,40 88,10 |
Erreka enfin quelque chose que nous de lisible !
Etape IV: Conversion des données et formatage en série temporelle xts :
Comme on peut le voir notre data.frame est “sale”, les valeurs ne sont pas reconnaissables sous R, le format de la date non plus et les noms des colonnes ne sont pas au bon endroit …etc.
Maintenant sans trop de commentaire on fait ceci :
1 2 3 4 5 6 7 8 9 10 |
> raw_dataframe <- readHTMLTable(node[[3]], stringsAsFactor = F) > raw_dataframe <- raw_dataframe[ - (1:3),] > head(raw_dataframe) V1 V2 V3 V4 V5 V6 V7 V8 V9 V10 V11 V12 V13 V14 V15 V16 V17 4 16/11/2016 ALLIANCES 88,42 87,50 89,00 86,50 21Â 709 1Â 106Â 781Â 200,00 5 15/11/2016 ALLIANCES 90,00 89,10 90,00 88,00 10Â 578 1Â 127Â 019Â 484,80 6 14/11/2016 ALLIANCES 90,20 90,00 92,00 90,00 20Â 058 1Â 138Â 403Â 520,00 7 11/11/2016 ALLIANCES 90,24 90,00 90,50 89,00 7Â 044 1Â 138Â 403Â 520,00 8 10/11/2016 ALLIANCES 90,00 90,27 91,99 89,11 29Â 915 1Â 141Â 818Â 730,60 9 09/11/2016 ALLIANCES 90,10 90,50 91,88 90,10 30Â 088 1Â 144Â 727Â 984,00 |
Nous constatons qu’il y a des colonnes intercalaires inutiles et par chance se sont des colonnes de rang impair. Ce pattern va nous permettre de les exclure et d’exclure aussi la colonne 4 (pas utile non plus) :
1 2 3 4 5 6 7 8 9 |
> raw_dataframe <- raw_dataframe[ - c(4, seq(1, 17, by = 2))] > head(raw_dataframe) V2 V6 V8 V10 V12 V14 V16 4 16/11/2016 88,42 87,50 89,00 86,50 21Â 709 1Â 106Â 781Â 200,00 5 15/11/2016 90,00 89,10 90,00 88,00 10Â 578 1Â 127Â 019Â 484,80 6 14/11/2016 90,20 90,00 92,00 90,00 20Â 058 1Â 138Â 403Â 520,00 7 11/11/2016 90,24 90,00 90,50 89,00 7Â 044 1Â 138Â 403Â 520,00 8 10/11/2016 90,00 90,27 91,99 89,11 29Â 915 1Â 141Â 818Â 730,60 9 09/11/2016 90,10 90,50 91,88 90,10 30Â 088 1Â 144Â 727Â 984,00 |
Là c’est bon maintenant ça ressemble de mieux en mieux à l’idéal. Ce qu’il reste à faire c’est de formater toutes les colonnes en type numeric() excepté la première colonne que nous allons convertir en type Date() :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
> raw_dataframe[-1] <- apply(raw_dataframe[-1], 2, function(x) { + as.numeric(gsub('Â ', '', gsub(',', '.',x))) + }) > head(raw_dataframe) V2 V6 V8 V10 V12 V14 V16 4 16/11/2016 88.42 87.50 89.00 86.50 21709 1106781200 5 15/11/2016 90.00 89.10 90.00 88.00 10578 1127019485 6 14/11/2016 90.20 90.00 92.00 90.00 20058 1138403520 7 11/11/2016 90.24 90.00 90.50 89.00 7044 1138403520 8 10/11/2016 90.00 90.27 91.99 89.11 29915 1141818731 9 09/11/2016 90.10 90.50 91.88 90.10 30088 1144727984 > require(xts) > table <- `names<-`(xts(raw_dataframe[-1], as.Date(raw_dataframe[[1]], + format = '%d/%m/%Y')),c('Open','High','Low','Close','Volume','Capitalisation')) > head(table,20) Open High Low Close Volume Capitalisation 2013-11-19 520.0 520.0 524.0 515.0 9823 6365515520 2013-11-20 520.0 520.0 525.0 508.0 67247 6365515520 2013-11-21 525.0 525.0 528.0 518.0 9150 6426722400 2013-11-22 522.0 522.0 528.0 520.0 14816 6389998272 2013-11-25 527.5 534.9 534.9 527.5 300 6547912022 2013-11-26 529.9 530.0 530.0 522.0 4032 6487929280 2013-11-27 530.0 530.0 530.0 515.0 9340 6487929280 2013-11-28 528.5 529.0 530.0 522.0 3444 6475687904 2013-11-29 528.0 529.0 529.0 521.0 5238 6475687904 2013-12-02 534.9 534.9 534.9 534.9 30 6547912022 2013-12-03 530.9 520.0 535.0 515.0 9349 6365515520 2013-12-04 528.0 514.0 528.0 506.1 28421 6292067264 2013-12-05 514.0 520.0 523.9 514.0 5084 6365515520 2013-12-06 520.0 520.0 525.0 517.0 13747 6365515520 2013-12-09 524.9 524.0 524.9 515.3 11104 6414481024 2013-12-10 520.0 523.7 523.7 516.2 3965 6410808611 2013-12-11 516.5 519.0 523.0 516.5 11160 6353274144 2013-12-12 519.9 518.9 519.9 516.6 5075 6352050006 2013-12-13 515.7 514.0 517.9 500.0 43239 6292067264 2013-12-16 517.0 517.0 517.8 510.0 4434 6328791392 > class(table) [1] "xts" "zoo" |
C’est fini, c’était l’assaut final, vos données sont prêtes à l’emploi notamment pour l’analyse de séries temporelles ….
Maintenant me diriez-vous: Pourquoi n’a-t-on pas plutôt téléchargé manuellement le fichier excel ? la dernière étape va répondre à cette question
Etape V: Réutilisation du code et la fonction getSymbolsCasaBourse()
L’intérêt de ce travail fastidieux que nous venons d’abattre c’est que nous pouvons tout mettre dans une fonction et l’utiliser à chaque fois que nous voulons télécharger un cours et ceci pour n’importe quelle société et pour n’importe quelle période (enfin qui ne va pas au delà de 3 ans) . Ainsi, après quelque réajustement pour que notre fonction puisse faire des extractions précise pour une période fournie en argument, nous pourrions faire télécharger les cours de ATTIJARIWAFA BANK du début de 2016 à aujourd’hui comme ceci :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
> # Cours de ATTIJARIWAFABANK > AWB <- getSymbolsCasaBourse("ATTI", start = "2016-01-01", end = "2016-11-16") > head(AWB) Open High Low Close Volume Capitalisation 2016-01-04 331.00 329.00 334.50 329.00 7276 66960457354 2016-01-05 327.10 330.00 330.00 326.50 11320 67163984580 2016-01-06 330.00 330.00 330.00 326.25 38700 67163984580 2016-01-07 326.70 330.00 330.00 326.70 4840 67163984580 2016-01-08 326.45 327.00 328.00 326.45 26606 66553402902 2016-01-12 327.05 327.15 327.15 327.00 24168 66583931986 > # Cours de ALLIANCES > ALLI <- getSymbolsCasaBourse("ALLI", start = "2016-01-01", end = "2016-11-16") > head(ALLI) Open High Low Close Volume Capitalisation 2016-01-04 42.68 41.85 43.00 38.61 9685 529357637 2016-01-05 42.40 43.80 43.80 41.00 16101 554023046 2016-01-06 45.00 44.70 46.00 43.00 26369 565407082 2016-01-07 43.10 42.77 44.80 42.50 2824 540994651 2016-01-08 43.00 43.10 44.75 41.50 37401 545168797 2016-01-12 44.27 46.20 47.39 43.00 126556 584380474 |
Et comme nous voulons finir en beauté on va visualiser graphiquement ces données :
1 2 3 4 |
require(quantmod) chartSeries(AWB, TA = c(addVo(), addBBands())) chartSeries(ALLI, TA = c(addVo(), addBBands()), theme = chartTheme('white')) |
Pour la société ATTIJARIWAFABANK ça donne :
Pour la société ALLIANCES ça donne :