Les données brutes issues du web scraping atterrissent presque jamais dans un état qu'un modèle peut utiliser. Récupérez quelques milliers de pages de produits, d'annonces ou d'articles et vous obtenez des lignes dupliquées, des prix stockés en chaînes, des dates dans cinq formats, des cellules vides, et du texte parsemé d'entités HTML et d'espaces parasites. Alimentez cela directement dans une tâche d'entraînement et le modèle apprend le bruit aussi facilement que le signal. Le travail qui transforme un scrape en jeu de données, c'est le nettoyage et la structuration des données, et c'est là que se gagne ou se perd la majeure partie de la précision d'un pipeline AI ou ML.

Ce guide est un tutoriel pratique pour structurer et nettoyer les données web pour l'AI : charger un scrape brut, le dédupliquer, normaliser les types et formats, gérer les valeurs manquantes, effectuer un nettoyage de texte basique et une tokenisation, concevoir un schéma sur lequel votre code en aval peut compter, et valider le résultat avant qu'il n'atteigne jamais un modèle. Chaque étape dispose de code Python exécutable que vous pouvez coller dans un notebook. À la fin, nous indiquons où Crawlbase peut renvoyer une sortie propre et structurée d'emblée pour que vous fassiez moins de cela manuellement.

Pourquoi le nettoyage et la structuration décident de la qualité du modèle

Les modèles ne raisonnent pas sur vos données comme vous le faites. Une ligne dupliquée est un poids supplémentaire sur un exemple. Un prix stocké comme "$1,299.00" est une chaîne que le modèle ne peut pas comparer à 1299.0. Une date écrite comme "03/04/2025" dans certaines lignes et "2025-04-03" dans d'autres devient deux tokens non liés. Aucun de ces problèmes ne génère d'erreur, ce qui est exactement pourquoi ils sont dangereux : le pipeline s'exécute, les métriques semblent plausibles, et le modèle apprend silencieusement depuis une vue corrompue du monde.

Le nettoyage corrige les dommages évidents (doublons, valeurs manquantes, formats incohérents) et la structuration impose un contrat : chaque colonne a un type, une unité et une signification. Ce contrat est ce qui permet au même jeu de données d'alimenter un classifieur aujourd'hui et un modèle différent le trimestre prochain sans surprise. La même discipline s'applique que vous fassiez du web scraping pour le machine learning sur quelques milliers de lignes ou que vous lanciez un scraping web à grande échelle dans les millions.

Commencer avec un scrape brut réaliste

Pour rendre les étapes concrètes, supposez que vous avez scrapé un ensemble d'annonces de produits e-commerce et les avez écrites dans raw_products.csv. Un vrai scrape est désordonné, donc le fichier ci-dessous l'est aussi : lignes dupliquées, symboles monétaires et séparateurs de milliers dans price, formats de date mixtes, cellules vides, et texte d'avis avec des entités HTML et des espaces irréguliers.

python
import pandas as pd

df = pd.read_csv("raw_products.csv")

# A first look at the damage before touching anything
print(df.shape)
print(df.dtypes)
print(df.isna().sum())
print(df.head())

La sortie dtypes est le signe révélateur. Si price revient comme object au lieu d'un type numérique, pandas n'a pas pu l'analyser, ce qui signifie qu'il y a des données non numériques dans la colonne. Exécuter isna().sum() tôt vous indique quelles colonnes ont des valeurs manquantes et à quel point c'est grave, afin que vous puissiez décider quoi corriger avant d'écrire une seule transformation.

Dédupliquer d'abord

La déduplication vient avant tout le reste car les doublons gonflent chaque statistique ultérieure. Les doublons exacts sont le cas facile : des lignes identiques d'un scraper qui a revisité la même URL ou paginé sur une fenêtre qui se chevauche.

python
# Drop fully identical rows
df = df.drop_duplicates()

# Deduplicate on a business key, keeping the most recent capture
df = (df.sort_values("scraped_at")
        .drop_duplicates(subset=["product_id"], keep="last"))

Le deuxième schéma compte plus en pratique. Deux captures du même product_id ne sont pas des lignes identiques une fois que le prix a changé, mais vous voulez généralement un enregistrement par produit, pas deux. Trier par heure de capture et garder "last" vous donne la version la plus récente. Choisissez la clé qui identifie réellement une entité dans votre domaine (un ID produit, une URL, un SKU) plutôt que de faire confiance à l'égalité de lignes complètes.

Dédupliquer avant d'imputer

L'ordre n'est pas cosmétique ici. Si vous remplissez les valeurs manquantes d'abord et déduplication ensuite, vos moyennes imputées sont calculées sur des comptages gonflés et biaisées vers les entités les plus dupliquées. Supprimez toujours les doublons avant toute statistique (moyenne, médiane, mode) dont dépendent les étapes ultérieures.

