Web Scraping Intelligent : Collecter des Données avec l IA

Web Scraping Intelligent : Collecter des Données avec l IA

Votre équipe passe-t-elle des heures à copier-coller des données depuis des sites web concurrents, des plateformes d’annonces ou des bases de données publiques ? Selon une étude Statista, les entreprises qui s’appuient sur des données web structurées prennent des décisions 3 fois plus rapides que celles qui travaillent manuellement. Le web scraping — collecte automatisée de données web — existe depuis les années 2000, mais l’IA a radicalement changé la donne : là où il fallait des développeurs spécialisés et des semaines de travail pour extraire des données structurées depuis des sites hétérogènes, un pipeline IA peut aujourd’hui le faire en quelques heures.

Web scraping classique vs web scraping augmenté par l’IA

L’approche classique et ses limites

Le scraping traditionnel repose sur des sélecteurs CSS ou XPath : on cible un élément HTML précis et on en extrait le contenu.

# Approche classique avec BeautifulSoup
import requests
from bs4 import BeautifulSoup

def scrape_product_price(url: str) -> str:
    response = requests.get(url)
    soup = BeautifulSoup(response.text, 'html.parser')
    
    # Sélecteur CSS fragile — cassé dès que le site change son HTML
    price_element = soup.select_one('.product-price .amount')
    return price_element.text if price_element else None

Problèmes :

  • Fragile : un changement de classe CSS dans le HTML casse le scraper
  • Non générique : chaque site nécessite son propre code
  • Incapable de comprendre le contexte (distinguer un prix principal d’un prix barré)
  • Impuissant face aux sites JavaScript dynamiques

L’approche IA : robuste et générique

Avec un LLM, on décrit ce qu’on veut extraire en langage naturel. Le modèle comprend la structure sémantique de la page, pas seulement sa structure HTML.

import anthropic
import httpx

client = anthropic.Anthropic()

def scrape_with_llm(url: str, extraction_schema: dict) -> dict:
    """
    Extrait des données structurées d'une page web via LLM.
    extraction_schema : description des champs à extraire
    """
    # Récupération du HTML brut
    response = httpx.get(url, follow_redirects=True, timeout=15)
    html_content = response.text
    
    # Nettoyage : on ne garde que le texte visible (pas les styles/scripts)
    from bs4 import BeautifulSoup
    soup = BeautifulSoup(html_content, 'html.parser')
    
    # Suppression des éléments non pertinents
    for tag in soup.find_all(['script', 'style', 'nav', 'footer', 'header']):
        tag.decompose()
    
    visible_text = soup.get_text(separator='\n', strip=True)[:8000]  # Limite de contexte
    
    schema_description = "\n".join([
        f"- {key}: {desc}" 
        for key, desc in extraction_schema.items()
    ])
    
    prompt = f"""Extrais les informations suivantes depuis ce contenu web.
Réponds uniquement avec un JSON valide contenant ces champs :
{schema_description}

Si une information est absente, mets null.

CONTENU DE LA PAGE :
{visible_text}"""
    
    response = client.messages.create(
        model="claude-haiku-4-5",  # Haiku suffit pour cette tâche, moins coûteux
        max_tokens=1024,
        messages=[{"role": "user", "content": prompt}]
    )
    
    import json
    return json.loads(response.content[0].text)

# Utilisation
schema = {
    "nom_produit": "Nom complet du produit",
    "prix_ttc": "Prix TTC en euros (nombre décimal)",
    "prix_barre": "Ancien prix barré si disponible, sinon null",
    "disponibilite": "En stock / Rupture / Précommande",
    "description_courte": "Description principale du produit en 1-2 phrases"
}

data = scrape_with_llm("https://exemple-boutique.fr/produit/123", schema)

Playwright pour les sites dynamiques

De nombreux sites modernes chargent leur contenu via JavaScript (React, Vue, Angular). BeautifulSoup ne voit que le HTML initial — il faut un vrai navigateur.

from playwright.async_api import async_playwright
import asyncio

async def scrape_dynamic_page(url: str) -> str:
    async with async_playwright() as p:
        browser = await p.chromium.launch(
            headless=True,
            args=['--no-sandbox', '--disable-setuid-sandbox']
        )
        
        context = await browser.new_context(
            user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
            viewport={'width': 1920, 'height': 1080}
        )
        
        page = await context.new_page()
        
        # Blocage des ressources inutiles (images, fonts) pour accélérer
        await page.route("**/*.{png,jpg,jpeg,gif,webp,woff,woff2}", 
                         lambda route: route.abort())
        
        await page.goto(url, wait_until='networkidle', timeout=30000)
        
        # Attendre que le contenu principal soit chargé
        await page.wait_for_selector('[data-testid="product-title"], h1', timeout=10000)
        
        # Scroll pour déclencher le lazy loading
        await page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
        await asyncio.sleep(1)
        
        content = await page.content()
        await browser.close()
        
        return content

# Pipeline complet
async def full_scrape_pipeline(url: str, schema: dict) -> dict:
    html = await scrape_dynamic_page(url)
    
    from bs4 import BeautifulSoup
    soup = BeautifulSoup(html, 'html.parser')
    for tag in soup.find_all(['script', 'style']):
        tag.decompose()
    text = soup.get_text(separator='\n', strip=True)[:8000]
    
    return extract_with_llm(text, schema)

Stratégies anti-détection

Les sites mettent en place des protections contre le scraping abusif. Voici les bonnes pratiques pour rester sous le radar :

Délais et respect du robots.txt

import time
import random
from urllib.robotparser import RobotFileParser

