Comment créer un AI web scraper avec Python ?

Un AI web scraper en Python récupère une page, nettoie le HTML, le transforme en Markdown, puis interroge un LLM sur le contenu utile. Le vrai sujet, ce n’est pas scraper plus. C’est envoyer moins de bruit au modèle et obtenir une réponse exploitable.

De quoi avez vous besoin ?

Avant de récupérer la moindre page, je pose toujours une base propre. C’est moins sexy que le scraping lui-même, mais c’est ce qui évite les erreurs pénibles après deux heures de test. Il vous faut un notebook Jupyter, quelques librairies Python, une clé API OpenAI dans un fichier .env, et une règle simple : je ne mets jamais une clé API en dur dans le code.

Pour installer les briques utiles, je pars généralement sur ça :

pip install requests beautifulsoup4 markdownify openai ftfy python-dotenv

Chaque librairie a son rôle. Requests va chercher les pages web. BeautifulSoup lit et nettoie le HTML. Markdownify transforme le HTML en Markdown, un format texte plus simple à donner à une IA. Ftfy corrige les problèmes d’encodage, les caractères bizarres qu’on voit souvent sur des pages mal propres. Python-dotenv charge les variables depuis le fichier .env. OpenAI permet d’appeler le modèle.

Entre nous, on le sait bien, faire appel à un consultant en automatisation intelligente et en agent IA, c’est souvent le raccourci le plus malin. On en parle ?

Je démarre souvent mon notebook avec ce bloc :

import os
import re
import requests

from bs4 import BeautifulSoup, Comment
from ftfy import fix_text
from markdownify import markdownify
from openai import OpenAI
from dotenv import load_dotenv

# Charge les variables du fichier .env
load_dotenv()

OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")

# Stoppe tout si la clé API n'est pas disponible
if not OPENAI_API_KEY:
    raise ValueError("OPENAI_API_KEY est manquante. Vérifiez votre fichier .env.")

# Modèle léger pour commencer
MODEL_NAME = "gpt-5.4-nano"

# Création du client OpenAI
client = OpenAI(api_key=OPENAI_API_KEY)

Le fichier .env contient simplement votre clé, comme ça :

OPENAI_API_KEY=sk-votre-cle-api-ici

Je vérifie aussi la facturation côté API avant de chercher plus loin. C’est le genre de détail bête qui bloque tout pendant vingt minutes. J’ai déjà vu ça chez un client : le code était bon, les imports étaient bons, la clé était bonne, mais le compte OpenAI n’était pas prêt côté billing.

Le modèle gpt-5.4-nano est un bon choix pour démarrer léger. Si vous devez analyser des pages longues, extraire beaucoup de champs, ou faire du raisonnement plus fin, le choix du modèle changera. Je préfère commencer petit, mesurer, puis monter en gamme si besoin.

Outil Rôle Point d’attention
Jupyter Notebook Tester vite et garder une trace claire Éviter de laisser des clés visibles dans les cellules
Requests et BeautifulSoup Récupérer et parser les pages web Gérer les erreurs HTTP et le HTML sale
Markdownify et ftfy Nettoyer le contenu avant l’IA Limiter le bruit envoyé au modèle
OpenAI API Analyser et structurer le contenu Vérifier la clé API et la facturation

Comment récupérer une page ?

La première chose que je fais dans un scraper, c’est récupérer proprement le HTML de la page. Pas besoin de sur-ingénierie au début. Je veux juste une requête HTTP claire, un User-Agent explicite, un timeout raisonnable, et une erreur nette si le serveur répond mal.

import requests


def fetch_page(url):
    headers = {
        "User-Agent": "SimpleAIScraper/1.0"
    }

    response = requests.get(
        url,
        headers=headers,
        timeout=15
    )

    response.raise_for_status()

    return response.text


html = fetch_page("https://www.example.com")

print(html[:500])

Le User-Agent sert à dire qui fait la requête. Ici, je mets SimpleAIScraper/1.0. C’est simple, explicite, et ça évite de laisser Python envoyer un User-Agent par défaut un peu vague. Le timeout de 15 secondes évite aussi de bloquer le script indéfiniment si le site ne répond pas.

La ligne importante, c’est response.raise_for_status(). Si la page renvoie une erreur HTTP, par exemple une 404 ou une 500, Python lève une exception. Je préfère ça plutôt que de continuer comme si tout allait bien avec une page d’erreur à la place du vrai contenu. J’ai déjà vu ça chez un client, un scraper tournait “sans erreur”, mais il ingérait depuis deux jours une page de maintenance. Techniquement le script marchait. Dans les faits, les données étaient inutilisables.

J’isole cette logique dans une fonction dès le début parce que ça me simplifie la vie très vite. Je peux la tester seule, la réutiliser partout, et la remplacer plus tard si le projet grossit. Si demain je dois ajouter des retries, un proxy, une session persistante, ou passer sur une librairie asynchrone, je touche une seule zone du code.

