📤 Webscrapping et Client HTTP.

Le web scraping désigne les techniques d’extraction du contenu des sites internet. C’est une pratique que l’on envisage lorsque l’on a accès a des données utiles publiquement mais qu’il ne nous est pas fourni de fichiers produits ou d’API pour les traiter.

Elle s’appuie donc sur l’utilisation de client HTTP pour traiter des données non formattées pour l’utilisation, typiquement des pages web mises a disposition aux utilisateurs au format HTML.

Le terme fait généralement référence à l’usage de bots pour collecter ces contenus automatiquement.

Le webscraping peut s’apprécier au travers de processus de type ETL (Extract Transform Load) :

  • Extract : on réalise l’extraction des données brutes a partir de pages web.
  • Transform : on effectue un traitement a partir des données brutes pour les rendre exploitables. parsing
  • Load : on les sauvegarde dans un fichier, une base de données ou autre.

Le plan de ce cours s’articulera selon ces parties avec une partie sur la persistence en 3ème partie de ce cours.

Comme pour les autres parties, les exemples sont disponibles ici : https://github.com/conception-logicielle-ensai/exemples-cours/tree/main/cours-5/webscrapping

Exemples d’utilisation

  • Effectuer des suivi de prix : Marché / Bourse / Site d’annonces / Comparateur de prix - C’est le cas par exemple a l’INSEE dans la section IPC.
  • Récupération de données textuelles massives : commentaires, tweets, .. - lecture des avis googles pour
  • Récupération de listes de contact : Annuaires, pages jaunes, pages blanches - Base de sondage entreprises via pages jaunes
  • Aggrégation de statistiques déjà calculées : google trends, ..

🩶 Webscrapping et légalité : existence d’une zone grise

Le webscraping est l’une des pratiques les plus courantes pour la récupération de données sur le web. Il peut être utilisé pour récupérer des données publiquement accessibles, mais ces données sont parfois protégées par le droit d'auteur ou pour les données personnelles. C’est ce qui explique qu’il y a là une zone grise.

Droit des données :

Il est donc préférable d’utiliser des APIs au maximum pour la récupération de données lorsque celles-ci sont disponibles.

🌑 Récupération des données : Client HTTP

Les clients http sont nécessaires pour la récupération des données exposées sur les sites. Il en existe de différents types.

Client en ligne de commande

Les outils comme Wget et cURL permettent d’envoyer des requêtes HTTP directement depuis le terminal.

  • cURL : Supporte plusieurs protocoles (HTTP, FTP, etc.), permet d’envoyer des requêtes GET, POST, etc.
  • Wget : Principalement utilisé pour télécharger des fichiers depuis le web.

Ils permettent de scripter des requêtes et sont disponibles sur la plupart des OS.

Remarque : Nous vous avons présenté curl dans la session précédente. Il est très utile puisqu’il est commun d’échanger des commandes curl entre des équipes de développeur (DEV) ou des équipes de maintenance d’infrastructure (OPS)

Clients utilitaires

Il existe des clients utilitaires comme Insomnia et Postman offrent une interface graphique pour tester des API REST.

  • Postman : Permet d’envoyer des requêtes HTTP, d’automatiser des tests et de documenter des API.
  • Insomnia : Similaire à Postman, axé sur la simplicité et l’expérience utilisateur.
Pour aller plus loin

Lien vers les sites pour télécharger/get started avec ces clients http : Postman et Insomnia

Ils peuvent être très pratiques si vous travaillez avec des collègues ne maitrisant pas python puisque vous pouvez leur partager vos scripts.

Les navigateurs sont des clients HTTP très adaptés pour effectuer des requêtes HTTP. Ils proposent par ailleurs une interface de dévtools directement incorporée.

Ils proposent :

  • Un onglet Réseau : Cela permet de traquer les requêtes HTTP effectuées par le navigateur. Cela peut être utile pour les stocker ou les reproduire via script.
  • Un onglet debug: interface debug côté client (nos pages executant du javascript cela permet de comprendre un bug d’affichage par ex)
  • Une console: elle permet d’executer du javascript sur la page
  • Un inspecteur : permet de scanner les éléments et de récupérer des informations sur celles ci.

…Et d’autres fonctionnalités..

Consultons ensemble cette documentation :

Pour aller plus loin

Lien vers une vidéo présentant les outils de développements chrome : https://www.youtube.com/watch?v=BrsyIyYSP1c

Utilisation de requests

Pour effectuer des requêtes http on utilisera a nouveau la librairie client http requests.