Normaliser les types et formats

Avec une ligne par entité, faites de chaque colonne un type unique et prévisible. C'est l'étape qui transforme "$1,299.00" en 1299.0 et cinq formats de date en un seul. Les chaînes monétaires ont besoin de leurs symboles et séparateurs retirés avant de s'analyser en nombres ; les dates ont besoin d'un seul parser avec errors="coerce" pour que les données non analysables deviennent NaT au lieu de faire planter l'exécution.

python
# Strip currency symbols and separators, then parse to float
df["price"] = (df["price"]
    .astype("string")
    .str.replace(r"[^\d.]", "", regex=True))
df["price"] = pd.to_numeric(df["price"], errors="coerce")

# Parse mixed date formats into one datetime type
df["listed_on"] = pd.to_datetime(
    df["listed_on"], errors="coerce"
)

# Normalize a categorical column: trim and lowercase
df["category"] = df["category"].str.strip().str.lower()

Normaliser les catégorielles est le gain discret ici. Les scrapes produisent régulièrement "Electronics", "electronics " et "ELECTRONICS" comme trois valeurs distinctes pour une seule catégorie. Supprimer les espaces et mettre en minuscules les regroupe en une seule, ce qui signifie des résultats group-by plus propres et une seule feature au lieu de trois après encodage. Faites la même standardisation sur les unités : si certains prix sont en dollars et d'autres en centimes, convertissez en une seule unité maintenant, pendant que vous vous souvenez encore laquelle est laquelle.

Gérer les valeurs manquantes délibérément

Il n'y a pas de règle universelle pour les données manquantes, seulement des compromis. Supprimer les lignes est sûr quand les lacunes sont rares et que la ligne est inutile sans le champ. L'imputation conserve la ligne mais invente une valeur, donc elle n'a de sens que quand la colonne est nécessaire et qu'une estimation raisonnable existe. Décidez colonne par colonne plutôt qu'en appliquant un seul appel global à tout le frame.

python
# Drop rows missing a field the record cannot exist without
df = df.dropna(subset=["product_id", "price"])

# Impute a numeric column with its median (robust to outliers)
df["rating"] = df["rating"].fillna(df["rating"].median())

# Fill a categorical with an explicit sentinel, not a guess
df["brand"] = df["brand"].fillna("unknown")

La médiane bat la moyenne pour imputer les colonnes numériques car quelques valeurs aberrantes extrêmes (un prix mal scrapé de 99999) tirent la moyenne mais déplacent à peine la médiane. Pour les catégorielles, un sentinel explicite "unknown" est honnête : il dit au modèle que la valeur était absente plutôt que de prétendre qu'elle appartenait à la catégorie la plus courante. Ne laissez jamais un encodeur en aval traiter silencieusement NaN comme une vraie catégorie.

Nettoyer le texte et tokeniser

Si votre jeu de données contient du texte libre (descriptions de produits, avis, corps d'articles), il nécessite sa propre passe. Le texte scrapé arrive avec des entités HTML (&, '), des balises résiduelles, des URLs et des espaces incohérents. Nettoyez-le avant de tokeniser ou les tokens seront remplis de déchets. La tokenisation ici signifie découper le texte en unités qu'un modèle consomme ; l'exemple ci-dessous effectue une tokenisation par espaces, ce qui est suffisant pour illustrer le nettoyage qui doit venir en premier.

python
import re
import html

def clean_text(value):
    if pd.isna(value):
        return ""
    text = html.unescape(str(value))   # & -> &
    text = re.sub(r"<[^>]+>", " ", text)   # strip tags
    text = re.sub(r"http\S+", " ", text)   # strip URLs
    text = re.sub(r"\s+", " ", text)         # collapse whitespace
    return text.strip().lower()

df["review_clean"] = df["review"].apply(clean_text)
df["tokens"] = df["review_clean"].str.split()

L'ordre dans clean_text est délibéré : désescaper les entités d'abord pour que &amp;amp; devienne & avant qu'une regex s'exécute, supprimer les balises et les URLs ensuite, puis réduire les espaces en dernier pour que les lacunes laissées par les suppressions précédentes se ferment. Mettre en minuscules à la fin maintient les comptages de tokens honnêtes ("Fast" et "fast" deviennent un seul token). Pour un vrai travail NLP, vous remplaceriez le split final par un vrai tokeniseur, mais le nettoyage ci-dessus est la partie que les données scrapées nécessitent toujours.

Crawlbase Crawling API

La majorité de la suppression d'entités ci-dessus existe parce que les scrapes HTML bruts sont bruyants. La Crawling API peut renvoyer une sortie propre et structurée (y compris une vue markdown de la page) pour que le texte arrive sans balises ni texte générique, réduisant l'étape de nettoyage à la normalisation de types. Pointez-la vers une page publique sur le niveau gratuit et comparez la sortie à une récupération brute.