Les erreurs fréquentes arrivent vite, même sur un scraper simple :

  • Un timeout, quand le site met trop de temps à répondre.
  • Un 403, quand l’accès est refusé.
  • Un 404, quand l’URL n’existe pas ou plus.
  • Des redirections, parfois normales, parfois piégeuses.
  • Une page vide, souvent liée à du JavaScript ou à un blocage côté serveur.

Je garde aussi quelques réflexes simples. Je vérifie les conditions du site, j’évite de taper trop fort, je respecte les règles d’accès publiques quand elles existent, et je ne scrape pas des espaces privés ou protégés. Récupérer la page, c’est seulement le début. Le HTML brut est souvent plein de bruit, et c’est justement ce qu’on va nettoyer ensuite.

Comment nettoyer le HTML ?

Avant d’envoyer une page à un LLM, je nettoie toujours le HTML. Le modèle n’a pas besoin de lire le menu, les scripts de tracking, le footer, les boutons de partage ou les formulaires de newsletter. Tout ça prend de la place dans le contexte, coûte des tokens, et brouille la réponse.

Un cas très classique : une page produit ou un article avec 800 lignes de navigation, de CSS, de scripts et de footer, pour seulement 40 lignes vraiment utiles. Quand la réponse devient floue, ce n’est pas forcément parce que le modèle est mauvais. C’est souvent parce qu’on lui donne un mauvais contexte.

Je pars d’un HTML brut, souvent sale, avec des caractères cassés, des espaces bizarres, parfois des commentaires inutiles. La fonction ci-dessous fait trois choses simples : elle corrige le texte avec ftfy, elle parse le HTML avec BeautifulSoup, puis elle supprime les éléments qui polluent le contenu.

import ftfy
from bs4 import BeautifulSoup, Comment

def clean_html(html):
    # Corrige les problèmes d'encodage et de caractères cassés
    html = ftfy.fix_text(html)

    soup = BeautifulSoup(html, "html.parser")

    # Supprime les commentaires HTML
    for comment in soup.find_all(string=lambda text: isinstance(text, Comment)):
        comment.extract()

    # Supprime les blocs inutiles pour le LLM
    tags_to_remove = [
        "script", "style", "noscript", "svg", "img", "iframe",
        "nav", "header", "footer", "aside", "form", "button"
    ]

    for tag_name in tags_to_remove:
        for tag in soup.find_all(tag_name):
            tag.decompose()

    # Récupère uniquement le texte lisible
    text = soup.get_text(separator="\n")

    # Nettoie les lignes vides et les espaces
    lines = [line.strip() for line in text.splitlines() if line.strip()]

    return "\n".join(lines)

Les balises comme script et style sont rarement utiles, elles contiennent du code, pas de l’information métier. Les balises nav, header et footer répètent souvent les mêmes liens sur toutes les pages. Les forms, buttons et iframes ajoutent du bruit interactif, mais très peu de sens.

J’ai vu ça chez un client sur un scraper de fiches fournisseurs. Le modèle inventait parfois des infos alors que les données étaient bien présentes. Le souci venait du HTML envoyé : 90 % de bruit. Après nettoyage, les réponses sont devenues beaucoup plus stables.

Critère HTML brut HTML nettoyé
Bruit Très élevé, avec menus, scripts, footer et formulaires Beaucoup plus faible, centré sur le contenu utile
Coût en tokens Plus cher, car le modèle lit trop de texte inutile Moins cher, car on réduit le contexte envoyé
Qualité de réponse Plus floue, parfois hors sujet Plus précise, car le modèle reçoit une meilleure matière

Comment faire répondre le modèle ?

Une fois le HTML nettoyé, je ne l’envoie pas tel quel au modèle. Je le convertis en Markdown. C’est beaucoup plus lisible pour un LLM, parce qu’on garde les titres, les listes, les paragraphes, parfois les liens, mais on évite de transporter tout le bruit du DOM avec ses classes CSS, ses div imbriquées et ses attributs inutiles.

J’utilise souvent markdownify pour ça. C’est une petite librairie Python qui transforme du HTML en Markdown propre. L’idée n’est pas d’avoir un rendu parfait, l’idée c’est d’avoir un texte structuré, compact, facile à injecter dans un prompt.

from markdownify import markdownify as md

def html_to_markdown(clean_html: str) -> str:
    markdown = md(
        clean_html,
        heading_style="ATX",
        bullets="-",
        strip=["script", "style", "svg", "form", "nav", "footer"]
    )

    lines = [line.strip() for line in markdown.splitlines()]
    clean_markdown = "\n".join(line for line in lines if line)

    return clean_markdown

