Extraction de données web : classement universitaire, comparaison de prix et téléchargement d'images

Exercice 1 : Scraping du clsasement académique chinois 2020

L'objectif consiste à extraire les données du classement ShanghaiRanking pour l'année 2020 en exploitant les bibliothèques requests et BeautifulSoup.

Impléation complète

import requests
from bs4 import BeautifulSoup

adresse = "http://www.shanghairanking.cn/rankings/bcur/2020"

requete = requests.get(adresse)
requete.encoding = "utf-8"

analyseur = BeautifulSoup(requete.text, "html.parser")
lignes = analyseur.select(".rk-table tbody tr")

print("Rang\tÉtablissement\tProvince\tCatégorie\tScore")

for ligne in lignes:
    position = ligne.select_one("td:nth-child(1)").text.strip()
    etablissement = ligne.select_one("td:nth-child(2)").text.strip().replace("\n", "").replace("  ", "")
    region = ligne.select_one("td:nth-child(3)").text.strip()
    categorie = ligne.select_one("td:nth-child(4)").text.strip()
    points = ligne.select_one("td:nth-child(5)").text.strip()
    
    print(f"{position}\t{etablissement}\t{region}\t{categorie}\t{points}")

Mécanismes essentiels

La requête HTTP initialise la connexion avec le serveur. L'attribution explicite de l'encodage UTF-8 garantit la lisibilité des caractères chinois. Le sélecteur CSS .rk-table tbody tr isole les rangées pertinentes du tableau, tandis que select_one avec le pseudo-sélecteur nth-child extrait chaque cellule selon son indice positionnel. Un nettoyage textuel élimine les artefacts de formatage.


Exercice 2 : Crawler orienté prix pour produits e-commerce

Ce script récupère les articles correspondant au mot-clé « 书包 » (sac à dos) sur une plateforme de comparaison de prix, avec navigation paginée.

Code source

import requests
import re
import time


def extraire_produits():
    domaine = "https://www.meidebi.com"
    critere = "书包"
    pages_max = 5
    
    entetes = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8'
    }

    try:
        verification = requests.get(domaine, headers=entetes, timeout=10)
        print("Connexion au site validée.")
    except requests.RequestException as err:
        print(f"Échec de connexion: {err}")
        return

    articles = []
    indice_page = 1

    while indice_page <= pages_max:
        url_page = f"{domaine}/Search?keyword={critere}&p={indice_page}"
        print(f"Récupération page {indice_page}...")

        try:
            reponse = requests.get(url_page, headers=entetes, timeout=10)
            reponse.raise_for_status()
            reponse.encoding = reponse.apparent_encoding
            contenu = reponse.text
        except requests.RequestException as err:
            print(f"Erreur page {indice_page}: {err}")
            break

        motif = re.compile(
            r'<li class="clearfix">.*?'
            r'<span class="red price">\s*¥\s*([\d\.]+)\s*</span>.*?'
            r'<h6><a href=".*?" target="_blank">(.*?)</a></h6>',
            re.DOTALL
        )
        correspondances = motif.findall(contenu)

        if not correspondances:
            print(f"Aucun produit détecté page {indice_page}, arrêt.")
            break
            
        articles.extend(correspondances)

        if 'class="next' not in contenu:
            print("Dernière page atteinte.")
            break

        indice_page += 1
        time.sleep(1)

    print("\n" + "=" * 50)
    print("RÉSULTATS")
    print("=" * 50)
    print("N°\tPrix\t\tArticle")
    print("-" * 50)
    
    for numero, (montant, designation) in enumerate(articles, start=1):
        print(f"{numero}\t¥{montant}\t{designation.strip()}")


if __name__ == "__main__":
    extraire_produits()

Points techniques

Le pré-test de connectivité évite les traitemants inutiles en cas d'indisponibilité du serveur. L'expression régulière exploite re.DOTALL pour capturer des motifs multilignes. La temporisation de une seconde entre requêtes respecte la politique de courtoisie du web scraping.


Exercice 3 : Téléchargement d'images depuis un portail universitaire

Ce programme parcourt six pages du site d'actualités de l'Université Fuzhou pour archiver les images au format .jpg, .jpeg ou .png.

Solution complète

import requests
from bs4 import BeautifulSoup
import os
import time
from urllib.parse import urljoin

urls_cibles = [
    "https://news.fzu.edu.cn/yxfd.htm",
    "https://news.fzu.edu.cn/yxfd/1.htm",
    "https://news.fzu.edu.cn/yxfd/2.htm",
    "https://news.fzu.edu.cn/yxfd/3.htm",
    "https://news.fzu.edu.cn/yxfd/4.htm",
    "https://news.fzu.edu.cn/yxfd/5.htm"
]

dossier_destination = "images_fzu"
compteur_images = 0

if not os.path.exists(dossier_destination):
    os.makedirs(dossier_destination)
    print(f"Dossier créé: {dossier_destination}")


def telecharger_image(url_image, numero_page):
    global compteur_images
    
    try:
        entetes = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/128.0.0.0 Safari/537.36"
        }
        
        flux = requests.get(url_image, headers=entetes, timeout=15)
        flux.raise_for_status()

        nom_fichier = url_image.split("/")[-1]
        if "." not in nom_fichier:
            nom_fichier += ".jpg"
            
        nom_final = f"page_{numero_page}_{compteur_images + 1}_{nom_fichier}"
        chemin_sauvegarde = os.path.join(dossier_destination, nom_final)

        with open(chemin_sauvegarde, "wb") as fichier:
            fichier.write(flux.content)

        compteur_images += 1
        print(f"[Page {numero_page}] Téléchargée: {nom_final}")
        return True
        
    except Exception as erreur:
        print(f"[Page {numero_page}] Échec: {url_image} — {erreur}")
        return False


def analyser_page(url_page, numero_page):
    print(f"\n--- Analyse page {numero_page}: {url_page} ---")
    
    try:
        entetes = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/128.0.0.0 Safari/537.36"
        }
        
        reponse = requests.get(url_page, headers=entetes, timeout=15)
        reponse.encoding = reponse.apparent_encoding

        soup = BeautifulSoup(reponse.text, "html.parser")
        conteneurs = soup.find_all("div", class_="img slow")

        if not conteneurs:
            print(f"Aucune image repérée page {numero_page}")
            return

        for bloc in conteneurs:
            balise_img = bloc.find("img")
            if balise_img and balise_img.get("src"):
                url_complete = urljoin(url_page, balise_img["src"])
                telecharger_image(url_complete, numero_page)
                time.sleep(1)

        print(f"Page {numero_page} terminée")
        
    except Exception as erreur:
        print(f"Erreur page {numero_page}: {erreur}")


if __name__ == "__main__":
    print("Démarrage du crawler d'images FZU")
    
    for position, url in enumerate(urls_cibles):
        page = position + 1
        analyser_page(url, page)
        
        if position < len(urls_cibles) - 1:
            print("Pause de 2 secondes...")
            time.sleep(2)

    print(f"\nOpération terminée: {compteur_images} images archivées")
    print(f"Emplacement: {os.path.abspath(dossier_destination)}")

Architecture du script

La fonction urljoin résout les chemins relatifs en URLs absolues, indispensable pour le téléchargement. La classe CSS img slow sert d'ancre sémantique pour localsier les conteneurs d'images. Le stockage en mode binaire ("wb") préserve l'intégrité des fichiers graphiques. Les temporisations escaladées (1 seconde entre images, 2 secondes entre pages) minimisent l'empreinte du crawler.

Étiquettes: web-scraping Python BeautifulSoup requests regex

Publié le 27 septembre à 00h31