def check_robots_allowed(url: str, user_agent: str = "*") -> bool:
    from urllib.parse import urlparse
    parsed = urlparse(url)
    robots_url = f"{parsed.scheme}://{parsed.netloc}/robots.txt"
    
    rp = RobotFileParser()
    rp.set_url(robots_url)
    try:
        rp.read()
        return rp.can_fetch(user_agent, url)
    except Exception:
        return True  # En cas d'erreur, on suppose autorisé

class RespectfulScraper:
    def __init__(self, min_delay: float = 1.0, max_delay: float = 3.0):
        self.min_delay = min_delay
        self.max_delay = max_delay
        self.last_request_time = {}
    
    def wait_politely(self, domain: str):
        """Respecte un délai entre requêtes vers le même domaine."""
        last_time = self.last_request_time.get(domain, 0)
        elapsed = time.time() - last_time
        wait_time = random.uniform(self.min_delay, self.max_delay)
        
        if elapsed < wait_time:
            time.sleep(wait_time - elapsed)
        
        self.last_request_time[domain] = time.time()
    
    def scrape(self, url: str, schema: dict) -> dict | None:
        from urllib.parse import urlparse
        domain = urlparse(url).netloc
        
        if not check_robots_allowed(url):
            print(f"⚠️ Scraping interdit par robots.txt : {url}")
            return None
        
        self.wait_politely(domain)
        return scrape_with_llm(url, schema)

Rotation de proxies et d’User-Agents

Pour les scraping à grande échelle, la rotation de proxies est indispensable. Des services comme Bright Data, Oxylabs ou ScraperAPI proposent des proxies résidentiels qui passent la plupart des protections.

import httpx
import itertools

PROXIES = [
    "http://user:pass@proxy1.example.com:8080",
    "http://user:pass@proxy2.example.com:8080",
    # ...
]

proxy_cycle = itertools.cycle(PROXIES)

def get_with_proxy(url: str) -> httpx.Response:
    proxy = next(proxy_cycle)
    with httpx.Client(proxy=proxy, timeout=15) as client:
        return client.get(url)

Pipeline de collecte avec enrichissement IA

L’IA ne sert pas seulement à l’extraction — elle peut aussi enrichir les données collectées :

from dataclasses import dataclass
from typing import Optional
import pandas as pd

@dataclass
class ProductData:
    url: str
    nom: str
    prix: float
    description: str
    # Champs enrichis par IA
    categorie_ia: Optional[str] = None
    sentiment_description: Optional[str] = None
    mots_cles_seo: Optional[list] = None

def enrich_product(product: ProductData) -> ProductData:
    """Enrichit un produit avec des analyses IA."""
    
    enrichment_prompt = f"""Analyse ce produit et fournis un JSON avec :
- "categorie": catégorie principale parmi [Électronique, Vêtements, Maison, Alimentaire, Sport, Autre]
- "sentiment": sentiment de la description parmi [positif, neutre, négatif]
- "mots_cles": liste de 5 mots-clés SEO pertinents

Produit : {product.nom}
Description : {product.description}"""
    
    response = client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=300,
        messages=[{"role": "user", "content": enrichment_prompt}]
    )
    
    import json
    enrichment = json.loads(response.content[0].text)
    
    product.categorie_ia = enrichment.get("categorie")
    product.sentiment_description = enrichment.get("sentiment")
    product.mots_cles_seo = enrichment.get("mots_cles", [])
    
    return product

# Pipeline complet : collecte → extraction → enrichissement → export
async def run_scraping_campaign(urls: list[str], schema: dict) -> pd.DataFrame:
    scraper = RespectfulScraper(min_delay=2.0, max_delay=5.0)
    results = []
    
    for url in urls:
        raw_data = scraper.scrape(url, schema)
        if raw_data:
            product = ProductData(url=url, **raw_data)
            product = enrich_product(product)
            results.append(product)
    
    df = pd.DataFrame([vars(r) for r in results])
    df.to_csv("products_enriched.csv", index=False)
    return df

Considérations légales

Le web scraping est un sujet juridique complexe. Voici les points essentiels à retenir pour une PME française :

Ce qui est généralement autorisé :

  • Scraper des données publiques sans authentification
  • Respecter le robots.txt (même si non obligatoire, c’est la norme éthique)
  • Scraper à une fréquence raisonnable sans impacter le serveur cible

Ce qui est risqué ou interdit :

  • Contourner des systèmes d’authentification ou de protection (CFAA aux USA, directive NIS2 en Europe)
  • Scraper des données personnelles sans base légale RGPD
  • Reproduire des contenus protégés par le droit d’auteur
  • Violer explicitement les CGU d’un service (peut engager la responsabilité civile)

Règle pratique : si les données sont publiquement accessibles, non personnelles, et que votre usage est interne (veille concurrentielle, agrégation de prix), vous êtes généralement dans une zone acceptable. En cas de doute, consultez un juriste.

Ce qu’il faut retenir

L’IA transforme le web scraping de deux manières complémentaires : elle rend l’extraction plus robuste (plus besoin de sélecteurs fragiles) et elle permet d’enrichir les données collectées à la volée (catégorisation, sentiment, mots-clés). Combinée avec Playwright pour les sites dynamiques et des pratiques de scraping respectueuses, cette approche permet de construire des pipelines de données web fiables et maintenables.

Le coût marginal d’analyse par page est faible (quelques centimes avec Haiku) et les gains de productivité sont considérables comparés à la collecte manuelle.

Vous avez un besoin de collecte de données web automatisée ? Notre équipe conçoit et déploie des pipelines de scraping sur mesure, adaptés à votre secteur et respectueux du cadre légal. Contactez-nous à contact@brio-novia.eu pour discuter de votre projet.