Concevoir un schéma et l'appliquer

Jusqu'ici, le nettoyage a été réactif. Un schéma en fait un contrat : un ensemble déclaré de colonnes, chacune avec un seul type, que chaque lot doit satisfaire. Encoder le schéma comme des types vers lesquels vous castez (et comme des assertions que vous vérifiez) signifie que le prochain scrape soit se conforme, soit échoue bruyamment, au lieu de dériver silencieusement dans le même désordre que vous venez de nettoyer.

python
# A schema is just a column -> dtype contract
schema = {
    "product_id": "string",
    "category":   "category",
    "price":      "float64",
    "rating":     "float64",
    "brand":      "string",
    "listed_on":  "datetime64[ns]",
}

# Keep only schema columns, in order, and cast each one
df = df[list(schema.keys())].astype(schema)

Sélectionner list(schema.keys()) supprime toutes les colonnes parasites que le scraper a ajoutées et fixe l'ordre des colonnes, pour que chaque export ait la même forme. L'appel astype(schema) caste chaque colonne et lèvera une erreur si une valeur ne peut pas être coercée, ce qui est le comportement que vous voulez : mieux un échec bruyant maintenant qu'une colonne corrompue découverte après un cycle d'entraînement. Utiliser le dtype category pour les champs à faible cardinalité comme category réduit également la mémoire et accélère les group-bys sur les grands frames.

Valider avant d'exporter

La validation est la porte entre "semble propre" et "est propre". Quelques assertions détectent les échecs qui empoisonnent silencieusement un modèle : doublons survivants, nombres hors plage, nulls dans des colonnes qui devraient être complètes. Exécutez-les sur chaque lot et arrêtez le pipeline quand l'une échoue.

python
def validate(frame):
    assert frame["product_id"].is_unique, "duplicate product_id"
    assert frame["price"].between(0, 100000).all(), "price out of range"
    assert frame["rating"].between(0, 5).all(), "rating out of range"
    assert frame[["product_id", "price"]].notna().all().all(), "unexpected nulls"
    return frame

df = validate(df)

Les vérifications de plage valent leur place : une note de 50 sur une échelle de 0 à 5 ou un prix négatif est presque toujours un bug d'analyse d'une étape précédente, et l'assertion le fait apparaître avant que les données n'atteignent un modèle. Si vous dépassez les assertions manuelles, une bibliothèque de validation de schéma comme Pandera ou Great Expectations exprime les mêmes règles de façon déclarative, mais les assertions ci-dessus suffisent à rendre un pipeline fiable.

Exporter le jeu de données propre

Avec le frame dédupliqué, normalisé, imputé, casté selon le schéma, et validé, écrivez-le dans un format qui préserve vos types. CSV est portable mais typé en chaînes ; Parquet conserve les dtypes, compresse bien et se charge plus vite, ce qui importe une fois que vous faites de l'entraînement de modèle AI sur le résultat.

python
# Parquet preserves dtypes and is fast to reload
df.to_parquet("clean_products.parquet", index=False)

# CSV if you need maximum portability
df.to_csv("clean_products.csv", index=False)

Ce fichier est maintenant un jeu de données, pas un scrape : une ligne par entité, un type par colonne, aucun doublon survivant, valeurs manquantes traitées délibérément, et chaque valeur dans sa plage déclarée. De là, le chemin vers un modèle est le familier : feature engineering et split train/test, et les données en dessous ne vous surprendront pas.

Laisser la source renvoyer des données plus propres

L'étape de nettoyage la plus rapide est celle que vous sautez parce que les données sont arrivées propres. Beaucoup du travail ci-dessus (désescapage d'entités, suppression de balises, réduction d'espaces) n'existe que parce que vous avez scrapé du HTML brut. La Crawling API peut renvoyer une vue propre en style markdown d'une page pour que le texte arrive sans balises ni texte générique, et la Crawling API analyse automatiquement de nombreux sites populaires en champs JSON structurés, ce qui supprime l'écriture de sélecteurs et la majeure partie des suppositions de types avant que pandas ne voie les données. Quand vous avez besoin d'adresses IP résidentielles rotatives sans gérer un pool, le Smart AI Proxy couvre cet aspect.

Rien de cela ne supprime le besoin de dédupliquer, valider et appliquer un schéma (ce sont des propriétés de votre jeu de données, pas de la page), mais cela réduit la première moitié bruyante du travail. Pour où va ce jeu de données ensuite, voir comment fonctionne l'extraction de données AI et, pour la capture à volume élevé, les schémas dans le scraping web e-commerce.

Récapitulatif

