Skip to content

Commit

Permalink
Correction TP scraping (#435)
Browse files Browse the repository at this point in the history
* correction tp

* add to requirements

* webdriver

* Retire les exos

---------

Co-authored-by: linogaliana <lino.galiana@insee.fr>
  • Loading branch information
antoine-palazz and linogaliana committed Oct 16, 2023
1 parent 97676f5 commit fbbf066
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 197 deletions.
248 changes: 51 additions & 197 deletions content/manipulation/04a_webscraping_TP.qmd
Expand Up @@ -18,7 +18,6 @@ description: |
données à restructurer. Le web scraping, que les Canadiens nomment
_"moissonnage du web"_, est une manière de plus en plus utilisée de
récupérer une grande masse d'information en temps réel.
eval: false
image: featured_webscraping.jpg
---

Expand Down Expand Up @@ -301,12 +300,39 @@ de comprendre la structure d'une page web et dissocier les éléments exportable


```{python}
import urllib
!pip install -q lxml
import bs4
import lxml
import pandas
import urllib
from urllib import request
```

::: {.cell .markdown}
```{=html}
<div class="alert alert-info" role="alert">
<h3 class="alert-heading"><i class="fa-solid fa-comment"></i> Note</h3>
```
Pour être en mesure d'utiliser `Selenium`, il est nécessaire
de faire communiquer `Python` avec un navigateur _web_ (Firefox ou Chromium).
Le _package_ `webdriver-manager` permet de faire savoir à `Python`
se trouve ce navigateur s'il est déjà installé dans un chemin standard.
Pour l'installer, le code de la cellule ci-dessous peut être utilisé

```{=html}
</div>
```
:::

Pour faire fonctionner `Selenium`, il faut utiliser un package
nommé `webdriver-manager`:

```{python}
!pip install webdriver-manager
```

### Récupérer le contenu d'une page HTML

On va commencer doucement. Prenons une page _wikipedia_,
Expand Down Expand Up @@ -684,7 +710,7 @@ center = gdf[['latitude', 'longitude']].mean().values.tolist()
sw = gdf[['latitude', 'longitude']].min().values.tolist()
ne = gdf[['latitude', 'longitude']].max().values.tolist()
m = folium.Map(location = center, tiles='Stamen Toner')
m = folium.Map(location = center, tiles='openstreetmap')
# I can add marker one by one on the map
for i in range(0,len(gdf)):
Expand Down Expand Up @@ -1173,8 +1199,7 @@ navigateur Google Chrome minimaliste.
La version de [chromedriver](https://sites.google.com/a/chromium.org/chromedriver/)
doit être `>= 2.36` et dépend de la version de `Chrome` que vous avez sur votre environnement
de travail. Pour installer cette version minimaliste de `Chrome` sur un environnement
`Linux`, vous pouvez
vous référer à l'encadré dédié
`Linux`, vous pouvez vous référer à l'encadré dédié.


::: {.cell .markdown}
Expand All @@ -1194,6 +1219,8 @@ Sur `Colab`, vous pouvez utiliser les commandes suivantes :
!cp /usr/lib/chromium-browser/chromedriver /usr/bin
```

<br>

Si vous êtes sur le `SSP Cloud`, vous pouvez
exécuter les commandes suivantes :

Expand All @@ -1208,10 +1235,11 @@ import chromedriver_autoinstaller
chromedriver_autoinstaller.install()
```

<br>

Vous pouvez ensuite installer `Selenium`.
Par
exemple, depuis une
cellule de `Notebook`:
Par exemple, depuis une
cellule de `Notebook` :

```{python}
#| output: false
Expand All @@ -1226,21 +1254,20 @@ cellule de `Notebook`:
Après avoir installé `Chromium`,
il est nécessaire d'indiquer à `Python`
le trouver. Si vous êtes sur `Linux` et que vous
avez suivi les consignes précédentes, vous
pouvez faire:
avez suivi les consignes précédentes, vous pouvez faire :

```{python}
#| output: false
import sys
sys.path.insert(0,'/usr/lib/chromium-browser/chromedriver')
import selenium
path_to_web_driver = "chromedriver"
from webdriver_manager.chrome import ChromeDriverManager
path_to_web_driver = ChromeDriverManager().install()
```

En premier lieu, il convient d'initialiser le comportement
de `Selenium` en répliquant les paramètres
du navigateur. Pour cela, on va d'abord initialiser
notre navigateur avec quelques options:
notre navigateur avec quelques options :

```{python}
#| output: false
Expand All @@ -1252,16 +1279,11 @@ from selenium.webdriver.common.keys import Keys
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--no-sandbox')
#chrome_options.add_argument('--verbose')
```

```{python}
#| include: false
# specific pour usage dans docker container
chrome_options.add_argument('--disable-dev-shm-usage')
#chrome_options.add_argument('--verbose')
```

Puis on lance le navigateur:
Puis on lance le navigateur :

```{python}
#| output: false
Expand Down Expand Up @@ -1373,13 +1395,17 @@ from selenium.webdriver.common.keys import Keys
# on ouvre la page internet du jeu 2048
service = Service(executable_path=path_to_web_driver)
browser = webdriver.Chrome(service=service)
browser = webdriver.Chrome(service=service,
options=chrome_options)
browser.get('https://play2048.co//')
# Ce qu'on va faire : une boucle qui répète inlassablement la même chose : haut / droite / bas / gauche
# on commence par cliquer sur la page pour que les touches sachent
browser.find_element("class name", 'grid-container').click()
button = browser.find_element("class name", 'grid-container')
browser.execute_script("arguments[0].click();", button)
time.sleep(0.5)
grid = browser.find_element("tag name", 'body')
# pour savoir quels coups faire à quel moment, on crée un dictionnaire
Expand All @@ -1403,181 +1429,9 @@ print('Score final : {} en {} coups'.format(scoreElem.text, count))
browser.quit()
```

## Exercices supplémentaires

### Récupérer les noms et âges des ministres français

Pour cet exercice, on propose de scraper la liste des ministres français depuis le [site du gouvernement](https://www.gouvernement.fr/composition-du-gouvernement). L'objectif sera, _in fine_ de faire un graphique qui représente la distribution de leurs âges.
La solution pour cet exercice a été proposée
par [Tien-Thinh](https://github.com/tttienthinh)
et [Antoine Palazzolo](https://github.com/antoine-palazz).

Pour être en mesure de faire cet exercice, il est
recommandé d'installer le package `dateparser`

```{python}
#| output: false
!pip install dateparser
#depuis un notebook. En ligne de commande, retirer le !
```

Pour cet exercice, nous proposons d'utiliser les _packages_
suivants:

```{python}
import time
from tqdm import tqdm
import urllib
import re, datetime
from dateutil.parser import parse as parse_dt
import dateparser
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import bs4
```

Nous proposons également d'utiliser la fonction suivante
pour calculer l'âge à partir de la date de naissance.

```{python}
def from_birth_to_age(birth):
today = datetime.date.today()
return today.year - birth.year - ((today.month, today.day) < (birth.month, birth.day))
```


::: {.cell .markdown}
```{=html}
<div class="alert alert-success" role="alert">
<h3 class="alert-heading"><i class="fa-solid fa-pencil"></i> Exercice : Les ministres français </h3>
```


1. Créer des variables globales `url_gouvernement` et `url_gouvernement` qui représenteront,
respectivement, la racine de l'URL du site web et le chemin au sein de celui-ci ;
2. Utiliser `bs4` pour récupérer la composition du gouvernement, qui est contenue dans un `<div>`
ayant une classe _ad hoc_. Nommer cet objet `compo`
3. Utiliser `find_all` pour récupérer la liste des ministres dans `compo`. Nommer
cet objet `ministres`
4. Inspecter la structure des champs au sein de `ministres`. Répérer les id `biography`. Comme
la structure est générique, on va écrire une fonction `from_bio_to_age` sur laquelle on va itérer
pour chaque élément de la liste `ministres`. Cette fonction effectuera les opérations suivantes :
+ Remplacer les champs de dates de naissance non numériques (par exemple _"1er"_), en valeur numérique (par exemple 1).
+ Utiliser la regex `[0-3]?\d \S* \d{4}` avec le _package_ `re` pour extraire les dates
de naissance. Nommer l'objet `str_date`.
+ Appliquer `dateparser.parse` pour convertir sous forme de date
+ Appliquer `from_birth_to_age` pour transformer cette date de naissance en âge
5. Pour chaque élément de la liste `ministres`, faire une boucle (en introduisant un
`time.sleep(0.25)` entre chaque itération pour ne pas surcharger le site):
+ Récupérer les noms et prénoms, fonctions pour chaque ministre
+ Récupérer l'URL de la photo
+ Créer un URL pour chaque ministre afin d'appliquer la fonction
`from_bio_to_age`
6. Utiliser `matplotlib` ou `seaborn` pour faire un histogramme d'âge

```{=html}
</div>
```
:::

```{python}
#| echo: false
# 1/ Créer des variables globales
url_gouvernement = "https://www.gouvernement.fr"
suffixe_ministres = "/composition-du-gouvernement"
```

```{python}
#| echo: false
# 2/ Récupérer compo gouvernement
url = f"{url_gouvernement}/{suffixe_ministres}"
html = urllib.request.urlopen(url).read()
page = bs4.BeautifulSoup(html)
compo = page.find("div", {"class":"composition-du-gouvernement-contenu"}) # Nous n'avons besoin que de la composition
```

```{python}
#| echo: false
# 3/ Récupérer les ministres
ministres = compo.find_all("div", {"class":"ministre"})
```

A l'issue de la question 4, on devrait
retrouver les informations suivantes :

```{python}
print(f"Nous retrouvons ainsi {len(ministres)} ministres.")
```

```{python}
def from_bio_to_age(url):
html = urllib.request.urlopen(url).read()
page = bs4.BeautifulSoup(html)
s = page.find("div", {"id":"biography"}).text.replace("1er", "1") # un peu ad hoc
expression = re.compile("[0-3]?\d \S* \d{4}") # renvoie parfois des dates autres que dates de naissance
str_date = expression.findall(s)[0]
date_de_naissance = dateparser.parse(str_date).date()
return from_birth_to_age(date_de_naissance)
```

```{python}
#| include: false
liste = []
for ministre in tqdm(ministres):
prenom_nom = ministre.find("a", {"class":"ministre-nom"}).text
fonction = ministre.find("p", {"class":"ministre-fonction"}).text
photo = ministre.find("img")["src"]
href = url_gouvernement + ministre.find("a", {"class":"ministre-nom"})["href"]
try:
age = from_bio_to_age(href)
except:
age = np.NaN
liste.append({
'Nom complet': prenom_nom,
'Fonction': fonction,
'Photo': photo,
'href': href,
'Age': age
})
time.sleep(0.25) # Ne pas surcharger les requêtes
```

_In fine_, on obtient une liste dont le premier élément
prend la forme suivante :

```{python}
liste[0]
```

Finalement, le `DataFrame` pourra être
structuré sous la forme suivante. On va éliminer
les âges égaux à 0 sont qui sont des erreurs
de scraping:
lorsque la date de naissance complète n'est pas disponible
sur la biographie d'un ministre.

```{python}
df = pd.DataFrame(liste)
df = df.loc[df['Age'] != 0]
df.head(3)
```

Finalement, l'histogramme aura l'aspect suivant:

```{python}
plt.hist(df.Age, bins=np.arange(25, 80, 4))
```

### Analyse textuelle des commentaires clients de services de commande de repas en ligne
## Exercice supplémentaire

Pour découvrir une autre application possible du _web scraping_, vous pouvez également vous lancer dans le sujet 5 de l'édition 2023 du Funathon, disponible :
Pour découvrir une autre application possible du _web scraping_, vous pouvez également vous lancer dans le sujet 5 de l'édition 2023 d'un hackathon non compétitif organisé par l'Insee :

- Sur [`Github`](https://github.com/InseeFrLab/funathon2023_sujet5)
- Sur le [`SSPCloud`](https://www.sspcloud.fr/formation?search=funat&path=%5B%22Funathon%202023%22%5D)
Expand Down
4 changes: 4 additions & 0 deletions requirements.txt
Expand Up @@ -9,3 +9,7 @@ kaleido
yellowbrick
wordcloud
pywaffle
scikit-image
webdriver-manager
spacy
gensim

0 comments on commit fbbf066

Please sign in to comment.