Ce Markdown devient la matière utile. Le modèle n’a pas besoin de toute la page web. Il a besoin du bon extrait, dans un format qu’il comprend vite. J’ai vu ça chez un client qui scrapait des pages produit entières avec menus, avis, recommandations et footer. Les réponses étaient moyennes, lentes, et chères. En envoyant seulement le contenu utile converti en Markdown, les réponses sont devenues beaucoup plus stables.

Ensuite, je pose une question précise au modèle. Et je lui impose une règle simple : il répond à partir du contenu fourni, sans inventer. C’est important. Un scraper augmenté à l’IA doit rester utile, pas créatif n’importe comment.

from openai import OpenAI

client = OpenAI()
MODEL_NAME = "gpt-4o-mini"

def ask_model(markdown_content: str, question: str) -> str:
    prompt = f"""
Tu réponds uniquement à partir du contenu fourni.

Si l'information n'est pas présente, dis clairement :
"Je ne trouve pas l'information dans le contenu fourni."

Question :
{question}

Contenu :
{markdown_content}
"""

    response = client.chat.completions.create(
        model=MODEL_NAME,
        temperature=0,
        messages=[
            {"role": "system", "content": "Tu es un assistant précis, factuel et concis."},
            {"role": "user", "content": prompt}
        ]
    )

    return response.choices[0].message.content.strip()

Une question business simple peut ressembler à ça :

  • Résume l’offre de cette page en 5 lignes.
  • Quels sont les points clés du service proposé ?
  • Quel est le prix mentionné, s’il existe ?
  • À quel type de client cette offre semble s’adresser ?

Cette approche réduit les tokens, donc le coût. Elle améliore aussi la qualité, parce que le modèle reçoit moins de bruit. Et surtout, elle rend le pipeline plus simple à automatiser derrière, dans un workflow data, Make, n8n, Airflow ou n’importe quel outil low code.

On le rend fiable maintenant ?

Un bon AI web scraper, ce n’est pas juste un script qui aspire une page. C’est une petite chaîne propre, récupérer, nettoyer, convertir, interroger. Le point qui change tout, c’est le nettoyage avant l’appel au modèle. Moins de bruit, moins de tokens, moins de réponses molles. Je garderais ce setup simple pour démarrer, puis je le renforcerais selon le contexte, logs, gestion d’erreurs, limites d’appels, automatisation. Vous avez déjà une base solide pour tester vite, comprendre ce qui se passe, et transformer une page web en réponse utile pour votre business.

FAQ

  • Qu’est ce qu’un AI web scraper ?
    Un AI web scraper récupère le contenu d’une page web, le nettoie, puis utilise un modèle de langage pour répondre à une question précise. La différence avec un scraper classique, c’est qu’on ne se limite pas à extraire des champs. On peut demander un résumé, une analyse ou une réponse ciblée à partir du contenu utile.
  • Pourquoi convertir le HTML en Markdown ?
    Le Markdown garde une structure lisible sans transporter tout le bruit du HTML. C’est plus simple pour le modèle, plus léger en tokens, et souvent plus clair pour obtenir une réponse fiable. Je préfère envoyer un contenu propre et structuré plutôt qu’un DOM plein de scripts, menus et pieds de page.
  • Quelles bibliothèques Python utiliser ?
    La base tient avec requests pour récupérer la page, BeautifulSoup pour parser et nettoyer le HTML, ftfy pour corriger le texte, markdownify pour convertir en Markdown, python-dotenv pour charger la clé API, et openai pour interroger le modèle. C’est simple, lisible, et suffisant pour un premier pipeline.
  • Pourquoi nettoyer le HTML avant d’appeler le LLM ?
    Parce que le HTML brut contient souvent beaucoup d’éléments inutiles, navigation, scripts, images, formulaires, header, footer. Si vous envoyez tout ça au modèle, vous augmentez le coût en tokens et vous dégradez la qualité de la réponse. Le nettoyage améliore directement le signal envoyé au LLM.
  • Est ce qu’un AI web scraper peut être automatisé ?
    Oui, une fois les fonctions séparées proprement, récupération, nettoyage, conversion et appel au modèle, le pipeline peut être automatisé. On peut ensuite l’intégrer dans un notebook, un script planifié ou un workflow low code. Le plus important reste de garder des contrôles simples, erreurs HTTP, timeout, contenu vide et usage raisonnable.

 

 

A propos de l’auteur

Je suis Franck Scandolera, expert et formateur en tracking avancé server-side, Analytics Engineering, automatisation No/Low Code avec n8n, intégration de l’IA en entreprise et SEO/GEO. J’accompagne des équipes data, marketing et business sur des sujets très concrets, de la collecte fiable à l’automatisation intelligente. J’ai travaillé avec des références comme Logis Hôtel, Yelloh Village, BazarChic, la Fédération Française de Football ou Texdecor. Je dirige l’agence webAnalyste et l’organisme Formations Analytics. Si vous voulez mettre ce type de pipeline IA en place proprement, contactez moi.

Retour en haut