Points clés

  • Dédupliquer d'abord. Supprimez les doublons avant toute statistique, sinon les moyennes imputées sont calculées sur des comptages gonflés et biaisés.
  • Normaliser les types et formats. Retirez les symboles monétaires vers des floats, analysez les dates mixtes avec errors="coerce", et nettoyez et mettez en minuscules les catégorielles.
  • Gérer les valeurs manquantes par colonne. Supprimez les lignes manquant d'un champ essentiel, imputez les numériques avec la médiane, et remplissez les catégorielles avec un sentinel "unknown" explicite.
  • Nettoyer le texte avant de tokeniser. Désescapez les entités, supprimez les balises et URLs, puis réduisez les espaces, dans cet ordre.
  • Définir un schéma et valider. Castez chaque colonne vers un type déclaré et vérifiez l'unicité, les plages et le non-null sur chaque lot pour que les mauvaises données échouent bruyamment.
  • Une entrée plus propre signifie moins de travail. Crawlbase peut renvoyer une sortie propre ou markdown et du JSON auto-analysé, réduisant l'étape de nettoyage avant que pandas ne s'exécute.

Foire aux questions

Pourquoi le nettoyage des données est-il si important avant d'entraîner un modèle AI ?

Parce que les modèles apprennent ce qui est dans les données, y compris les erreurs. Les doublons sur-pondèrent certains exemples, les prix typés en chaînes ne peuvent pas être comparés, les formats de date mixtes se fragmentent en valeurs non liées, et les cellules manquantes sont mal lues par les encodeurs. Aucun de ces problèmes ne génère d'erreur, donc le pipeline s'exécute et le modèle s'entraîne silencieusement sur une vue corrompue. Le nettoyage supprime ces dommages pour que le modèle apprenne le signal plutôt que le bruit.

Dois-je dédupliquer ou gérer les valeurs manquantes en premier ?

Dédupliquer d'abord. Si vous imputez avant de supprimer les doublons, chaque moyenne avec laquelle vous remplissez est calculée sur des comptages gonflés et biaisée vers les entités les plus dupliquées. Supprimez les doublons exacts et regroupez sur une clé métier (en gardant la capture la plus récente), puis calculez les médianes et modes que vous utilisez pour l'imputation.

Comment décider entre supprimer des lignes et imputer les valeurs manquantes ?

Décidez par colonne. Supprimez la ligne quand le champ manquant est celui sans lequel l'enregistrement ne peut pas exister, comme une clé primaire ou la valeur cible, et que les lacunes sont rares. Imputez quand la colonne est nécessaire en aval et qu'une estimation défendable existe : médiane pour les numériques car elle résiste aux valeurs aberrantes, et un sentinel "unknown" explicite pour les catégorielles pour que l'absence soit enregistrée plutôt que devinée.

Quel est le nettoyage de texte minimum dont les données scrapées ont besoin ?

Désescapez les entités HTML, supprimez les balises et URLs résiduelles, et réduisez les séquences d'espaces en espaces simples, dans cet ordre, puis mettez en minuscules. Le texte scrapé contient régulièrement &amp;, du balisage parasite et des espaces irréguliers qui deviendraient sinon des tokens bruyants. Cette passe est suffisante avant la tokenisation par espaces ; pour la NLP en production, vous remplaceriez ensuite par un tokeniseur dédié.

Pourquoi se donner la peine d'appliquer un schéma si les données sont déjà propres ?

Parce que le prochain lot ne sera pas propre sauf si quelque chose l'y force. Un schéma déclare un type par colonne et caste chaque lot vers celui-ci, de sorte qu'un scrape qui dérive (une nouvelle colonne, un prix qui ne s'analyse soudainement plus) échoue bruyamment au lieu de réintroduire silencieusement le désordre que vous venez de supprimer. Cela transforme le nettoyage d'une tâche ponctuelle en un contrat répétable.

Crawlbase peut-il réduire la quantité de nettoyage que je dois faire ?

Oui, pour la première moitié bruyante. La Crawling API peut renvoyer une vue propre en style markdown d'une page pour que le texte arrive sans balises ni texte générique, et la Scraper API analyse automatiquement de nombreux sites populaires en JSON structuré, ce qui supprime l'écriture de sélecteurs et la majeure partie des suppositions de types. Vous déduplication, validez et appliquez un schéma vous-même, car ce sont des propriétés de votre jeu de données plutôt que de la page source.

Commencer à construire

Crawlez n'importe quel site à grande échelle, sans combattre l'infrastructure.

Crawlbase gère les proxies, les empreintes et les CAPTCHA afin que votre équipe livre des pipelines de données au lieu de maintenir la plomberie de crawl. 1 000 requêtes gratuites, sans carte requise.

En libre-service · Sans appel commercial requis · Volumes de crawl entreprise disponibles