Le web scraping permet d'extraire automatiquement des données depuis des sites web. Python offre d'excellents outils : requests pour récupérer les pages, BeautifulSoup pour parser le HTML, et Selenium pour les sites dynamiques JavaScript.

Important - Légalité et éthique :
  • Respectez le fichier robots.txt du site
  • Ne surchargez pas les serveurs (délais entre requêtes)
  • Vérifiez les conditions d'utilisation
  • Privilégiez les APIs officielles si disponibles

Récupérer une Page Web

Python
import requests
from time import sleep

# Requête GET simple
url = "https://example.com"
response = requests.get(url)

# Vérifier le statut
if response.status_code == 200:
    print("Page récupérée avec succès")
    html_content = response.text
else:
    print(f"Erreur: {response.status_code}")

# Headers personnalisés (simuler un navigateur)
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
response = requests.get(url, headers=headers)

# Timeout pour éviter blocages
try:
    response = requests.get(url, timeout=10)
except requests.Timeout:
    print("Timeout - site trop lent")
except requests.RequestException as e:
    print(f"Erreur: {e}")

# Gérer les redirections
response = requests.get(url, allow_redirects=True)
print(f"URL finale: {response.url}")

# Télécharger fichier
response = requests.get("https://example.com/image.jpg")
with open("image.jpg", "wb") as f:
    f.write(response.content)

Parser HTML avec BeautifulSoup

Installation et bases

Bash
pip install beautifulsoup4 lxml
Python
import requests
from bs4 import BeautifulSoup

# Récupérer et parser
url = "https://example.com"
response = requests.get(url)
soup = BeautifulSoup(response.content, 'html.parser')

# Trouver éléments
titre = soup.find('h1')                    # Premier h1
tous_liens = soup.find_all('a')            # Tous les liens
premier_lien = soup.find('a')              # Premier lien

# Par classe CSS
articles = soup.find_all('div', class_='article')
# OU
articles = soup.find_all('div', {'class': 'article'})

# Par ID
header = soup.find(id='header')

# Sélecteurs CSS
articles = soup.select('div.article')      # Classe
titres = soup.select('h1, h2, h3')         # Plusieurs éléments
liens = soup.select('div.content a')       # Descendance

# Extraire données
print(titre.text)                          # Texte
print(premier_lien.get('href'))            # Attribut
print(premier_lien['href'])                # Alternative

Navigation dans l'arbre HTML

Python
# Parents et enfants
element = soup.find('div', class_='content')
parent = element.parent
enfants = element.children                  # Itérable
tous_enfants = element.descendants          # Récursif

# Frères et sœurs
suivant = element.next_sibling
precedent = element.previous_sibling
tous_suivants = element.find_next_siblings()

# Recherche dans sous-arbre
div = soup.find('div', class_='container')
liens_dans_div = div.find_all('a')

Exemple complet : scraper articles

Python
import requests
from bs4 import BeautifulSoup
import csv
from time import sleep

def scraper_articles(url_base, nb_pages=5):
    """Scrape articles d'un site avec pagination"""
    tous_articles = []
    
    for page in range(1, nb_pages + 1):
        url = f"{url_base}?page={page}"
        print(f"Scraping page {page}...")
        
        try:
            response = requests.get(url, timeout=10)
            soup = BeautifulSoup(response.content, 'html.parser')
            
            # Trouver tous les articles
            articles = soup.find_all('article', class_='post')
            
            for article in articles:
                # Extraire données
                titre_elem = article.find('h2', class_='title')
                date_elem = article.find('time')
                auteur_elem = article.find('span', class_='author')
                lien_elem = article.find('a')
                
                # Créer dictionnaire
                data = {
                    'titre': titre_elem.text.strip() if titre_elem else None,
                    'date': date_elem.get('datetime') if date_elem else None,
                    'auteur': auteur_elem.text.strip() if auteur_elem else None,
                    'lien': lien_elem.get('href') if lien_elem else None
                }
                
                tous_articles.append(data)
            
            # Pause pour ne pas surcharger
            sleep(2)
            
        except Exception as e:
            print(f"Erreur page {page}: {e}")
            continue
    
    return tous_articles

