Lise Vaudor / Sébastien Rey-Coyrehourcq / Fabien Pfaender
In two words
In more than two words …
rvest
: web scrapingdplyr
: table-wranglingstringr
: stringspurrr
: iterationHTML Document
Understanding html documents’ structure
Tools SelectorGadget or html inspector
Package rvest:
To collect the contents of a web page, one has to:
<html>
<style>
h1 {background-color: powderblue;}
.image {margin-left:50%;}
.comment{border-style:solid; background-color:LemonChiffon;}
.comment-author{font-style: italic;}
</style>
<h1> MA VIE A LA FERME </h1>
<div class="ingredients">
<b> INGREDIENTS</b>
<ul>
<li> >1 cochon(s) </li>
<li> >1 légume(s) </li>
</ul>
</div>
<div class="right"><div class="image"><img src='images/cochon.png'></div></div>
<p> Je fais de la bouillie pour mes petits cochons.</p>
<p> Pour un cochon, pour deux cochons, pour trois cochons, pour quatre, puis pour cinq, pour six, pour sept, pour huit, pour neuf, boeuf! </p>
<b>comments</b>
<div class="comment">Et pour une poule sur un mur qui picoterait du pain dur?
</div>
<div class="comment-author">Emma, 22 ans, Limoges</div>
<div class="comment">Je vois que vous êtes, telle la petite poule rousse, bien aimable. Avez-vous pu compter sur l'aide du chat et du canard pour semer vos 5 grains de blé?</div>
<div class="comment-author">Michel, 56 ans, Rennes</div>
</html>
<html>
<style>
h1 {background-color: powderblue;}
.image {margin-left:50%;}
.comment{border-style:solid; background-color:LemonChiffon;}
.comment-author{font-style: italic;}
</style>
<h1> MA VIE A LA FERME </h1>
<div class="ingredients">
<b> INGREDIENTS</b>
<ul>
<li> >1 cochon(s) </li>
<li> >1 légume(s) </li>
</ul>
</div>
<div class="right"><div class="image"><img src='images/cochon.png'></div></div>
<p> Je fais de la bouillie pour mes petits cochons.</p>
<p> Pour un cochon, pour deux cochons, pour trois cochons, pour quatre, puis pour cinq, pour six, pour sept, pour huit, pour neuf, boeuf! </p>
<b>comments</b>
<div class="comment">Et pour une poule sur un mur qui picoterait du pain dur?
</div>
<div class="comment-author">Emma, 22 ans, Limoges</div>
<div class="comment">Je vois que vous êtes, telle la petite poule rousse, bien aimable. Avez-vous pu compter sur l'aide du chat et du canard pour semer vos 5 grains de blé?</div>
<div class="comment-author">Michel, 56 ans, Rennes</div>
</html>
Read html page in R:
library(rvest)
html=read_html("data/blog_de_ginette.htm", encoding="UTF-8")
html
## {xml_document}
## <html>
## [1] <head>\n<meta http-equiv="Content-Type" content="text/html; charset= ...
## [2] <body>\n<h1> MA VIE A LA FERME </h1>\n<div class="ingredients">\n < ...
<html>
<style>
h1 {background-color: powderblue;}
.image {margin-left:50%;}
.comment{border-style:solid; background-color:LemonChiffon;}
.comment-author{font-style: italic;}
</style>
<h1> MA VIE A LA FERME </h1>
<div class="ingredients">
<b> INGREDIENTS</b>
<ul>
<li> >1 cochon(s) </li>
<li> >1 légume(s) </li>
</ul>
</div>
<div class="right"><div class="image"><img src='images/cochon.png'></div></div>
<p> Je fais de la bouillie pour mes petits cochons.</p>
<p> Pour un cochon, pour deux cochons, pour trois cochons, pour quatre, puis pour cinq, pour six, pour sept, pour huit, pour neuf, boeuf! </p>
<b>comments</b>
<div class="comment">Et pour une poule sur un mur qui picoterait du pain dur?
</div>
<div class="comment-author">Emma, 22 ans, Limoges</div>
<div class="comment">Je vois que vous êtes, telle la petite poule rousse, bien aimable. Avez-vous pu compter sur l'aide du chat et du canard pour semer vos 5 grains de blé?</div>
<div class="comment-author">Michel, 56 ans, Rennes</div>
</html>
Extract some elements (“nodes” or “nodesets”):
html_nodes(html,"b")
## {xml_nodeset (2)}
## [1] <b> INGREDIENTS</b>
## [2] <b>Commentaires</b>
html_nodes(html,".comment-author")
## {xml_nodeset (2)}
## [1] <div class="comment-author">Emma, 22 ans, Limoges</div>
## [2] <div class="comment-author">Michel, 56 ans, Rennes</div>
html_nodes(html,".ingredients") %>%
html_children()
## {xml_nodeset (2)}
## [1] <b> INGREDIENTS</b>
## [2] <ul>\n<li> >1 cochon(s) </li>\n <li> >1 légume(s) </li>\n ...
<html>
<style>
h1 {background-color: powderblue;}
.image {margin-left:50%;}
.comment{border-style:solid; background-color:LemonChiffon;}
.comment-author{font-style: italic;}
</style>
<h1> MA VIE A LA FERME </h1>
<div class="ingredients">
<b> INGREDIENTS</b>
<ul>
<li> >1 cochon(s) </li>
<li> >1 légume(s) </li>
</ul>
</div>
<div class="right"><div class="image"><img src='images/cochon.png'></div></div>
<p> Je fais de la bouillie pour mes petits cochons.</p>
<p> Pour un cochon, pour deux cochons, pour trois cochons, pour quatre, puis pour cinq, pour six, pour sept, pour huit, pour neuf, boeuf! </p>
<b>comments</b>
<div class="comment">Et pour une poule sur un mur qui picoterait du pain dur?
</div>
<div class="comment-author">Emma, 22 ans, Limoges</div>
<div class="comment">Je vois que vous êtes, telle la petite poule rousse, bien aimable. Avez-vous pu compter sur l'aide du chat et du canard pour semer vos 5 grains de blé?</div>
<div class="comment-author">Michel, 56 ans, Rennes</div>
</html>
Extract the type of nodes or nodesets:
html_nodes(html,".image") %>%
html_name()
## [1] "div"
<html>
<style>
h1 {background-color: powderblue;}
.image {margin-left:50%;}
.comment{border-style:solid; background-color:LemonChiffon;}
.comment-author{font-style: italic;}
</style>
<h1> MA VIE A LA FERME </h1>
<div class="ingredients">
<b> INGREDIENTS</b>
<ul>
<li> >1 cochon(s) </li>
<li> >1 légume(s) </li>
</ul>
</div>
<div class="right"><div class="image"><img src='images/cochon.png'></div></div>
<p> Je fais de la bouillie pour mes petits cochons.</p>
<p> Pour un cochon, pour deux cochons, pour trois cochons, pour quatre, puis pour cinq, pour six, pour sept, pour huit, pour neuf, boeuf! </p>
<b>comments</b>
<div class="comment">Et pour une poule sur un mur qui picoterait du pain dur?
</div>
<div class="comment-author">Emma, 22 ans, Limoges</div>
<div class="comment">Je vois que vous êtes, telle la petite poule rousse, bien aimable. Avez-vous pu compter sur l'aide du chat et du canard pour semer vos 5 grains de blé?</div>
<div class="comment-author">Michel, 56 ans, Rennes</div>
</html>
Extract the content of nodes or nodesets:
html_nodes(html,"b") %>%
html_text()
## [1] " INGREDIENTS" "Commentaires"
<html>
<style>
h1 {background-color: powderblue;}
.image {margin-left:50%;}
.comment{border-style:solid; background-color:LemonChiffon;}
.comment-author{font-style: italic;}
</style>
<h1> MA VIE A LA FERME </h1>
<div class="ingredients">
<b> INGREDIENTS</b>
<ul>
<li> >1 cochon(s) </li>
<li> >1 légume(s) </li>
</ul>
</div>
<div class="right"><div class="image"><img src='images/cochon.png'></div></div>
<p> Je fais de la bouillie pour mes petits cochons.</p>
<p> Pour un cochon, pour deux cochons, pour trois cochons, pour quatre, puis pour cinq, pour six, pour sept, pour huit, pour neuf, boeuf! </p>
<b>comments</b>
<div class="comment">Et pour une poule sur un mur qui picoterait du pain dur?
</div>
<div class="comment-author">Emma, 22 ans, Limoges</div>
<div class="comment">Je vois que vous êtes, telle la petite poule rousse, bien aimable. Avez-vous pu compter sur l'aide du chat et du canard pour semer vos 5 grains de blé?</div>
<div class="comment-author">Michel, 56 ans, Rennes</div>
</html>
Extract the attributes of nodes or nodesets:
html_nodes(html,"div") %>%
html_attrs()
## [[1]]
## class
## "ingredients"
##
## [[2]]
## class
## "right"
##
## [[3]]
## class
## "image"
##
## [[4]]
## class
## "comment"
##
## [[5]]
## class
## "comment-author"
##
## [[6]]
## class
## "comment"
##
## [[7]]
## class
## "comment-author"
Extract data and make it into a table:
page="data/blog_de_ginette.htm"
html=read_html(page, encoding="UTF-8")
texte=html %>% html_nodes(".comment") %>% html_text()
auteur=html %>% html_nodes(".comment-author") %>% html_text()
tib_comments=tibble(texte,auteur)
tib_comments
## # A tibble: 2 x 2
## texte auteur
## <chr> <chr>
## 1 Et pour une poule sur un mur qui picoterait du pain dur? c'e… Emma, 22 …
## 2 Je vois que vous êtes, telle la petite poule rousse, bien ai… Michel, 5…
It is actually a good idea to make all this into a fonction that would have the page’s url as intput and the tibble as output:
extract_comments=function(page){
html=read_html(page, encoding="UTF-8")
texte=html %>% html_nodes(".comment") %>% html_text()
auteur=html %>% html_nodes(".comment-author") %>% html_text()
tib_comments=tibble(doc=rep(page,length(texte)),
texte,
auteur)
return(tib_comments)
}
extract_comments("data/blog_de_ginette.htm")
## # A tibble: 2 x 3
## doc texte auteur
## <chr> <chr> <chr>
## 1 data/blog_de_ginette.htm Et pour une poule sur un mur qui pico… Emma, 2…
## 2 data/blog_de_ginette.htm Je vois que vous êtes, telle la petit… Michel,…
extract_comments("data/blog_de_jean-marc.htm")
## # A tibble: 6 x 3
## doc texte auteur
## <chr> <chr> <chr>
## 1 data/blog_de_jean-marc.htm Les thons, avec un t comme croc… "Eddie, 76 …
## 2 data/blog_de_jean-marc.htm Pourquoi ces thons ne préféraie… Yves, 40 an…
## 3 data/blog_de_jean-marc.htm Tout ça me fait penser au blog … Roberta, 18…
## 4 data/blog_de_jean-marc.htm Je préfère la chanson qui parle… Eduardo, 29…
## 5 data/blog_de_jean-marc.htm On ne comprend pas trop cette p… Lise, 35 an…
## 6 data/blog_de_jean-marc.htm Et pendant ce temps-là, le roi … Nadia, 43 a…
Now, let’s imagine that we actually have to deal with several pages with a common structure.
We would like to apply extract_comments()
iteratively to all these pages.
The purrr
package enables us to apply a function iteratively to all elements of a list or of a vector (… of course this is just a more straightforward way to loop through a for
structure…).
pages=c("data/blog_de_ginette.htm",
"data/blog_de_jean-marc.htm",
"data/blog_de_norbert.htm")
list_comments=map(pages, extract_comments)
list_comments
## [[1]]
## # A tibble: 2 x 3
## doc texte auteur
## <chr> <chr> <chr>
## 1 data/blog_de_ginette.htm Et pour une poule sur un mur qui pico… Emma, 2…
## 2 data/blog_de_ginette.htm Je vois que vous êtes, telle la petit… Michel,…
##
## [[2]]
## # A tibble: 6 x 3
## doc texte auteur
## <chr> <chr> <chr>
## 1 data/blog_de_jean-marc.htm Les thons, avec un t comme croc… "Eddie, 76 …
## 2 data/blog_de_jean-marc.htm Pourquoi ces thons ne préféraie… Yves, 40 an…
## 3 data/blog_de_jean-marc.htm Tout ça me fait penser au blog … Roberta, 18…
## 4 data/blog_de_jean-marc.htm Je préfère la chanson qui parle… Eduardo, 29…
## 5 data/blog_de_jean-marc.htm On ne comprend pas trop cette p… Lise, 35 an…
## 6 data/blog_de_jean-marc.htm Et pendant ce temps-là, le roi … Nadia, 43 a…
##
## [[3]]
## # A tibble: 4 x 3
## doc texte auteur
## <chr> <chr> <chr>
## 1 data/blog_de_norbert.htm "A quel moment faut-il claque… Jonas, 37 ans, …
## 2 data/blog_de_norbert.htm Norbert, mon petit chat, ça f… Julie, 34 ans, …
## 3 data/blog_de_norbert.htm L'ambiance doit être sympa qu… Mickaël, 23 ans…
## 4 data/blog_de_norbert.htm Vous devez avoir un grain pou… Viviane, 58 ans…
tibtot_comments <- list_comments %>%
bind_rows()
tibtot_comments
## # A tibble: 12 x 3
## doc texte auteur
## <chr> <chr> <chr>
## 1 data/blog_de_ginette.htm Et pour une poule sur un mur qui… Emma, 22 …
## 2 data/blog_de_ginette.htm Je vois que vous êtes, telle la … Michel, 5…
## 3 data/blog_de_jean-marc.htm Les thons, avec un t comme croco… "Eddie, 7…
## 4 data/blog_de_jean-marc.htm Pourquoi ces thons ne préféraien… Yves, 40 …
## 5 data/blog_de_jean-marc.htm Tout ça me fait penser au blog d… Roberta, …
## 6 data/blog_de_jean-marc.htm Je préfère la chanson qui parle … Eduardo, …
## 7 data/blog_de_jean-marc.htm On ne comprend pas trop cette pa… Lise, 35 …
## 8 data/blog_de_jean-marc.htm Et pendant ce temps-là, le roi d… Nadia, 43…
## 9 data/blog_de_norbert.htm "A quel moment faut-il claquer d… Jonas, 37…
## 10 data/blog_de_norbert.htm Norbert, mon petit chat, ça fait… Julie, 34…
## 11 data/blog_de_norbert.htm L'ambiance doit être sympa quand… Mickaël, …
## 12 data/blog_de_norbert.htm Vous devez avoir un grain pour é… Viviane, …
str_c() to combine strings
str_c("abra","ca","dabra")
## [1] "abracadabra"
str_c("Les jeux","de mots laids","sont pour","les gens bêtes", sep=" ")
## [1] "Les jeux de mots laids sont pour les gens bêtes"
str_detect() detects a pattern
str_detect(c("Quarante","carottes","crues",
"croient","que","croquer",
"crée","des","crampes."),
pattern="cr")
## [1] FALSE FALSE TRUE TRUE FALSE TRUE TRUE FALSE TRUE
str_replace() replaces a pattern with another
str_replace(c("All we hear is",
"Radio ga ga",
"Radio goo goo",
"Radio ga ga"),
pattern="goo",
replacement="ga")
## [1] "All we hear is" "Radio ga ga" "Radio ga goo" "Radio ga ga"
str_extract() extracts the pattern (if it’s there!)
str_extract(c("L'âne","Trotro","trotte","à une allure","traitreusement","tranquille"),
pattern="tr")
## [1] NA "tr" "tr" NA "tr" "tr"
Regular expressions are used to define patterns through rules of construction.A tutorial here
.
A character class corresponds to the notation [...]
.
For instance, to detect all voyels in the string:
str_view_all("youp la boum",
"[aeiou]")
See the difference:
str_view_all("A132-f445-e34-C308-M9-E18",
"[308]")
str_view_all("A132-f445-e34-C308-M9-E18",
"308")
Any character can be noted .
.
For instance, the pattern “any character followed by a letter” can be searched through
str_view_all("32a-B44-552-98eEf",
".[a-z]")
To find dots, question marks, exclamation marks:
str_view_all(c("Allô, John-John? Ici Joe la frite. Surprise!"),
"[\\.\\?\\!]")
Note that we don’t write "[.?!]"
, but "[\\.\\?\\!]"
.
.
(as we saw before), as well as ?
and !
are special characters. So, to point out actual dots or interrogation/exclamation marks, one has to use the escape character \
. The regular expression hence becomes [\.\?\!]
…
But it does not end here… as it is not directly the regular expression that is passed to the function, but a string that is interpreted as a regular expression. Thus each escape character \
has to be escaped through a \
. So that the pattern passed to the function is actually "[\\.\\?\\!]"
.
A character class can be defined as all characters excluding the ones listed. This is noted as [^...]
:
For instance, all characters that are neither a vowel nor a blank space:
str_view_all("turlututu chapeau pointu",
"[^aeiou ]")
Ranges of characters are noted [...-...]
For instance, all numbers between 1 and 5:
str_view_all(c("3 petits cochons", "101 dalmations", "7 nains"),
"[1-5]")
… or all those between A-F or a-e:
str_view_all("A132-f445-e34-C308-M2244-Z449-E18",
"[A-Fa-e]")
Some character classes are predefined for instance digits, punctuation characters, lower-case alphabetical characters, etc.
Quantifiers are used to specify how many consecutive times a particular class or group occurs.
zero or one: the pattern of interest is followed by ?
.
str_view_all(c("file1990-fileB1990-fileAbis2005"),
"file\\d?")
zero or more: O the pattern of interest is followed by *
.
str_view_all(c("file1990-fileB1990-fileAbis2005"),
"file\\d*")
one or more : the pattern of interest is followed by +
.
str_view_all(c("file1990-fileB1990-fileAbis2005"),
"file\\d+")
XPATH is another way to query the DOM, it’s very powerfull, but also a little complex. Use some cheatsheet to remember principal operators.
Some examples :
html_nodes(html, xpath = "//div[@class='ingredients']//ul//li")
## {xml_nodeset (2)}
## [1] <li> >1 cochon(s) </li>
## [2] <li> >1 légume(s) </li>
html_nodes(html, xpath = "//text()[contains(.,'cochons') and not(contains(.,'poule'))]")
## {xml_nodeset (2)}
## [1] Je fais de la bouillie pour mes petits cochons.
## [2] Pour un cochon, pour deux cochons, pour trois cochons, pour quatre, ...
html_nodes(html, xpath = "//div[@class='comment']")
## {xml_nodeset (2)}
## [1] <div class="comment">Et pour une poule sur un mur qui picoterait du ...
## [2] <div class="comment">Je vois que vous êtes, telle la petite poule ro ...
How to defend from webscraping :
Legend : { : cost, difficulty to implement } , { : dificulty to bypass } , { or : user happiness }
How to attack :
One rule : If you see it on your browser, so you can get it
Basics :
Advanced :
projet_leboncoin.Rproj
or directly the html page online
scrap_flightradar.Rmd
or directly the html page online!Warning! You need Docker installed