Les requêtes seront cette fois effectuées pour la récupération de pages web, pour récupérer des fichiers HTML bruts, et donc on privilégiera la récupération du text dans les réponses :

import requests

URL_COURS = "https://conception-logicielle.abrunetti.fr/"
response = requests.get(url)
print(response.text)  # Affiche le contenu HTML de la page

Utilisation d’un script dans une page HTML (Javascript)

En javascript il existe de nombreux client HTTP, nous privilégierons l’usage d’axios en seconde partie du cours, mais le client http “de base” est fetch :

Observez plutôt en récupérant le code source de la page d’accueil du cours :

Code source html / js avec la librairie fetch

<div id="fetch-with-fetch">
<button id="fetch-btn" style="display: inline-block; padding: 10px 20px; background-color: #007bff; color: white; font-size: 16px; border-radius: 5px; cursor: pointer; text-align: center; font-family: Arial, sans-serif;" onclick="fetchPageFetch()">Charger la page avec le package fetch</button>
<div id="fetch-content" style="white-space: pre-wrap; border: 1px solid #000; padding: 10px; margin-top: 10px;"></div>

<script>
async function fetchPageFetch() {
    const url = "https://conception-logicielle.abrunetti.fr/";
    try {
        const response = await fetch(url);
        if (!response.ok) {
            throw new Error(`Erreur: ${response.status}`);
        }
        const text = await response.text();
        document.getElementById("fetch-content").textContent = text;
    } catch (error) {
        document.getElementById("fetch-content").textContent = "Erreur lors du chargement: " + error.message;
    }
}
</script>
</div>

Pour une meilleure gestion des client http, plutôt que de devoir reparamétrer les urls, on préconise un usage d’axios dans ce cours, voici un exemple sur une page statique.

Code source html / js avec la librairie axios

<div id="axios">
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <!-- Import Axios -->

<button id="axios-btn" style="display: inline-block; padding: 10px 20px; background-color: #007bff; color: white; font-size: 16px; border-radius: 5px; cursor: pointer; text-align: center; font-family: Arial, sans-serif;" onclick="fetchPageAxios()">Charger la page avec le package axios</button>


<script>
        async function fetchPageAxios() {
            const url = "https://conception-logicielle.abrunetti.fr/";
            try {
                const response = await axios.get(url);
                document.getElementById("axios-content").textContent = response.data;
            } catch (error) {
                document.getElementById("axios-content").textContent = "Erreur lors du chargement: " + error.message;
            }
        }
</script>
<div id="axios-content" style="white-space: pre-wrap; border: 1px solid #000; padding: 10px; margin-top: 10px;"></div>
</div>

Remarque, c’est exactement la même base de code pour la récupération de données depuis une API, ce qui d’ailleurs est plus fréquent en JS.

🕷 Web Crawling: exemple avec Selenium

Le web crawling est un autre mode de webscraping, l’objectif ici est d’utiliser un site web et de le parcourir comme un utilisateur a l’aide d’un script. Cela peut être très utile lorsque vous désirez extraire des données d’un site qui nécessite une authentification, ou que vous désirez réaliser des scénarios plus complexes d’extractions.

Cela est également très utile pour tester les fonctionnalités côté Frontend, a partir d’une url.

Selenium est un outil qui permet d’executer des actions scriptées comme le parcours de pages sur des interfaces web, il est très utilisé et possède une intégration dans différents languages : java, python, javascript,..

Fonctionnellement, Selenium s’appuie sur les driver de navigateur pour lancer en tâche de fond ou en interactif un navigateur a partir duquel il va pouvoir interagir avec les pages web.

Pour l’installer il faut donc :

  • l’installer avec pip : pip3 install selenium
  • Installer un webdriver : Soit firefox, soit chrome

Pour les exemples, on utilise gecko, le webdriver de firefox

Les cas d’utilisation de sélénium est le parcours de page pour récupérer des informations :

Manipulation des données

Une fois les pages récupérées, il faut les traiter pour en récupérer des données exploitables pour des traitements internes a nos applicatifs.

Par exemple, on peut vouloir aggréger les indicateurs récupérés ou préparer des données textuelles pour ensuite pouvoir entrainer un modèle sur ces données.

🔎 Regex, Expressions régulières : Isoler et capturer des données textuelles

Les expressions régulières, ou regex pour faire court, sont des motifs que vous pouvez utiliser pour rechercher du texte dans une chaîne de caractères. On parle assez souvent de pattern matching. On va donc ici élaborer des patterns pour récupérer les ensembles cohérents de chaine de caractères qui respectent ce pattern.