# Utilisation
articles = scraper_articles("https://example-blog.com/posts", nb_pages=3)

# Sauvegarder en CSV
with open('articles.csv', 'w', newline='', encoding='utf-8') as f:
    writer = csv.DictWriter(f, fieldnames=['titre', 'date', 'auteur', 'lien'])
    writer.writeheader()
    writer.writerows(articles)

print(f"Total articles récupérés: {len(articles)}")

Selenium pour Sites Dynamiques

BeautifulSoup ne peut pas exécuter JavaScript. Pour les sites avec contenu dynamique (React, Vue, chargement AJAX), utilisez Selenium qui pilote un vrai navigateur.

Installation

Bash
pip install selenium

# Télécharger ChromeDriver ou utiliser webdriver-manager
pip install webdriver-manager

Utilisation basique

Python
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service

# Initialiser navigateur
options = webdriver.ChromeOptions()
options.add_argument('--headless')  # Mode sans interface
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')

driver = webdriver.Chrome(
    service=Service(ChromeDriverManager().install()),
    options=options
)

try:
    # Charger page
    driver.get("https://example.com")
    
    # Attendre chargement (max 10 secondes)
    element = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.CLASS_NAME, "content"))
    )
    
    # Trouver éléments
    titre = driver.find_element(By.TAG_NAME, "h1")
    liens = driver.find_elements(By.TAG_NAME, "a")
    
    # Extraire données
    print(titre.text)
    for lien in liens:
        print(lien.get_attribute('href'))
    
    # Prendre screenshot
    driver.save_screenshot("page.png")
    
finally:
    driver.quit()

Interactions avec la page

Python
from selenium.webdriver.common.keys import Keys
import time

# Remplir formulaire
champ_recherche = driver.find_element(By.NAME, "q")
champ_recherche.send_keys("Python scraping")
champ_recherche.send_keys(Keys.RETURN)

# Cliquer bouton
bouton = driver.find_element(By.ID, "submit-button")
bouton.click()

# Scroller en bas de page
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

# Attendre chargement dynamique
time.sleep(2)

# Changer de fenêtre/onglet
driver.switch_to.window(driver.window_handles[1])

# Exécuter JavaScript
titre = driver.execute_script("return document.title")

# Gérer popup
alert = driver.switch_to.alert
alert.accept()  # OK
# alert.dismiss()  # Annuler

Exemple : scraper site avec pagination infinie

Python
from selenium import webdriver
from selenium.webdriver.common.by import By
import time

def scraper_scroll_infini(url, nb_scrolls=5):
    """Scrape site avec chargement infini (scroll)"""
    driver = webdriver.Chrome()
    driver.get(url)
    
    tous_elements = []
    derniere_hauteur = driver.execute_script("return document.body.scrollHeight")
    
    for i in range(nb_scrolls):
        print(f"Scroll {i+1}/{nb_scrolls}")
        
        # Récupérer éléments actuels
        elements = driver.find_elements(By.CLASS_NAME, "product-card")
        
        for elem in elements:
            titre = elem.find_element(By.CLASS_NAME, "title").text
            prix = elem.find_element(By.CLASS_NAME, "price").text
            tous_elements.append({'titre': titre, 'prix': prix})
        
        # Scroller en bas
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        time.sleep(3)  # Attendre chargement nouveau contenu
        
        # Vérifier si nouveau contenu chargé
        nouvelle_hauteur = driver.execute_script("return document.body.scrollHeight")
        if nouvelle_hauteur == derniere_hauteur:
            print("Plus de contenu à charger")
            break
        derniere_hauteur = nouvelle_hauteur
    
    driver.quit()
    
    # Éliminer doublons
    elements_uniques = list({elem['titre']: elem for elem in tous_elements}.values())
    return elements_uniques

produits = scraper_scroll_infini("https://example-shop.com/products", nb_scrolls=10)
print(f"Total produits: {len(produits)}")

Bonnes Pratiques

Respecter les sites

Python
import time
import random

# Délai entre requêtes
time.sleep(random.uniform(2, 5))  # 2-5 secondes aléatoire

