layout: true <style> .onehundredtwenty { font-size: 120%; } <style> .ninety { font-size: 90%; } .eightyfive { font-size: 85%; } .eighty { font-size: 80%; } .seventyfive { font-size: 75%; } .seventy { font-size: 70%; } .fifty { font-size: 50%; } .forty { font-size: 40%; } </style> <!-- --- --> <!-- class: banner --> --- name: title-slide <div style="background-image: url('images/scraping2.jpeg'); background-size: 65%; background-position: 70% 50%; background-repeat: no-repeat;"> ## Une introduction au WebScraping ###.fancy[Naviguer, <br>Aspirer, <br>Extraire, <br>Nettoyer] <br> <br> [.seventyfive[
@Ben_Guinaudeau]](http://twitter.com/Ben_Guinaudeau)<br> [.seventyfive[
@benjaminguinaudeau]](http://github.com/benjaminguinaudeau)<br> [.seventyfive[
benguinaudeau.com/]](https://benguinaudeau.com/) .seventyfive[4 Janvier 2022] .fifty[Lien vers la présentation: [benguinaudeau.com/maindoc/eiom_2022_webscraping](https://benguinaudeau.com/maindoc/eiom_2022_webscraping)] --- ### Objectif: Un tremplin non technique vers le webscraping <hr style="height:15px; visibility:hidden;" /> 1. Apprendre la structure d'un projet de webscraping -- <hr style="height:15px; visibility:hidden;" /> 2. Introduire les requêtes HTTP et les langages HTML/CSS -- <hr style="height:15px; visibility:hidden;" /> 3. Présenter les packages `{xml2}` et `{rvest}` --- ### Programme de l'atelier 1. .fancy[Webscraping] .seventyfive[comme dans "grattage du web"?] -- 2. .fancy[Naviguer] .seventyfive[Localiser les données brutes] -- 3. .fancy[Aspirer] .seventyfive[Lire une page HTML] -- 4. .fancy[Extraire] .seventyfive[Raffiner les données brutes] -- 5. .fancy[Nettoyer] -- 6. .seventyfive[9 conseils pour devenir une "gratteuse" professionnelle] --- ### .fancy[Webscraping] <center> <img src="images/grattage.png"> </center> --- ### .fancy[Webscraping] <center> <div class="tenor-gif-embed" data-postid="10982646" data-share-method="host" data-aspect-ratio="1.38122" data-width="70%"> <a href="https://tenor.com/view/shiba-inu-computer-fast-typing-gif-10982646">Shiba Inu GIF</a> from <a href="https://tenor.com/search/shiba-gifs">Shiba GIFs</a> </div> <script type="text/javascript" async src="https://tenor.com/embed.js"></script> </center> --- ### .fancy[Webscraping] > Aspiration/extraction automatique de données web -- .cols3[ <hr style="height:15px; visibility:hidden;" /> <center> Données non-structurées, lisibles par l'humain et publiquement accessibles <img src = "images/debat_unstructured.png"> <hr style="height:15px; visibility:hidden;" /> </center> ] .cols3[ <br> <center> .large[**`->`**] </center> ] .cols3[ <hr style="height:10px; visibility:hidden;" /> <center> Jeux de données sytématiques, structurés, rectangulaires et lisibles par la machine <img src = "images/debat_structured.png"> <hr style="height:15px; visibility:hidden;" /> </center> ] --- ### .fancy[Webscraping] .seventyfive[A quoi bon?] <center> <img src="images/meme/1_yXPBG8SczoV4UUOMkfJaUg.jpeg"> </center> --- ### La révolution digitale est une opportunité unique pour les chercheurs.euses .seventyfive[ 1 Accès exhaustif à d'immense corpus de documents + médias, accords internationaux, discours et procédures parlementaires, rapports annuels d'entreprises, jurisprudences, etc. ] -- <!-- Les réseaux sociaux transforment chaque utilisateur en producteur de contenu --> .seventyfive[ 2 La trace digitale des réseaux sociaux permet l'étude de nombreux phénomènes sociaux + polarisation affective/idéologique (Facebook), transmission de l'information parmi les acteurs d'un marché (Twitter), parcours professionels (Linkedin), ... ] --- ### Les sites web sont optimisés pour une navigation humaine > A quel âge Justin Trudeau est-il devenu premier ministre? -- Etapes: A. naviguer vers un moteur de recherche <hr style="height:15px; visibility:hidden;" /> -- B. chercher `"Justin Trudeau dates importantes"` <hr style="height:15px; visibility:hidden;" /> -- C. sélectionner parmi les résultats un site contenant l'information <hr style="height:15px; visibility:hidden;" /> -- D. extraire rapidement l'information cherchée en exploitant le formatage --- ### Mais l'extraction systématique de données peut rapidement devenir laborieux > A quel âge les premiers ministres du Canada sont-ils devenus premier ministre? Répéter A-D pour chacun des 23 premiers ministres -- + 1 Justin Trudeau: A, B, C,D -- + 2 Stephen Harper: A, B, C, D -- + 3 Paul Martin: A, B, C, D -- + ... + 23 John Macdonald: A, B, C, D --- <center> <h3> Aspiration automatique de données web </h3> </center> .leftcol[ #### Avantages + efficace (même pour des ensembles de mégadonnées) + systématique (données disponibles pour la population ; pas besoin d'échantillonage) + précis + (reproducible) ] -- .rightcol[ #### Inconvénients + détournement de l'objectif initial des sites internet (processus technique) + potentielle violation des CGU ] --- ### Comment approcher un projet d'aspiration automatique? > *There is no one solution to all problems. It’s the problem itself that can lead to the solution.* <br> .seventyfice[~Jay Maisel] -- Chaque projet est unique, mais tous reposent sur les mêmes compétences: * ingénierie URL * connaissances minimales des requêtes HTTP et du langage HTML * manipulation des sélecteurs CSS --- class: center <br> ### Une approche unifiée pour les projets d'aspiration automatique <br> -- 1 .fancy[Naviguer] .seventyfive[Localiser les données brutes] -- 2 .fancy[Aspirer] .seventyfive[Accéder à l'information brute] -- 3 .fancy[Extraire] .seventyfive[Raffiner les données brutes] -- 4 .fancy[Nettoyer] --- <center> <h3> 1 .fancy[Naviguer] .seventyfive[Localiser les données brutes] </h3> <br> <img src = "images/meme/morpheus_meme-432x270.png" style = "height: 100%"> </center> --- ### .fancy[Naviguer] .seventyfive[Localiser les données brutes] <br> + Quelles données doivent être prélevées? -- + Quelles pages devront être visitées afin de collecter les données? -- + Est-ce que la collecte devra être répétée chaque jour, chaque semaine, chaque année? --- ### .fancy[Exemple 1] Dates des élections fédérales canadiennes et allemandes -- <center> <a href = "https://www.elections.ca/content.aspx?section=ele&dir=pas&document=index&lang=f"> Site officiel d'Elections Canada</a> <img src="images/elect_canada_official.png" style="width: 80%" /> </center> --- ### .fancy[Exemple 1] Dates des élections fédérales canadiennes et allemandes <center> <a href = "https://www.bundestag.de/parlament/wahlen/ergebnisse_seit1949-244692"> Site officiel des élections allemandes</a> <img src="images/elect_german_official.png" style="width: 80%" /> </center> --- ### .fancy[Exemple 1] Dates des élections fédérales canadiennes et allemandes <center> <a href = "https://en.wikipedia.org/wiki/List_of_Canadian_federal_general_elections"> Liste des élections canadiennes sur Wikipédia</a> <img src="images/elect_canada_wiki.png" style="width: 80%" /> </center> --- ### .fancy[Exemple 1] Dates des élections fédérales canadiennes et allemandes <center> <a href = "https://de.wikipedia.org/wiki/Bundestagswahl"> Liste des élections allemandes sur Wikipédia</a> <img src="images/elect_german_wiki.png" style="width: 80%" /> </center> --- <center> <h3> 2 .fancy[Aspirer] .seventyfive[Charger la donnée brute] </h3> <br> <img src="images/requete.png" style="width: 80%" /> </center> --- ### .fancy[Aspirer] Comprendre la structure d'une URL <center> <img src="images/url.png" style="width: 80%" /> </center> --- ### .fancy[Aspirer] Comprendre la structure d'une URL > https://en.wikipedia.org:80/wiki/List_of_Canadian_federal_general_elections -- Au moins quatre éléments composent une URL 1. un protocole `https://` -- 2. un nom de domaine `en.wikipedia.org` -- 3. un port `:80` -- 4. un chemin vers le contenu spécifique `/wiki/List_of_Canadian_federal_general_elections` --- <!-- google.com == http://google.com:80/index.html --> ### .fancy[Aspirer] avec <img src="images/Rlogo.svg" style="display: inline-block; margin: 0"; width="30px"/> et le package `{xml2}` #### Input .details[ ```r url <- "https://en.wikipedia.org/wiki/List_of_Canadian_federal_general_elections" page <- xml2::read_html(url) page ``` ``` #> {html_document} #> <html class="client-nojs" lang="en" dir="ltr"> #> [1] <head>\n<meta http-equiv="Content-Type" content="text/html; charset=UTF-8 ... #> [2] <body class="mediawiki ltr sitedir-ltr mw-hide-empty-elt ns-0 ns-subject ... ``` ] -- <br> #### Output ```r xml2::write_html(page, file = "test.html") # file.show("test.html") # browseURL("test.html") ``` --- <center> <h3> .fancy[3 Extraire] .seventyfive[Raffiner les données avec `{rvest}`] </h3> <img src="images/meme/rvest.jpeg" style="width: 80%"> </center> --- #### .fancy[Exemple 2] .seventyfive[Extraire les noms, partis et liens des députés canadiens] <center> <a href = "https://www.noscommunes.ca/members/fr/constituencies">Exemple 2 </a> <img src="images/mp_screenshot.png" style="width: 80%" /> </center> --- ### .fancy[Extraire] .seventyfive[Qu'est-ce qu'une page web?] <center> <img src="images/html_css_js.png" style="width: 60%"> </center> --- ### .fancy[Extraire] .seventyfive[Qu'est-ce qu'une page web?] Une page web a trois composantes principales: -- 1. **HTML (Hyper Text Markup Language)** <br> prend en charge le contenu (textes, images, liens internes/externes) <hr style="height:15px; visibility:hidden;" /> -- 2. **CSS (Cascading Style Sheets)** <br> définit l'apparence des éléments HTML <hr style="height:15px; visibility:hidden;" /> -- 3. **JS (Javascript)** <br> modifie l'apparence/le contenu en fonction des interactions avec l'utilisateur --- #### .fancy[Extraire] .seventyfive[Disséquer une page web avec les outils *developer*] <center> <a href = "https://www.noscommunes.ca/members/fr/constituencies"> Example 2 </a> <img src="images/developer_tools.png" style="width: 80%" /> </center> --- ### .fancy[Extraire] .seventyfive[Une mosaïque d'éléments HTML] <center> <img src="images/html_element_example.png" style="width: 50%" /> </center> -- Chaque élément HTML * est délimité par des balises de début `<tag>` et de fin `</tag>` -- * est charactérisé par des attributs insérés dans la balise de début (id, class, type, style, etc.) -- * peut contenir un ou plusieurs éléments enfants (dont il est le parent) --- ### .fancy[Extraire] .seventyfive[Une mosaïque d'éléments HTML] <center> <img src="images/html_tree.png" style="width: 50%" /> </center> -- Chaque page web peut être représentée comme une arborescence: -- * avec un sommet initial unique (document), -- * qui contient au moins deux enfants `head` et `body` --- ### .fancy[Extraire] .seventyfive[Une mosaïque d'éléments HTML] <center> <img src="images/exemple_developer.png" style="width: 100%" /> </center> --- ### .fancy[Extraire] .seventyfive[Parcourir l'arborescence HTML avec `{rvest}`] 1 Isoler l'élément contenant la donnée ```r webpage <- xml2::read_html(url) elem <- isole_element(webpage, "id_of_the_element_of_containing_the_data") ``` -- 2 Extraire l'information ```r value <- extract_data(elem) ``` -- <center> <img src="images/html_element_example.png" style="width: 50%" /> </center> --- ### .fancy[Extraire] .seventyfive[Parcourir l'arborescence HTML avec `{rvest}`] #### .seventyfive[ 2 fonctions d'isolation] + .seventyfive[`html_element(parent, sélecteur)` isole le premier élément enfant de l'élément `parent` correspondant à `sélecteur`] -- + .seventyfive[`html_elements(parent, sélecteur)` isole l'ensemble des éléments enfant de l'élément `parent` correspondant à `sélecteur`] -- #### .seventyfive[2 fonctions d'extraction] + .seventyfive[`html_text(element)` extrait le texte de `element`] -- + .seventyfive[`html_attr(element, attr)` extrait l'attribut `attr` de `element`] --- ### .fancy[Extraire] .seventyfive[isoler avec `rvest::html_element`] .seventyfive[`html_element(parent, sélecteur)` isole le premier élément enfant de l'élément `parent` correspondant à `sélecteur`] .details[ ```r page <- xml2::read_html("https://www.noscommunes.ca/members/fr/constituencies") rvest::html_element(page, "body") ``` ``` #> {html_node} #> <body class="body-wrapper ce-hoc vh-100"> #> [1] <header><!-- Skip to Content --><a href="#StartOfContent" class="ce-hoc- ... #> [2] <nav id="ce-hoc-header" class="ce-hoc-nav navbar navbar-expand-lg navbar ... #> [3] <a id="StartOfContent" class="sr-only" tabindex="-1">Début du contenu</a> #> [4] <main><nav aria-label="Secondary menu"><div class="ce-submenu">\r\n ... #> [5] <footer id="ce-hoc-footer" class="ce-hoc-footer hidden-print"><div class ... #> [6] <script src="/members/js/site.min.js?v=T-j8Lyczoqix_gsvzYQpltjUChEYTEjUv ... #> [7] <script src="https://cdn.datatables.net/1.10.19/js/jquery.dataTables.min ... #> [8] <script src="https://cdn.datatables.net/1.10.19/js/dataTables.bootstrap4 ... #> [9] <script>\r\n $(function () {\r\n $('[data-toggle="tooltip"]'). ... #> [10] <script>\r\n $(function () {\r\n //Set default DataTable behav ... #> [11] <script src="/members/js/Main/constituencies.min.js"></script> #> [12] <script>\n $(function () {\n /*script for the datatabl ... ``` ] --- ### .fancy[Extraire] .seventyfive[isoler avec `rvest::html_element`] .seventyfive[`html_element(parent, sélecteur)` isole le premier élément enfant de l'élément `parent` correspondant à `sélecteur`] .details[ ```r rvest::html_element(page, "header") ``` ``` #> {html_node} #> <header> #> [1] <a href="#StartOfContent" class="ce-hoc-skipnav sr-only sr-only-focusable ... #> [2] <div id="ce-parl-header" class="ce-parl-header hidden-print">\n\t\t ... ``` ] --- ### .fancy[Extraire] .seventyfive[isoler avec `rvest::html_element`] .seventyfive[`html_element(parent, sélecteur)` isole le premier élément enfant de l'élément `parent` correspondant à `sélecteur`] .details[ ```r rvest::html_element(page, "[class='mip-mp-name']") ``` ``` #> {html_node} #> <span class="mip-mp-name"> ``` ] .details[ ```r # Shortcut for class rvest::html_element(page, ".mip-mp-name") ``` ``` #> {html_node} #> <span class="mip-mp-name"> ``` ] --- ### .fancy[Extraire] .seventyfive[isoler avec `rvest::html_elements`] .seventyfive[`html_elements(parent, sélecteur)` isole l'ensemble des éléments enfant de l'élément `parent` correspondant à `sélecteur`] .details[ ```r rvest::html_elements(page, "[class='mip-mp-name']") ``` ``` #> {xml_nodeset (338)} #> [1] <span class="mip-mp-name">Ed Fast</span> #> [2] <span class="mip-mp-name">Sylvie Bérubé</span> #> [3] <span class="mip-mp-name">Sébastien Lemire</span> #> [4] <span class="mip-mp-name">Serge Cormier</span> #> [5] <span class="mip-mp-name">Mélanie Joly</span> #> [6] <span class="mip-mp-name">Mark Holland</span> #> [7] <span class="mip-mp-name">Angelo Iacono</span> #> [8] <span class="mip-mp-name">Carol Hughes</span> #> [9] <span class="mip-mp-name">Stéphane Lauzon</span> #> [10] <span class="mip-mp-name">Leah Taylor Roy</span> #> [11] <span class="mip-mp-name">Ken McDonald</span> #> [12] <span class="mip-mp-name">Kristina Michaud</span> #> [13] <span class="mip-mp-name">Ryan Williams</span> #> [14] <span class="mip-mp-name">Blake Richards</span> #> [15] <span class="mip-mp-name">John Brassard</span> #> [16] <span class="mip-mp-name">Doug Shipley</span> #> [17] <span class="mip-mp-name">Damien C. Kurek</span> #> [18] <span class="mip-mp-name">Rosemarie Falk</span> #> [19] <span class="mip-mp-name">Nathaniel Erskine-Smith</span> #> [20] <span class="mip-mp-name">Richard Lehoux</span> #> ... ``` ] --- ### .fancy[Extraire] .seventyfive[extraire le texte `rvest::html_text`] .seventyfive[`html_text(element)` extrait le texte de `element`] .details[ ```r # Isole l'ensemble des éléments dont la classe contient `mip-constituency-tile` mp_tuiles <- rvest::html_elements(page, "[class='mip-mp-name']") # Pour chaque élément de l'ensemble, extrait l'attribut href rvest::html_text(mp_tuiles) ``` ``` #> [1] "Ed Fast" "Sylvie Bérubé" #> [3] "Sébastien Lemire" "Serge Cormier" #> [5] "Mélanie Joly" "Mark Holland" #> [7] "Angelo Iacono" "Carol Hughes" #> [9] "Stéphane Lauzon" "Leah Taylor Roy" #> [11] "Ken McDonald" "Kristina Michaud" #> [13] "Ryan Williams" "Blake Richards" #> [15] "John Brassard" "Doug Shipley" #> [17] "Damien C. Kurek" "Rosemarie Falk" #> [19] "Nathaniel Erskine-Smith" "Richard Lehoux" #> [21] "Caroline Desbiens" "Julie Vignola" #> [23] "Dominic LeBlanc" "Louis Plamondon" #> [25] "Dominique Vien" "Yves-François Blanchet" #> [27] "Yves Perron" "Churence Rogers" #> [29] "Emmanuel Dubourg" "Martin Shields" #> [31] "Shafqat Ali" "Maninder Sidhu" #> [33] "Ruby Sahota" "Kamal Khera" #> [35] "Sonia Sidhu" "Larry Maguire" #> [37] "Larry Brock" "Pascale St-Onge" #> [39] "Alexandra Mendès" "Alex Ruff" #> [41] "Karina Gould" "Terry Beech" #> [43] "Jagmeet Singh" "Len Webber" #> [45] "Jasraj Singh Hallan" "Bob Benzen" #> [47] "Stephanie Kusie" "Michelle Rempel Garner" #> [49] "Pat Kelly" "Tom Kmiec" #> [51] "Ron Liepert" "George Chahal" #> [53] "Greg McLean" "Bryan May" #> [55] "Mike Kelloway" "Lawrence MacAulay" #> [57] "Todd Doherty" "Pierre Poilievre" #> [59] "Dan Albas" "Pierre Paul-Hus" #> [61] "Marty Morantz" "Sean Casey" #> [63] "Brenda Shanahan" "Dave Epp" #> [65] "Richard Martel" "Mark Strahl" #> [67] "Niki Ashton" "John Aldag" #> [69] "Clifford Small" "Marie-Claude Bibeau" #> [71] "Ron McKinnon" "Gord Johns" #> [73] "Alistair MacGregor" "Stephen Ellis" #> [75] "Jeremy Patzer" "Darren Fisher" #> [77] "Dan Mazier" "Julie Dzerowicz" #> [79] "Carla Qualtrough" "Gary Vidal" #> [81] "Michael Coteau" "Han Dong" #> [83] "Robert Oliphant" "Anju Dhillon" #> [85] "Martin Champoux" "Kyle Seeback" #> [87] "Erin O'Toole" "Blake Desjarlais" #> [89] "Ziad Aboultaif" "Tim Uppal" #> [91] "Matt Jeneroux" "Heather McPherson" #> [93] "Randy Boissonnault" "Kelly McCauley" #> [95] "Mike Lake" "Marco Mendicino" #> [97] "Robert J. Morrissey" "Karen Vecchio" #> [99] "Daniel Blaikie" "Randall Garrison" #> [101] "Chris Lewis" "Yvan Baker" #> [103] "James Maloney" "Kirsty Duncan" #> [105] "Dan Muys" "Ken Hardie" #> [107] "John Barlow" "Laila Goodridge" #> [109] "Jenica Atwin" "Rob Moore" #> [111] "Diane Lebouthillier" "Steven MacKinnon" #> [113] "Francis Drouin" "Chris Warkentin" #> [115] "Lloyd Longfield" "Leslyn Lewis" #> [117] "Jamie Schmale" "Andy Fillmore" #> [119] "Lena Metlege Diab" "Lisa Hepfner" #> [121] "Matthew Green" "Chad Collins" #> [123] "Filomena Tassi" "Shelby Kramp-Neuman" #> [125] "Soraya Martinez Ferrada" "Pablo Rodriguez" #> [127] "Greg Fergus" "Judy A. Sgro" #> [129] "Ben Lobb" "Gabriel Ste-Marie" #> [131] "Mario Simard" "Frank Caputo" #> [133] "Jenna Sudds" "Tracy Gray" #> [135] "Eric Melillo" "Raquel Dancho" #> [137] "Kody Blois" "Mark Gerretsen" #> [139] "Anna Roberts" "Mike Morrice" #> [141] "Tim Louis" "Valerie Bradford" #> [143] "Rob Morrison" "Mario Beaulieu" #> [145] "Alain Therrien" "Yvonne Jones" #> [147] "Alexis Brunelle-Duceppe" "Francis Scarpaleggia" #> [149] "Shannon Stubbs" "Lianne Rood" #> [151] "Scott Reid" "Tako Van Popta" #> [153] "David Lametti" "Marie-Hélène Gaudreau" #> [155] "Steven Guilbeault" "Fayçal El-Khoury" #> [157] "Michael Barrett" "Rachael Thomas" #> [159] "Jacques Gourde" "Peter Fragiskatos" #> [161] "Lindsay Mathyssen" "Arielle Kayabaga" #> [163] "Gudie Hutchings" "Sherry Romanado" #> [165] "Denis Trudel" "Joël Lightbound" #> [167] "Gérard Deltell" "René Arseneault" #> [169] "Heath MacDonald" "Marilène Gill" #> [171] "Yves Robillard" "Helena Jaczek" #> [173] "Mary Ng" "Paul Chiang" #> [175] "Glen Motz" "Luc Berthold" #> [177] "Adam van Koeverden" "Jean-Denis Garon" #> [179] "Jake Stewart" "Brad Vis" #> [181] "Omar Alghabra" "Iqra Khalid" #> [183] "Peter Fonseca" "Sven Spengemann" #> [185] "Iqwinder Gaheer" "Rechie Valdez" #> [187] "Ginette Petitpas Taylor" "Stéphane Bergeron" #> [189] "Luc Thériault" "Bernard Généreux" #> [191] "Anthony Housefather" "Fraser Tolmie" #> [193] "Lisa Marie Barron" "Chandra Arya" #> [195] "Peter Julian" "Tony Van Bynen" #> [197] "Tony Baldinelli" "Vance Badawey" #> [199] "Dean Allison" "Marc G. Serré" #> [201] "Anthony Rota" "Rachel Blaney" #> [203] "Mel Arnold" "Jonathan Wilkinson" #> [205] "Philip Lawrence" "Marc Garneau" #> [207] "John Williamson" "Sean Fraser" #> [209] "Chris d'Entremont" "Lori Idlout" #> [211] "Anita Anand" "Pam Damoff" #> [213] "Richard Cannings" "Marie-France Lalonde" #> [215] "Colin Carrie" "Yasir Naqvi" #> [217] "Anita Vandenbeld" "David J. McGuinty" #> [219] "Mona Fortier" "Rachel Bendayan" #> [221] "Dave MacKenzie" "Justin Trudeau" #> [223] "Arif Virani" "Scott Aitchison" #> [225] "Arnold Viersen" "John Nater" #> [227] "Michelle Ferreri" "Jennifer O'Connell" #> [229] "Xavier Barsalou-Duval" "Sameer Zuberi" #> [231] "Marc Dalton" "Sophie Chatel" #> [233] "Bonita Zarrillo" "Candice Bergen" #> [235] "Joël Godin" "Randy Hoback" #> [237] "Bob Zimmer" "Ted Falk" #> [239] "Jean-Yves Duclos" "Blaine Calkins" #> [241] "Earl Dreeshen" "Warren Steinley" #> [243] "Andrew Scheer" "Michael Kram" #> [245] "Cheryl Gallant" "Monique Pauzé" #> [247] "Majid Jowhari" "Alain Rayes" #> [249] "Wilson Miao" "Maxime Blanchette-Joncas" #> [251] "Luc Desilets" "Rhéal Éloi Fortin" #> [253] "Alexandre Boulerice" "Elizabeth May" #> [255] "Darrell Samson" "Wayne Long" #> [257] "Dan Vandal" "Simon-Pierre Savard-Tremblay" #> [259] "Christine Normandin" "Emmanuella Lambropoulos" #> [261] "Patricia Lattanzio" "François-Philippe Champagne" #> [263] "Claude DeBellefeuille" "Marilyn Gladu" #> [265] "Kevin Waugh" "Brad Redekopp" #> [267] "Corey Tochor" "Terry Sheehan" #> [269] "Jean Yip" "Salma Zahid" #> [271] "John McKay" "Shaun Chen" #> [273] "Gary Anandasangaree" "Bill Blair" #> [275] "James Bezan" "Kelly Block" #> [277] "Andréanne Larouche" "Élisabeth Brière" #> [279] "Garnett Genuis" "Terry Dowdall" #> [281] "Adam Chambers" "Taylor Bachrach" #> [283] "Robert Kitchen" "Rick Perkins" #> [285] "Kevin Vuong" "Michael Cooper" #> [287] "Chris Bittle" "Joanne Thompson" #> [289] "Seamus O'Regan" "Parm Bains" #> [291] "Eric Duncan" "Dane Lloyd" #> [293] "Viviane Lapointe" "Randeep Sarai" #> [295] "Sukh Dhaliwal" "Kerry-Lynne D. Findlay" #> [297] "Jaime Battiste" "Nathalie Sinclair-Desgagné" #> [299] "Michael V. McLeod" "Louise Chabot" #> [301] "Melissa Lantsman" "Marcus Powlowski" #> [303] "Patty Hajdu" "Charlie Angus" #> [305] "Richard Bragdon" "Marci Ien" #> [307] "Julie Dabrusin" "Carolyn Bennett" #> [309] "René Villemure" "Chrystia Freeland" #> [311] "Taleeb Noormohamed" "Don Davies" #> [313] "Joyce Murray" "Hedy Fry" #> [315] "Jenny Kwan" "Harjit S. Sajjan" #> [317] "Peter Schiefke" "Francesco Sorbara" #> [319] "Laurel Collins" "Marc Miller" #> [321] "Annie Koutrakis" "Bardish Chagger" #> [323] "Michael D. Chong" "Patrick Weiler" #> [325] "Ryan Turnbull" "Ali Ehsassi" #> [327] "Brian Masse" "Irek Kusmierczyk" #> [329] "Leah Gazan" "Jim Carr" #> [331] "Kevin Lamoureux" "Terry Duguid" #> [333] "Gerald Soroka" "Ya'ara Saks" #> [335] "Scot Davidson" "Ahmed Hussen" #> [337] "Cathay Wagantall" "Brendan Hanley" ``` ] --- ### .fancy[Extraire] .seventyfive[extraire des attributs avec `rvest::html_attr`] .seventyfive[`html_attr(element, attr)` extrait l'attribut `attr` de `element`] .details[ ```r # Isole l'ensemble des éléments dont la classe contient `mip-constituency-tile` mp_tuiles <- rvest::html_elements(page, "[class='mip-constituency-tile']") # Pour chaque élément de l'ensemble, extrait l'attribut href rvest::html_attr(mp_tuiles, "href") ``` ``` #> [1] "/members/fr/constituencies/abbotsford(898)" #> [2] "/members/fr/constituencies/abitibi-baie-james-nunavik-eeyou(637)" #> [3] "/members/fr/constituencies/abitibi-temiscamingue(638)" #> [4] "/members/fr/constituencies/acadie-bathurst(627)" #> [5] "/members/fr/constituencies/ahuntsic-cartierville(639)" #> [6] "/members/fr/constituencies/ajax(715)" #> [7] "/members/fr/constituencies/alfred-pellan(640)" #> [8] "/members/fr/constituencies/algoma-manitoulin-kapuskasing(716)" #> [9] "/members/fr/constituencies/argenteuil-la-petite-nation(641)" #> [10] "/members/fr/constituencies/aurora-oak-ridges-richmond-hill(717)" #> [11] "/members/fr/constituencies/avalon(605)" #> [12] "/members/fr/constituencies/avignon-la-mitis-matane-matapedia(642)" #> [13] "/members/fr/constituencies/bay-of-quinte(720)" #> [14] "/members/fr/constituencies/banff-airdrie(864)" #> [15] "/members/fr/constituencies/barrie-innisfil(718)" #> [16] "/members/fr/constituencies/barrie-springwater-oro-medonte(719)" #> [17] "/members/fr/constituencies/battle-river-crowfoot(865)" #> [18] "/members/fr/constituencies/battlefords-lloydminster(850)" #> [19] "/members/fr/constituencies/beaches-east-york(721)" #> [20] "/members/fr/constituencies/beauce(643)" #> [21] "/members/fr/constituencies/beauport-cote-de-beaupre-ile-dorleans-charlevoix(656)" #> [22] "/members/fr/constituencies/beauport-limoilou(644)" #> [23] "/members/fr/constituencies/beausejour(628)" #> [24] "/members/fr/constituencies/becancour-nicolet-saurel(645)" #> [25] "/members/fr/constituencies/bellechasse-les-etchemins-levis(646)" #> [26] "/members/fr/constituencies/beloeil-chambly(647)" #> [27] "/members/fr/constituencies/berthier-maskinonge(648)" #> [28] "/members/fr/constituencies/bonavista-burin-trinity(606)" #> [29] "/members/fr/constituencies/bourassa(651)" #> [30] "/members/fr/constituencies/bow-river(866)" #> [31] "/members/fr/constituencies/brampton-centre(722)" #> [32] "/members/fr/constituencies/brampton-east(723)" #> [33] "/members/fr/constituencies/brampton-north(724)" #> [34] "/members/fr/constituencies/brampton-west(726)" #> [35] "/members/fr/constituencies/brampton-south(725)" #> [36] "/members/fr/constituencies/brandon-souris(836)" #> [37] "/members/fr/constituencies/brantford-brant(727)" #> [38] "/members/fr/constituencies/brome-missisquoi(652)" #> [39] "/members/fr/constituencies/brossard-saint-lambert(653)" #> [40] "/members/fr/constituencies/bruce-grey-owen-sound(728)" #> [41] "/members/fr/constituencies/burlington(729)" #> [42] "/members/fr/constituencies/burnaby-north-seymour(899)" #> [43] "/members/fr/constituencies/burnaby-south(900)" #> [44] "/members/fr/constituencies/calgary-confederation(868)" #> [45] "/members/fr/constituencies/calgary-forest-lawn(869)" #> [46] "/members/fr/constituencies/calgary-heritage(870)" #> [47] "/members/fr/constituencies/calgary-midnapore(871)" #> [48] "/members/fr/constituencies/calgary-nose-hill(872)" #> [49] "/members/fr/constituencies/calgary-rocky-ridge(873)" #> [50] "/members/fr/constituencies/calgary-shepard(874)" #> [51] "/members/fr/constituencies/calgary-signal-hill(875)" #> [52] "/members/fr/constituencies/calgary-skyview(876)" #> [53] "/members/fr/constituencies/calgary-centre(867)" #> [54] "/members/fr/constituencies/cambridge(730)" #> [55] "/members/fr/constituencies/cape-breton-canso(616)" #> [56] "/members/fr/constituencies/cardigan(612)" #> [57] "/members/fr/constituencies/cariboo-prince-george(901)" #> [58] "/members/fr/constituencies/carleton(802)" #> [59] "/members/fr/constituencies/central-okanagan-similkameen-nicola(902)" #> [60] "/members/fr/constituencies/charlesbourg-haute-saint-charles(655)" #> [61] "/members/fr/constituencies/charleswood-st-james-assiniboia-headingley(837)" #> [62] "/members/fr/constituencies/charlottetown(613)" #> [63] "/members/fr/constituencies/chateauguay-lacolle(657)" #> [64] "/members/fr/constituencies/chatham-kent-leamington(731)" #> [65] "/members/fr/constituencies/chicoutimi-le-fjord(658)" #> [66] "/members/fr/constituencies/chilliwack-hope(903)" #> [67] "/members/fr/constituencies/churchill-keewatinook-aski(838)" #> [68] "/members/fr/constituencies/cloverdale-langley-city(904)" #> [69] "/members/fr/constituencies/coast-of-bays-central-notre-dame(607)" #> [70] "/members/fr/constituencies/compton-stanstead(659)" #> [71] "/members/fr/constituencies/coquitlam-port-coquitlam(905)" #> [72] "/members/fr/constituencies/courtenay-alberni(906)" #> [73] "/members/fr/constituencies/cowichan-malahat-langford(907)" #> [74] "/members/fr/constituencies/cumberland-colchester(618)" #> [75] "/members/fr/constituencies/cypress-hills-grasslands(851)" #> [76] "/members/fr/constituencies/dartmouth-cole-harbour(619)" #> [77] "/members/fr/constituencies/dauphin-swan-river-neepawa(839)" #> [78] "/members/fr/constituencies/davenport(732)" #> [79] "/members/fr/constituencies/delta(908)" #> [80] "/members/fr/constituencies/desnethe-missinippi-churchill-river(852)" #> [81] "/members/fr/constituencies/don-valley-east(733)" #> [82] "/members/fr/constituencies/don-valley-north(734)" #> [83] "/members/fr/constituencies/don-valley-west(735)" #> [84] "/members/fr/constituencies/dorval-lachine-lasalle(660)" #> [85] "/members/fr/constituencies/drummond(661)" #> [86] "/members/fr/constituencies/dufferin-caledon(736)" #> [87] "/members/fr/constituencies/durham(737)" #> [88] "/members/fr/constituencies/edmonton-griesbach(878)" #> [89] "/members/fr/constituencies/edmonton-manning(879)" #> [90] "/members/fr/constituencies/edmonton-mill-woods(880)" #> [91] "/members/fr/constituencies/edmonton-riverbend(881)" #> [92] "/members/fr/constituencies/edmonton-strathcona(882)" #> [93] "/members/fr/constituencies/edmonton-centre(877)" #> [94] "/members/fr/constituencies/edmonton-west(883)" #> [95] "/members/fr/constituencies/edmonton-wetaskiwin(884)" #> [96] "/members/fr/constituencies/eglinton-lawrence(738)" #> [97] "/members/fr/constituencies/egmont(614)" #> [98] "/members/fr/constituencies/elgin-middlesex-london(739)" #> [99] "/members/fr/constituencies/elmwood-transcona(840)" #> [100] "/members/fr/constituencies/esquimalt-saanich-sooke(923)" #> [101] "/members/fr/constituencies/essex(740)" #> [102] "/members/fr/constituencies/etobicoke-centre(741)" #> [103] "/members/fr/constituencies/etobicoke-lakeshore(742)" #> [104] "/members/fr/constituencies/etobicoke-north(743)" #> [105] "/members/fr/constituencies/flamborough-glanbrook(744)" #> [106] "/members/fr/constituencies/fleetwood-port-kells(909)" #> [107] "/members/fr/constituencies/foothills(885)" #> [108] "/members/fr/constituencies/fort-mcmurray-cold-lake(886)" #> [109] "/members/fr/constituencies/fredericton(629)" #> [110] "/members/fr/constituencies/fundy-royal(630)" #> [111] "/members/fr/constituencies/gaspesie-les-iles-de-la-madeleine(662)" #> [112] "/members/fr/constituencies/gatineau(663)" #> [113] "/members/fr/constituencies/glengarry-prescott-russell(745)" #> [114] "/members/fr/constituencies/grande-prairie-mackenzie(887)" #> [115] "/members/fr/constituencies/guelph(746)" #> [116] "/members/fr/constituencies/haldimand-norfolk(747)" #> [117] "/members/fr/constituencies/haliburton-kawartha-lakes-brock(748)" #> [118] "/members/fr/constituencies/halifax(620)" #> [119] "/members/fr/constituencies/halifax-west(621)" #> [120] "/members/fr/constituencies/hamilton-mountain(751)" #> [121] "/members/fr/constituencies/hamilton-centre(749)" #> [122] "/members/fr/constituencies/hamilton-east-stoney-creek(750)" #> [123] "/members/fr/constituencies/hamilton-west-ancaster-dundas(752)" #> [124] "/members/fr/constituencies/hastings-lennox-and-addington(753)" #> [125] "/members/fr/constituencies/hochelaga(664)" #> [126] "/members/fr/constituencies/honore-mercier(665)" #> [127] "/members/fr/constituencies/hull-aylmer(666)" #> [128] "/members/fr/constituencies/humber-river-black-creek(835)" #> [129] "/members/fr/constituencies/huron-bruce(754)" #> [130] "/members/fr/constituencies/joliette(667)" #> [131] "/members/fr/constituencies/jonquiere(668)" #> [132] "/members/fr/constituencies/kamloops-thompson-cariboo(910)" #> [133] "/members/fr/constituencies/kanata-carleton(755)" #> [134] "/members/fr/constituencies/kelowna-lake-country(911)" #> [135] "/members/fr/constituencies/kenora(756)" #> [136] "/members/fr/constituencies/kildonan-st-paul(841)" #> [137] "/members/fr/constituencies/kings-hants(622)" #> [138] "/members/fr/constituencies/kingston-and-the-islands(758)" #> [139] "/members/fr/constituencies/king-vaughan(757)" #> [140] "/members/fr/constituencies/kitchener-centre(759)" #> [141] "/members/fr/constituencies/kitchener-conestoga(760)" #> [142] "/members/fr/constituencies/kitchener-south-hespeler(761)" #> [143] "/members/fr/constituencies/kootenay-columbia(912)" #> [144] "/members/fr/constituencies/la-pointe-de-lile(669)" #> [145] "/members/fr/constituencies/la-prairie(670)" #> [146] "/members/fr/constituencies/labrador(608)" #> [147] "/members/fr/constituencies/lac-saint-jean(671)" #> [148] "/members/fr/constituencies/lac-saint-louis(672)" #> [149] "/members/fr/constituencies/lakeland(888)" #> [150] "/members/fr/constituencies/lambton-kent-middlesex(762)" #> [151] "/members/fr/constituencies/lanark-frontenac-kingston(763)" #> [152] "/members/fr/constituencies/langley-aldergrove(913)" #> [153] "/members/fr/constituencies/lasalle-emard-verdun(673)" #> [154] "/members/fr/constituencies/laurentides-labelle(674)" #> [155] "/members/fr/constituencies/laurier-sainte-marie(675)" #> [156] "/members/fr/constituencies/laval-les-iles(676)" #> [157] "/members/fr/constituencies/leeds-grenville-thousand-islands-and-rideau-lakes(764)" #> [158] "/members/fr/constituencies/lethbridge(889)" #> [159] "/members/fr/constituencies/levis-lotbiniere(678)" #> [160] "/members/fr/constituencies/london-north-centre(766)" #> [161] "/members/fr/constituencies/london-fanshawe(765)" #> [162] "/members/fr/constituencies/london-west(767)" #> [163] "/members/fr/constituencies/long-range-mountains(609)" #> [164] "/members/fr/constituencies/longueuil-charles-lemoyne(677)" #> [165] "/members/fr/constituencies/longueuil-saint-hubert(679)" #> [166] "/members/fr/constituencies/louis-hebert(680)" #> [167] "/members/fr/constituencies/louis-saint-laurent(681)" #> [168] "/members/fr/constituencies/madawaska-restigouche(631)" #> [169] "/members/fr/constituencies/malpeque(615)" #> [170] "/members/fr/constituencies/manicouagan(682)" #> [171] "/members/fr/constituencies/marc-aurele-fortin(701)" #> [172] "/members/fr/constituencies/markham-stouffville(768)" #> [173] "/members/fr/constituencies/markham-thornhill(769)" #> [174] "/members/fr/constituencies/markham-unionville(770)" #> [175] "/members/fr/constituencies/medicine-hat-cardston-warner(890)" #> [176] "/members/fr/constituencies/megantic-lerable(683)" #> [177] "/members/fr/constituencies/milton(771)" #> [178] "/members/fr/constituencies/mirabel(684)" #> [179] "/members/fr/constituencies/miramichi-grand-lake(632)" #> [180] "/members/fr/constituencies/mission-matsqui-fraser-canyon(914)" #> [181] "/members/fr/constituencies/mississauga-centre(772)" #> [182] "/members/fr/constituencies/mississauga-erin-mills(774)" #> [183] "/members/fr/constituencies/mississauga-east-cooksville(773)" #> [184] "/members/fr/constituencies/mississauga-lakeshore(775)" #> [185] "/members/fr/constituencies/mississauga-malton(776)" #> [186] "/members/fr/constituencies/mississauga-streetsville(777)" #> [187] "/members/fr/constituencies/moncton-riverview-dieppe(633)" #> [188] "/members/fr/constituencies/montarville(685)" #> [189] "/members/fr/constituencies/montcalm(686)" #> [190] "/members/fr/constituencies/montmagny-lislet-kamouraska-riviere-du-loup(687)" #> [191] "/members/fr/constituencies/mount-royal(688)" #> [192] "/members/fr/constituencies/moose-jaw-lake-centre-lanigan(854)" #> [193] "/members/fr/constituencies/nanaimo-ladysmith(915)" #> [194] "/members/fr/constituencies/nepean(778)" #> [195] "/members/fr/constituencies/new-westminster-burnaby(916)" #> [196] "/members/fr/constituencies/newmarket-aurora(779)" #> [197] "/members/fr/constituencies/niagara-falls(781)" #> [198] "/members/fr/constituencies/niagara-centre(780)" #> [199] "/members/fr/constituencies/niagara-west(782)" #> [200] "/members/fr/constituencies/nickel-belt(783)" #> [201] "/members/fr/constituencies/nipissing-timiskaming(784)" #> [202] "/members/fr/constituencies/north-island-powell-river(934)" #> [203] "/members/fr/constituencies/north-okanagan-shuswap(917)" #> [204] "/members/fr/constituencies/north-vancouver(918)" #> [205] "/members/fr/constituencies/northumberland-peterborough-south(785)" #> [206] "/members/fr/constituencies/notre-dame-de-grace-westmount(689)" #> [207] "/members/fr/constituencies/new-brunswick-southwest(634)" #> [208] "/members/fr/constituencies/central-nova(617)" #> [209] "/members/fr/constituencies/west-nova(626)" #> [210] "/members/fr/constituencies/nunavut(942)" #> [211] "/members/fr/constituencies/oakville(786)" #> [212] "/members/fr/constituencies/oakville-north-burlington(787)" #> [213] "/members/fr/constituencies/south-okanagan-west-kootenay(926)" #> [214] "/members/fr/constituencies/orleans(790)" #> [215] "/members/fr/constituencies/oshawa(788)" #> [216] "/members/fr/constituencies/ottawa-centre(789)" #> [217] "/members/fr/constituencies/ottawa-west-nepean(793)" #> [218] "/members/fr/constituencies/ottawa-south(791)" #> [219] "/members/fr/constituencies/ottawa-vanier(792)" #> [220] "/members/fr/constituencies/outremont(690)" #> [221] "/members/fr/constituencies/oxford(794)" #> [222] "/members/fr/constituencies/papineau(691)" #> [223] "/members/fr/constituencies/parkdale-high-park(795)" #> [224] "/members/fr/constituencies/parry-sound-muskoka(796)" #> [225] "/members/fr/constituencies/peace-river-westlock(891)" #> [226] "/members/fr/constituencies/perth-wellington(797)" #> [227] "/members/fr/constituencies/peterborough-kawartha(798)" #> [228] "/members/fr/constituencies/pickering-uxbridge(799)" #> [229] "/members/fr/constituencies/pierre-boucher-les-patriotes-vercheres(650)" #> [230] "/members/fr/constituencies/pierrefonds-dollard(692)" #> [231] "/members/fr/constituencies/pitt-meadows-maple-ridge(919)" #> [232] "/members/fr/constituencies/pontiac(693)" #> [233] "/members/fr/constituencies/port-moody-coquitlam(920)" #> [234] "/members/fr/constituencies/portage-lisgar(842)" #> [235] "/members/fr/constituencies/portneuf-jacques-cartier(694)" #> [236] "/members/fr/constituencies/prince-albert(855)" #> [237] "/members/fr/constituencies/prince-george-peace-river-northern-rockies(921)" #> [238] "/members/fr/constituencies/provencher(843)" #> [239] "/members/fr/constituencies/quebec(695)" #> [240] "/members/fr/constituencies/red-deer-lacombe(893)" #> [241] "/members/fr/constituencies/red-deer-mountain-view(892)" #> [242] "/members/fr/constituencies/regina-lewvan(856)" #> [243] "/members/fr/constituencies/regina-quappelle(857)" #> [244] "/members/fr/constituencies/regina-wascana(858)" #> [245] "/members/fr/constituencies/renfrew-nipissing-pembroke(800)" #> [246] "/members/fr/constituencies/repentigny(696)" #> [247] "/members/fr/constituencies/richmond-hill(801)" #> [248] "/members/fr/constituencies/richmond-arthabaska(697)" #> [249] "/members/fr/constituencies/richmond-centre(922)" #> [250] "/members/fr/constituencies/rimouski-neigette-temiscouata-les-basques(654)" #> [251] "/members/fr/constituencies/riviere-des-mille-iles(698)" #> [252] "/members/fr/constituencies/riviere-du-nord(699)" #> [253] "/members/fr/constituencies/rosemont-la-petite-patrie(700)" #> [254] "/members/fr/constituencies/saanich-gulf-islands(924)" #> [255] "/members/fr/constituencies/sackville-preston-chezzetcook(623)" #> [256] "/members/fr/constituencies/saint-john-rothesay(635)" #> [257] "/members/fr/constituencies/saint-boniface-saint-vital(844)" #> [258] "/members/fr/constituencies/saint-hyacinthe-bagot(702)" #> [259] "/members/fr/constituencies/saint-jean(703)" #> [260] "/members/fr/constituencies/saint-laurent(704)" #> [261] "/members/fr/constituencies/saint-leonard-saint-michel(705)" #> [262] "/members/fr/constituencies/saint-maurice-champlain(706)" #> [263] "/members/fr/constituencies/salaberry-suroit(707)" #> [264] "/members/fr/constituencies/sarnia-lambton(805)" #> [265] "/members/fr/constituencies/saskatoon-grasswood(859)" #> [266] "/members/fr/constituencies/saskatoon-west(861)" #> [267] "/members/fr/constituencies/saskatoon-university(860)" #> [268] "/members/fr/constituencies/sault-ste-marie(806)" #> [269] "/members/fr/constituencies/scarborough-agincourt(807)" #> [270] "/members/fr/constituencies/scarborough-centre(808)" #> [271] "/members/fr/constituencies/scarborough-guildwood(809)" #> [272] "/members/fr/constituencies/scarborough-north(810)" #> [273] "/members/fr/constituencies/scarborough-rouge-park(811)" #> [274] "/members/fr/constituencies/scarborough-southwest(812)" #> [275] "/members/fr/constituencies/selkirk-interlake-eastman(845)" #> [276] "/members/fr/constituencies/carlton-trail-eagle-creek(853)" #> [277] "/members/fr/constituencies/shefford(708)" #> [278] "/members/fr/constituencies/sherbrooke(709)" #> [279] "/members/fr/constituencies/sherwood-park-fort-saskatchewan(895)" #> [280] "/members/fr/constituencies/simcoe-grey(813)" #> [281] "/members/fr/constituencies/simcoe-north(814)" #> [282] "/members/fr/constituencies/skeena-bulkley-valley(925)" #> [283] "/members/fr/constituencies/souris-moose-mountain(862)" #> [284] "/members/fr/constituencies/south-shore-st-margarets(624)" #> [285] "/members/fr/constituencies/spadina-fort-york(815)" #> [286] "/members/fr/constituencies/st-albert-edmonton(894)" #> [287] "/members/fr/constituencies/st-catharines(803)" #> [288] "/members/fr/constituencies/st-johns-east(610)" #> [289] "/members/fr/constituencies/st-johns-south-mount-pearl(611)" #> [290] "/members/fr/constituencies/steveston-richmond-east(928)" #> [291] "/members/fr/constituencies/stormont-dundas-south-glengarry(816)" #> [292] "/members/fr/constituencies/sturgeon-river-parkland(896)" #> [293] "/members/fr/constituencies/sudbury(817)" #> [294] "/members/fr/constituencies/surrey-centre(929)" #> [295] "/members/fr/constituencies/surrey-newton(930)" #> [296] "/members/fr/constituencies/south-surrey-white-rock(927)" #> [297] "/members/fr/constituencies/sydney-victoria(625)" #> [298] "/members/fr/constituencies/terrebonne(711)" #> [299] "/members/fr/constituencies/northwest-territories(941)" #> [300] "/members/fr/constituencies/therese-de-blainville(649)" #> [301] "/members/fr/constituencies/thornhill(818)" #> [302] "/members/fr/constituencies/thunder-bay-rainy-river(819)" #> [303] "/members/fr/constituencies/thunder-bay-superior-north(820)" #> [304] "/members/fr/constituencies/timmins-james-bay(821)" #> [305] "/members/fr/constituencies/tobique-mactaquac(636)" #> [306] "/members/fr/constituencies/toronto-centre(822)" #> [307] "/members/fr/constituencies/toronto-danforth(823)" #> [308] "/members/fr/constituencies/toronto-st-pauls(804)" #> [309] "/members/fr/constituencies/trois-rivieres(712)" #> [310] "/members/fr/constituencies/university-rosedale(824)" #> [311] "/members/fr/constituencies/vancouver-granville(933)" #> [312] "/members/fr/constituencies/vancouver-kingsway(935)" #> [313] "/members/fr/constituencies/vancouver-quadra(936)" #> [314] "/members/fr/constituencies/vancouver-centre(931)" #> [315] "/members/fr/constituencies/vancouver-east(932)" #> [316] "/members/fr/constituencies/vancouver-south(937)" #> [317] "/members/fr/constituencies/vaudreuil-soulanges(710)" #> [318] "/members/fr/constituencies/vaughan-woodbridge(825)" #> [319] "/members/fr/constituencies/victoria(938)" #> [320] "/members/fr/constituencies/ville-marie-le-sud-ouest-ile-des-soeurs(713)" #> [321] "/members/fr/constituencies/vimy(714)" #> [322] "/members/fr/constituencies/waterloo(826)" #> [323] "/members/fr/constituencies/wellington-halton-hills(827)" #> [324] "/members/fr/constituencies/west-vancouver-sunshine-coast-sea-to-sky-country(939)" #> [325] "/members/fr/constituencies/whitby(828)" #> [326] "/members/fr/constituencies/willowdale(829)" #> [327] "/members/fr/constituencies/windsor-west(831)" #> [328] "/members/fr/constituencies/windsor-tecumseh(830)" #> [329] "/members/fr/constituencies/winnipeg-centre(846)" #> [330] "/members/fr/constituencies/winnipeg-south-centre(849)" #> [331] "/members/fr/constituencies/winnipeg-north(847)" #> [332] "/members/fr/constituencies/winnipeg-south(848)" #> [333] "/members/fr/constituencies/yellowhead(897)" #> [334] "/members/fr/constituencies/york-centre(832)" #> [335] "/members/fr/constituencies/york-simcoe(833)" #> [336] "/members/fr/constituencies/york-south-weston(834)" #> [337] "/members/fr/constituencies/yorkton-melville(863)" #> [338] "/members/fr/constituencies/yukon(940)" ``` ] <!-- ### .fancy[Exemple 2] .seventyfive[Résolution] --> <!-- # Chaque élément de `mp_tuiles` contient les données sur un seul député --> <!-- mp_tuiles <- rvest::html_elements(page, "[class='mip-mp-name']") --> <!-- # La fonction `extract_all_information_on_an_mp` permet d'extraire les données d'une seule tuile --> <!-- # Cette fonction pourra ensuite être appliqué à chacune des tuiles contenues dans mp_tuiles --> <!-- extract_all_information_on_an_mp <- function(mp_tuile){ --> <!-- ### .fancy[Exemple 2] .seventyfive[Résolution] --> <!-- extract_all_information_on_an_mp <- function(mp_tuile){ --> <!-- # Extract name --> <!-- name_elem <- rvest::html_element(mp_tuile, ".ce-mip-mp-name") --> <!-- name <- rvest::html_text(name_elem) --> <!-- # Extract Party --> <!-- party_elem <- rvest::html_element(mp_tuile, ".ce-mip-mp-party") --> <!-- party <- rvest::html_text(party_elem) --> <!-- # Extract Link --> <!-- link <- rvest::html_attr(mp_tuile, "href") --> <!-- link <- paste0("https://www.noscommunes.ca", link) --> <!-- out <- tibble::tibble(name, party, link) --> <!-- return(out) --> <!-- ### .fancy[Exemple 2] .seventyfive[Résolution avec `purrr::map_dfr`] --> <!-- mp_tuiles <- rvest::html_elements(page, "[class='mip-mp-name']") --> <!-- # purrr::map_dfr applique extract_all_information_on_an_mp à chaque élément de mp_tuiles --> <!-- purrr::map_dfr(mp_tuiles, extract_all_information_on_an_mp) --> <!-- ### .fancy[Exemple 2] .seventyfive[Résolution avec `for()`] --> <!-- all_data <- tibble::tibble() --> <!-- for(mp_tuile in mp_tuiles){ --> <!-- new_tile <- extract_all_information_on_an_mp(mp_tuile) --> <!-- all_data <- dplyr::bind_rows(all_data, new_tile) --> <!-- all_data --> --- <center> <h3> .fancy[4 Nettoyer] .seventyfive[L'éternel recommencement] </h3> <img src = "images/sisyphe.png" style = "width = 15%"> </center> --- ### .fancy[Nettoyer] .seventyfive[L'éternel recommencement] Une fois extraite, il faut encore: + transformer les valeurs numériques en `numeric` -- + transformer les dates en `Date` -- + ajouter de nouvelles variables (notamment `lgl`) -- + enlever les espaces superflus des vecteurs `chr` -- Tout cela, à l'aide des suspects habituels: + [`{dplyr}`](https://dplyr.tidyverse.org/) pour manipuler les jeux de données + [`{stringr}`](https://stringr.tidyverse.org/) pour manipuler les vecteurs `chr` + [`{lubridate}`](https://lubridate.tidyverse.org/) pour manipuler les dates + [`{purrr}`](https://purrr.tidyverse.org/) pour manipuler/itérer sur les listes --- ### Pour résumer Un projet d'aspiration automatique s'articule autour de quatre axes: 1. .fancy[Naviguer] .seventyfive[collecter l'ensemble des adresses à visiter] -- 2. .fancy[Aspirer] .seventyfive[charger le code HTML formant la page brute] -- 3. .fancy[Extraire] .seventyfive[raffiner les données brutes] -- 4. .fancy[Nettoyer] .seventyfive[finaliser les données selon le format souhaité] --- <center> <h3> 9 conseils pour devenir une "gratteuse" professionnelle </h3> </center> <br> <center> <img src = "images/meme/me-call-advice.jpeg" style = "width = 50%"> </center> --- > 1 Utilisez SelectorGadget pour se familiariser avec les sélecteurs CSS Pour en apprendre plus [1](https://selectorgadget.com/) ; [2](https://rvest.tidyverse.org/articles/articles/selectorgadget.html) <center> <img src = "images/selector_exemple.png" style = "width = 100%"> </center> --- <br> > 2 Considérez les implications techniques et légales de l'aspiration automatique Les noms de domaines incluent un fichier `robots.txt` précisant les sous-domaines pouvant être aspirés automatiquement. + [{robotstxt}](https://cran.r-project.org/web/packages/robotstxt/vignettes/using_robotstxt.html) --- <br> > 2 Considérez les implications tecniques et légales de l'aspiration automatique Chaque requête HTTP exerce un poid technique sur le serveur, évitez de surcharger avec des requêtes rapides et réduisez le rythme de requête. ```r for(link in links){ aspire(link) # Ajouter une second de pause entre chaque aspiration Sys.sleep(1) } ``` --- > 3 Aspirez directement les tableaux avec `rvest::html_table` Les tableaux HTML (y compris ceux de Wikipédia) peuvent être aspirés directement. .panelset[ .panel[.panel-name[Germany] .details[ ```r "https://de.wikipedia.org/wiki/Bundestagswahl" %>% xml2::read_html() %>% rvest::html_table() %>% purrr::chuck(2) ``` ``` #> # A tibble: 20 × 11 #> Wahltag Wahlbeteiligung `CDU/CSU` SPD FDP Grüne1 Linke2 AfD DP #> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> #> 1 14. August 1… 78,5 31,0 29,2 11,9 — — — 4,0 #> 2 6. September… 86,0 45,2 28,8 09,5 — — — 3,3 #> 3 15. Septembe… 87,8 50,2 31,8 07,7 — — — 3,4 #> 4 17. Septembe… 87,7 45,3 36,2 12,8 — — — GDP … #> 5 19. Septembe… 86,8 47,6 39,3 09,5 — — — — #> 6 28. Septembe… 86,7 46,1 42,7 05,8 — — — — #> 7 19. November… 91,1 44,9 45,8 08,4 — — — — #> 8 3. Oktober 1… 90,7 48,6 42,6 07,9 b — — — #> 9 5. Oktober 1… 88,6 44,5 42,9 10,6 01,5 — — — #> 10 6. März 1983 89,1 48,8 38,2 07,0 05,6 — — — #> 11 25. Januar 1… 84,3 44,3 37,0 09,1 08,3 — — — #> 12 2. Dezember … 77,8 43,8 33,5 11,0 05,1 02,4 — — #> 13 16. Oktober … 79,0 41,4 36,4 06,9 07,3 04,4 — — #> 14 27. Septembe… 82,2 35,1 40,9 06,2 06,7 05,1 — — #> 15 22. Septembe… 79,1 38,5 38,5 07,4 08,6 04,0 — — #> 16 18. Septembe… 77,7 35,2 34,2 09,8 08,1 08,7 — — #> 17 27. Septembe… 70,9 33,8 23,0 14,6 10,7 11,9 — — #> 18 22. Septembe… 71,5 41,5 25,7 04,8 08,4 08,6 04,7 — #> 19 24. Septembe… 76,2 32,9 20,5 10,7 08,9 09,2 12,6 — #> 20 26. Septembe… 76,6 24,1 25,7 11,5 14,8 4,9 10,3 — #> # … with 2 more variables: GB/BHE3 <chr>, Sonstige4 <chr> ``` ] ] .panel[.panel-name[Canada] .details[ ```r "https://en.wikipedia.org/wiki/List_of_Canadian_federal_general_elections" %>% xml2::read_html() %>% rvest::html_table() %>% purrr::chuck(3) ``` ``` #> # A tibble: 44 × 15 #> No. Year Summary Government Government `Official oppos… `Official oppos… #> <chr> <int> <chr> <lgl> <chr> <lgl> <chr> #> 1 1st 1867 "Liberal… NA 100[2] NA 62 #> 2 2nd 1872 "Conserv… NA 100[3] NA 95 #> 3 3rd 1874 "Liberal… NA 129 NA 65[2] #> 4 4th 1878 "Conserv… NA 134[2] NA 63 #> 5 5th 1882 "Conserv… NA 134[4] NA 73 #> 6 6th 1887 "Conserv… NA 124[4] NA 80 #> 7 7th 1891 "Conserv… NA 118[4] NA 90 #> 8 8th 1896 "Liberal… NA 117 NA 86[2] #> 9 9th 1900 "Liberal… NA 128 NA 79[2] #> 10 10th 1904 "Liberal… NA 137 NA 75[2] #> # … with 34 more rows, and 8 more variables: Third party <chr>, #> # Third party <chr>, Fourth party <chr>, Fourth party <chr>, #> # Fifth party <chr>, Fifth party <chr>, Other <int>, Total seats <int> ``` ] ] ] --- <br> > 4 Apprenez à maîtriser les expressions régulières Les expréssions régulières (regex) sont un langage pour décrire des schémas de charactère. Par exemple, `\\d{2}\\s\\d{2}` correspond à deux chiffres, suivis d'un espace, suivi de deux chiffres. Si elles semblent parfois obscures et alétoires, elles permettent un nettoyage efficace et rapide de données textuelles. Pour en apprendre plus sur les regex [1](https://r4ds.hadley.nz/regular-expressions.html) ; [2](https://regenerativetoday.com/a-complete-beginners-guide-to-regular-expressions-in-r/) --- <br> > 5 Peaufinez vos compétences en sélecteurs CSS Les sélecteurs CSS sont souvent (comme ici) évoqués de façon superficielle. Ce sont, en réalité, des outils à la syntaxe très puissante. Pour en apprendre plus [1](https://moderncss.dev/guide-to-advanced-css-selectors-part-one/) ; [2](https://www.geeksforgeeks.org/advanced-selectors-in-css/) ; [3](https://www.w3schools.com/cssref/css_selectors.asp) ; [4](https://www.smashingmagazine.com/2009/08/taming-advanced-css-selectors/) ; [5](https://www.learnhowtoprogram.com/user-interfaces/ui-development-basics/advanced-selectors) --- <br> > 6 Si elles existent, utilisez les API officielles Une API (Application Programming Interface) permet la dissémination de données formatées pour les machines. <!-- Certaines plateformes proposent des API afin de mettre leurs données à disposition du public. --> <!-- Elles contrastent donc avec les sites web, qui disséminent les données dans un format lisible par les humains. --> + [academictwitteR](https://github.com/cjbarrie/academictwitteR): Twitter + [riingo](https://business-science.github.io/riingo/): Données boursières (Tiingo) + [tuber](https://github.com/soodoku/tuber): Youtube + [WikipediR](https://github.com/Ironholds/WikipediR/): Wikipédia + [RedditExtractor](https://github.com/ivan-rivera/RedditExtractor): Reddit --- > 7 Remontez le temps avec la [Wayback Machine](https://archive.org/web/) -- <img src = "images/ulaval.png" style = "height: 70%"> --- <br> > 8 Révélez les API cachées dans les site web grâce à la rétro-ingénierie des sites web Beaucoup de sites web conservent leurs données dans des bases de données accessibles via une API personnalisée. Ces API "officieuses" peuvent être exploitées afin d'extraire un grand nombre de données. Pour en apprendre plus [1](https://medium.com/@williamyeny/finding-ratemyprofessors-private-api-with-chrome-developer-tools-bd6747dd228d) ; [2](https://qxf2.com/blog/api-testing-developer-tools/) --- <br> > 9 Simulez de réels navigateurs web afin de dépasser les problèmes de JS Pour les plus acharnées souhaitant aspirer des pages impliquant des interactions complexes: + [Selenium](https://github.com/ropensci/RSelenium) + [Puppeteer](https://pypi.org/project/pyppeteer/) + [Playwright](https://playwright.dev/python/docs/intro)/[Playwrightr](https://github.com/benjaminguinaudeau/playwrightr) --- class: center, middle, inverse ### Merci pour votre attention! <br> ### et n'oubliez pas <center> <img src="images/meme/tumblr_d3b29849ea0cd256471650fb6fd9d43f_6ec229bc_540.jpeg" style = "width: 30%"> </center>