Quel intêret ? On va pouvoir dans nos traitements identifier et récupérer des données a l’intérieur de balises par exemple, ou pour rechercher des occurences de certains mots dans des grandes chaines de texte.

Python prend en charge les expressions régulières grâce au module re déjà présent dans sa bibliothèque standard.

C’est un concept qui est présent dans la plupart des languages. Il est donc réutilisable lors de problématiques de traitement de données au format str

Syntaxe

AncresDescription
^Début de ligne. Correspond au début d’une chaîne de caractères.
$Fin de ligne. Correspond à la fin d’une chaîne de caractères.
\bLimite de mot. Correspond à la position entre un caractère de mot (\w) et un caractère qui n’est pas un caractère de mot.
Symbole spécialDescription
.Correspond à n’importe quel caractère, sauf un saut de ligne.
*Correspond à zéro ou plusieurs occurrences du caractère précédent.
\dCorrespond à un chiffre. Équivalent à [0-9].
\DCorrespond à tout caractère qui n’est pas un chiffre. Équivalent à [^0-9].
\wCorrespond à un caractère alphanumérique (lettres, chiffres, souligné). Équivalent à [a-zA-Z0-9_].
\WCorrespond à tout caractère qui n’est pas alphanumérique. Équivalent à [^a-zA-Z0-9_].
\sCorrespond à un caractère d’espacement (espace, tabulation, retour à la ligne).
\SCorrespond à tout caractère qui n’est pas un caractère d’espacement.

Exemples d’utilisation :

  • ^abc : Correspond à la chaîne “abc” au début de la ligne.
  • xyz$ : Correspond à la chaîne “xyz” à la fin de la ligne.
  • \d{3} : Correspond à trois chiffres consécutifs.
  • \w+ : Correspond à un ou plusieurs caractères alphanumériques.
  • ^toto.* récupère toute la ligne si elle contient toto au début

Groupes de Capture :

Les groupes de capture sont utilisés pour capturer une partie spécifique d’une correspondance d’expression régulière. Ils sont délimités par des parenthèses.

Expression régulièreDescription
(.*)Capture toute la chaine
<li>(.*?)</li>Capture tout ce qui est entre la balise li

On peut ensuite utiliser les groupes de capture avec \1 ou \0

Exemple pour récupérer le nombre de parties du cours envoyées sur le site du cours:

import re
def extract_with_regex(html):
    """
    Recuperation de toutes les données qui sont dans les balises summary a l'aide du groupe de capture
    """
    pattern = r"<summary>(.*?)</summary>"
    matches = re.findall(pattern, html, re.DOTALL)
    return [match.strip() for match in matches]

parties = extract_with_regex(html)
print(parties)
# ['💬 A propos', 
#   '🗂️ Projets', 
# 'Cours 1 - architecture de base et évolution',
#  'Cours 2 - qualification et bonnes pratiques', 
#  'Cours 3 - configuration et portabilité',
#  'Cours 4 - API webservice et développement web',
#  'Cours 5 - Interfaces Homme-Machine et données',
#  'Cours 6 - Déploiement et hébergement applicatifs'] 
Ce qu'il faut désormais limiter

Fonctions natives de str

La récupération des données issues d’un site au format html est possible par différents outils de type client HTTP. Ces données ne sont pas exploitables telles quelles, elle nécessitent a minima un retraitement par rapport a tous les éléments d’affichage inutiles pour l’exploitation des données.

Ce retraitement peut se fait de manière manuelle dans les str, avec les fonctions find, des boucles .. Mais cela n’est pas efficient et n’est pas adapté a des changements de casse dans le site (saut de ligne, espace, ajout d’une classe sur les summary..)

Exemple pour récupérer le nombre de parties du cours envoyées sur le site du cours:

def recuperation_partie_summary(ligne):
    """
    Recupère l'intérieur de la partie summary pour le html qui contient le nom de la partie
    """
    if "<summary>" in ligne:
            part = ligne.strip().replace("<summary>", "").replace("</summary>", "")
            return part
    return None