# User-Agent réaliste
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}

# Vérifier robots.txt
from urllib.robotparser import RobotFileParser

rp = RobotFileParser()
rp.set_url("https://example.com/robots.txt")
rp.read()

if rp.can_fetch("*", "https://example.com/page"):
    print("Scraping autorisé")
else:
    print("Scraping non autorisé")

Gestion d'erreurs robuste

Python
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry

def create_session():
    """Session avec retry automatique"""
    session = requests.Session()
    
    retry = Retry(
        total=3,
        backoff_factor=1,
        status_forcelist=[500, 502, 503, 504]
    )
    
    adapter = HTTPAdapter(max_retries=retry)
    session.mount('http://', adapter)
    session.mount('https://', adapter)
    
    return session

# Utilisation
session = create_session()
try:
    response = session.get(url, timeout=10)
    response.raise_for_status()  # Lève exception si erreur HTTP
except requests.RequestException as e:
    print(f"Erreur: {e}")

Caching pour éviter requêtes répétées

Python
import requests_cache
from datetime import timedelta

# Activer cache SQLite
requests_cache.install_cache(
    'scraping_cache',
    expire_after=timedelta(hours=24)
)

# Les requêtes identiques utilisent le cache
response = requests.get(url)  # 1ère fois: requête réelle
response = requests.get(url)  # 2ème fois: depuis cache

Exemples Pratiques

Scraper prix produits e-commerce

Python
import requests
from bs4 import BeautifulSoup
import pandas as pd
from datetime import datetime

def surveiller_prix(urls_produits):
    """Surveille prix de plusieurs produits"""
    resultats = []
    
    for url in urls_produits:
        try:
            response = requests.get(url, timeout=10)
            soup = BeautifulSoup(response.content, 'html.parser')
            
            # Adapter sélecteurs selon site
            nom = soup.find('h1', class_='product-title').text.strip()
            prix_elem = soup.find('span', class_='price')
            prix = float(prix_elem.text.replace('CHF', '').strip())
            
            dispo = soup.find('div', class_='stock')
            en_stock = 'En stock' in dispo.text if dispo else False
            
            resultats.append({
                'date': datetime.now(),
                'produit': nom,
                'prix': prix,
                'en_stock': en_stock,
                'url': url
            })
            
        except Exception as e:
            print(f"Erreur {url}: {e}")
    
    # Sauvegarder historique
    df = pd.DataFrame(resultats)
    df.to_csv('historique_prix.csv', mode='a', header=False, index=False)
    
    return resultats

# Liste produits à surveiller
produits = [
    "https://shop.com/product1",
    "https://shop.com/product2"
]

prix = surveiller_prix(produits)

# Alerter si prix baisse
for p in prix:
    if p['prix'] < 100:  # Seuil
        print(f"⚠️ ALERTE: {p['produit']} à {p['prix']} CHF !")

Scraper offres d'emploi

Python
def scraper_offres_emploi(mot_cle, ville):
    """Scrape offres d'emploi selon critères"""
    url = f"https://job-site.com/search?q={mot_cle}&location={ville}"
    
    response = requests.get(url)
    soup = BeautifulSoup(response.content, 'html.parser')
    
    offres = []
    for job in soup.find_all('div', class_='job-listing'):
        titre = job.find('h2', class_='job-title').text.strip()
        entreprise = job.find('span', class_='company').text.strip()
        lieu = job.find('span', class_='location').text.strip()
        lien = job.find('a')['href']
        
        # Scraper page détail
        detail_response = requests.get(lien)
        detail_soup = BeautifulSoup(detail_response.content, 'html.parser')
        description = detail_soup.find('div', class_='description').text.strip()
        
        offres.append({
            'titre': titre,
            'entreprise': entreprise,
            'lieu': lieu,
            'description': description[:200],  # Extrait
            'lien': lien
        })
        
        time.sleep(1)  # Pause entre pages
    
    return offres

# Rechercher développeur Python à Lausanne
offres = scraper_offres_emploi("Python Developer", "Lausanne")

# Sauvegarder
df = pd.DataFrame(offres)
df.to_excel('offres_emploi.xlsx', index=False)