def extraction_grandes_parties_raw(html:str):
    """
    Fonction qui extrait les sous parties de la page dans la balise navigation
    Développée sans fonctionnalités de parsing
    """
    balise_contenant_parties = "<nav role=\"navigation\">"
    balise_finissant_parties= "</nav>"
    
    start_idx = html.find(balise_contenant_parties)
    end_idx = html.find(balise_finissant_parties, start_idx)
    
    if start_idx == -1 or end_idx == -1:
        return []
    
    interieur_navigation = html[start_idx:end_idx]
    parts = []
    
    for line in interieur_navigation.split("\n"):
        partie = recuperation_partie_summary(line)
        if partie != None:
             parts.append(partie)
    
    return parts


extraction_grandes_parties_raw(html)
# ['💬 A propos', 
#   '🗂️ Projets', 
# 'Cours 1 - architecture de base et évolution',
#  'Cours 2 - qualification et bonnes pratiques', 
#  'Cours 3 - configuration et portabilité',
#  'Cours 4 - API webservice et développement web',
#  'Cours 5 - Interfaces Homme-Machine et données',
#  'Cours 6 - Déploiement et hébergement applicatifs'] 
Pour aller plus loin
Un jeu qui vous permet d'apprendre les regex et de pratiquer : https://regexcrossword.com/

🔪 Parsing - Parser Html avec beautiful soup

Beautiful Soup permet d’encapsuler l’arborescence des éléments html et xml dans un objet afin de pouvoir assez facilement le parcourir.

C’est une librairie externe on doit l’installer avec pip : pip3 install beautifulsoup4

from bs4 import BeautifulSoup
import requests

url = "https://conception-logicielle.abrunetti.fr/cours-2024/"
res = requests.get(url)
html = res.text
soup = BeautifulSoup(html, "html.parser")

Il se base sur les selecteurs css pour la récupération de données :

Exemple on souhaite récupérer tous les titres <h2> à l’intérieur d’un <div class="content">

Cela donne en python:

soup.select("div.content h2")

On peut également récupérer en cherchant dans la structure:

La balise <a> representant un lien dans la page

# Oneliner avec l'operateur list comprehension en python
links = [a["href"] for a in soup.find_all("a", href=True)]

Ou chercher par des attributs sur l’element HTML:

soup.find("div", class_="article-body")
Exemple : récupération des grandes parties du cours

def extract_parties_avec_beautifulsoup(html):
    soup = BeautifulSoup(html, "html.parser")
    return [summary.text.strip() for summary in soup.find_all("summary")]

parties = extract_parties_avec_beautifulsoup(html)
print(parties)
# ['💬 A propos', 
#   '🗂️ Projets', 
# 'Cours 1 - architecture de base et évolution',
#  'Cours 2 - qualification et bonnes pratiques', 
#  'Cours 3 - configuration et portabilité',
#  'Cours 4 - API webservice et développement web',
#  'Cours 5 - Interfaces Homme-Machine et données',
#  'Cours 6 - Déploiement et hébergement applicatifs'] 

Cela permet au global un meilleur parsing et une meilleure stabilité dans le traitement des fichiers html, c’est ce que l’on privilégiera pour le webscraping

Pour aller plus loin
Doc officielle : https://www.crummy.com/software/BeautifulSoup/bs4/doc/
Pour aller plus loin
Un autre outil très complet pour des projets python : scrapy

🔐 Protection contre le webscrapping : Humaniser nos processus.

La récupération de données en utilisant du webscrapping peut s’assimiler à une attaque informatique de type DDOS (Denial Of Service).

Ainsi certains sites ont mis en oeuvre des solutions pour se protéger contre le DDOS et le webscraping abusif. Ces solutions reposent sur les principes suivants :

  • Les sites bloquent des requêtes répétées sur des intervalles de temps trop proches venant d’une même IP (~même machine)
  • Les sites modifient contenu des balises html pour empêcher l’automatisation
  • Création de Honeypot 🍯 : liens invisibles que seul un bot “cliquerait” pour bloquer les bots/botteurs.
  • Authentification exigée au bout d’un certain nombre d’usages.

Ils proposent donc différentes guidelines générales pour les utilisateurs qui souhaitent webscraper :

  • Les sites précisent ce qu’ils permettent aux robots sur un endpoint particulier le endpoint robots.txt, exemple : https://www.google.com/robots.txt
  • Il faut en général essayer d’espacer un minimum les requêtes, il y a en général des couches réseau “Anti DDOS” qui bloquent les requêtes venant d’une même IP dans des intervalles de temps ressérrés.
  • Eviter d’effectuer des requêtes dans des périodes d’usage intensif des services récupérés. Par exemple pour une administration française, privilégier d’effectuer nos commandes de webscraping pendant la nuit via la planification du traitement.

Ressources externes (pour aller plus loin)