Une capture de page vide devrait produire un échec d'extraction. Dans notre test, elle a produit un ensemble de salle à manger Mainstays 5 pièces à 149 $, avec 4,5 étoiles et 120 avis. L'URL Walmart vendait en réalité un multicuiseur électrique à 59 $. L'enregistrement fabriqué par le modèle a passé notre JSON Schema, alors que le scraper Walmart intégré a renvoyé le multicuiseur lors des trois exécutions.

Cet exemple illustre un problème de l'extraction de données structurées : un JSON bien formé peut tout de même décrire le mauvais produit. Pour mesurer la différence, nous avons testé trois voies sur 25 pages produit Amazon et 25 pages produit Walmart : un scraper Crawlbase nommé, autoparse=true, et du Markdown suivi d'un appel à GPT-4o-mini avec un JSON Schema strict. Chaque voie a été exécutée trois fois. Nous avons mesuré la couverture des champs, la répétabilité, les valeurs absentes du texte capturé, la latence, ainsi que le nombre de requêtes et de tokens de modèle.

Le dépôt compagnon contient l'implémentation, neuf scripts de démonstration, des fixtures hors ligne et des tests. Sa liste d'URL définit les candidats et les réserves, et les résultats enregistrés conservent les mesures des exécutions du 2 novembre 2026.

En bref
  • Les deux voies de parseur ont passé le schéma applicatif sur 297 des 300 tentatives sans aucun token de modèle. La voie Markdown plus GPT-4o-mini a été acceptée sur 130 des 150.
  • La sortie structurée stricte a supprimé les erreurs de parsing, pas l'invention. Le modèle a renvoyé un objet complet pour chaque contrôle anti-bot et chaque corps vide qu'on lui a soumis, et l'un de ces objets a passé le schéma.
  • Essayez d'abord le parseur maintenu, normalisez, validez contre votre propre contrat, et n'appelez le modèle que pour les enregistrements qui échouent. Le routeur du dépôt fait exactement cela.

Choisir comment la page devient du JSON

La Crawling API prend en charge à la fois la récupération et l'extraction maintenue. Pour un flux de travail d'API de scraping Amazon, envoyer scraper=amazon-product-details renvoie le JSON du produit. Walmart utilise scraper=walmart-product-details. Avec autoparse=true, l'API détecte un type de page pris en charge et applique le scraper correspondant.

Les premiers sondages ont renvoyé le même objet produit via autoparse et le scraper nommé. Quand aucun scraper pris en charge ne correspond, Crawlbase se rabat sur le generic-extractor, qui renvoie des données de page génériques. Il s'agit toujours de requêtes distinctes : le benchmark présenté plus loin comporte des différences dans les valeurs renvoyées et des délais d'attente dépassés. L'extraction déterministe décrit l'approche de parsing ; elle ne rend pas immuable une page de détaillant en ligne ni chaque réponse.

Les deux voies partent du même endpoint de la Crawling API. Une seule d'entre elles place un modèle sur chaque page.

La route LLM récupère la page avec format=md, puis envoie le Markdown, l'URL du produit, les instructions et un schéma à huit champs à OpenAI. Sur une tentative réussie non mise en cache, cela ajoute un appel de modèle au crawl. La vue d'ensemble de l'extraction de données par IA explique ce schéma récupérer puis extraire ; ici, nous le comparons aux parseurs produit maintenus en utilisant la même API d'extraction de données pour l'acquisition.

Un sondage a aussi testé scraper= combiné à format=md sur Amazon. La réponse ne contenait que le JSON du scraper. Le benchmark récupère donc séparément les données parsées et le Markdown. Cela compte pour interpréter ses vérifications de preuves : le modèle est vérifié contre sa propre entrée capturée, alors que chaque parseur est vérifié contre un autre crawl de la même URL.

Définir un seul enregistrement avant de comparer les extracteurs

Les trois voies ont besoin d'une représentation commune avant que leurs sorties puissent être comparées. Le schéma partagé et les mappings définissent huit champs : title, id, brand, price, currency, availability, rating, et reviews_count.

Les réponses réelles des scrapers utilisaient des clés différentes :

Champ partagé Réponse Amazon Réponse Walmart
title name title
id ASIN dans productInformation, avec un repli sur l'URL dans le mapper itemId
brand brand brand
price rawPrice, avec repli sur price price
currency currency currency
availability inStock Aucun champ de stock dans les réponses enregistrées
rating customerReview ratings
reviews_count customerReviewCount reviewsCount

Ces clés observées différaient des noms documentés utilisés pendant le développement, les mappings suivent donc les payloads enregistrés. La disponibilité Walmart reste null quand aucun champ de stock n'est fourni. Le mapper gère aussi outOfStock ou availability si l'un des deux apparaît dans une réponse.

L'extraction par schéma LLM utilise un contrat de sortie distinct. Chaque clé doit être présente, mais chaque valeur peut être un null JSON. Le prompt demande de n'utiliser que les valeurs imprimées sur la page, et la description de chaque champ indique au modèle de renvoyer null quand la valeur est absente :

python
MAX_MARKDOWN_CHARS = 60_000

SYSTEM_PROMPT = (
    "You extract one product record from a retail product page converted to Markdown. "
    "Use only values printed on the page. If a value is not on the page, return null. "
    "Do not guess, convert, or fill in values from general knowledge."
)

Le client LLM demande une sortie structurée stricte à température 0 et envoie les 60 000 premiers caractères du Markdown :

python
response = self.client.chat.completions.create(
    model=self.model,
    temperature=0,
    messages=[
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": f"URL: {url}\n\n{page}"},
    ],
    response_format={
        "type": "json_schema",
        "json_schema": {"name": "product_v1", "strict": True, "schema": LLM_SCHEMA},
    },
)

Après l'extraction, l'application normalise le résultat et le valide contre product.v1. Ce schéma d'acceptation exige un titre et un identifiant d'au moins trois caractères, des prix et des compteurs non négatifs, des notes de 0 à 5, et une valeur de stock autorisée ou null. Un prix numérique exige une chaîne de devise composée de trois lettres majuscules :

python
PRODUCT_SCHEMA = {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": SCHEMA_VERSION,
    "type": "object",
    "required": FIELDS + ["_meta"],
    "additionalProperties": False,
    "properties": {
        "title": {"type": "string", "minLength": 3},
        "id": {"type": "string", "minLength": 3},
        "brand": {"type": ["string", "null"]},
        "price": {"type": ["number", "null"], "minimum": 0},
        "currency": {"type": ["string", "null"], "pattern": "^[A-Z]{3}$"},
        "availability": {"enum": AVAILABILITY + [None]},
        "rating": {"type": ["number", "null"], "minimum": 0, "maximum": 5},
        "reviews_count": {"type": ["integer", "null"], "minimum": 0},
        "_meta": {
            "type": "object",
            "required": ["url", "path", "extractor", "schema_version"],
            "properties": {
                "url": {"type": "string"},
                "path": {"enum": ["scraper", "autoparse", "markdown_llm"]},
                "extractor": {"type": "string"},
                "schema_version": {"const": SCHEMA_VERSION},
            },
        },
    },
    "allOf": [
        {"if": {"properties": {"price": {"type": "number"}}, "required": ["price"]},
         "then": {"properties": {"currency": {"type": "string"}}}},
    ],
}

Le code normalise les symboles monétaires courants, mais ce motif seul ne vérifie pas l'appartenance à la liste des devises ISO 4217. La distinction entre ces deux schémas explique les résultats : le modèle peut renvoyer un objet nullable complet et structurellement valide que l'application rejette ensuite parce que son titre est manquant.

Comparer 50 pages sur trois exécutions

Le harnais de benchmark part de 25 URL Amazon en /dp/ et de 25 URL Walmart en /ip/ ainsi que de réserves. Le Markdown de la première exécution de chaque URL sélectionnée doit dépasser 1 500 caractères et contenir son identifiant produit. Cela applique la même règle d'entrée avant de mesurer les trois voies, même si cela signifie aussi que le jeu de données commence avec des pages qui ont fourni un Markdown exploitable.

La sélection a remplacé 28 candidats. Amazon en a fourni sept : six pages 404 et un article indisponible dont la page ne mentionnait pas son ASIN. Walmart en a fourni 21 : six contrôles anti-bot, dix murs de consentement, deux corps vides, deux pages introuvables et une page qui a échoué à la règle d'entrée. Cinq candidats Walmart et toutes les réserves Walmart provenaient d'une seule liste de résultats organiques walmart-serp pour la recherche "kitchen essentials".

Les trois exécutions ont eu lieu à la suite le 2 novembre 2026. La matrice publiée contient 450 résultats de voie : 50 URL multipliées par trois voies et trois exécutions. Parmi ces résultats retenus, 412 ont utilisé le Normal token et 38 le JavaScript token après une nouvelle tentative. Ces totaux décrivent les résultats enregistrés ; les requêtes de sélection d'URL et les tentatives supplémentaires s'ajoutent au travail réseau sous-jacent.

Les dépendances Python épinglent le SDK Crawlbase 1.1.0. Les appels LLM enregistrés ont demandé gpt-4o-mini et ont renvoyé gpt-4o-mini-2024-07-18.

Le client de crawl fixe un délai d'attente client de 120 secondes. Quand un JavaScript token est configuré, il autorise une bascule après un cb_status 207 ou 503, ou après une réponse réussie contenant un challenge reconnu, un corps Markdown presque vide ou un résultat de parseur sans titre. Les réponses 404 et 410 d'origine sont exclues de cette nouvelle tentative. Les exceptions de transport sont enregistrées comme des échecs plutôt que retentées par ce client :

python
TIMEOUT_S = 120
RETRY_ON_JS = {"207", "503"}
# A 200 from the API can still carry an interstitial instead of the product page.
CHALLENGE_MARKERS = ("robot or human", "health information consent", "enter the characters you see below")
MIN_MARKDOWN_CHARS = 40


def unusable(result: "CrawlResult") -> bool:
    """Same rule for every path: no product content in an otherwise successful response."""
    if result.cb_status != "200" or result.error:
        return False
    if result.path == "markdown":
        text = (result.markdown or "").lower()
        return len(text) < MIN_MARKDOWN_CHARS or any(m in text for m in CHALLENGE_MARKERS)
    body = result.data.get("body") if isinstance((result.data or {}).get("body"), dict) else result.data
    return not (isinstance(body, dict) and (body.get("name") or body.get("title")))

Le module de métriques mesure plusieurs propriétés distinctes. Le taux de remplissage est la couverture moyenne non nulle sur les huit champs. Les tentatives qui ne produisent aucun enregistrement comptent zéro champ rempli ; les enregistrements rejetés par le schéma applicatif comptent tout de même leurs champs non nuls. La cohérence exige que l'enregistrement normalisé à huit champs soit identique sur les trois exécutions ; les métadonnées sont exclues. L'acceptation par le schéma compte les enregistrements qui passent product.v1, et la latence couvre la durée du crawl (les deux tentatives quand une nouvelle tentative JavaScript a eu lieu) plus la durée du modèle pour la voie LLM.

Pour la vérification contre le texte de la page, les valeurs du LLM sont comparées au préfixe Markdown qu'il a reçu, l'URL servant aussi de preuve pour l'identifiant produit. Les valeurs des parseurs sont comparées au crawl Markdown séparé de la même exécution, et la vérification est ignorée quand cette capture échoue à la règle de page produit. Le benchmark désactive volontairement la barrière de contenu Markdown du routeur afin de mesurer ce que fait le modèle avec les contrôles anti-bot et les pages vides.

Lire les résultats mesurés

Mesure Scraper intégré autoparse Markdown + gpt-4o-mini
Taux de remplissage moyen, Amazon 88.3% 89.0% 90.5%
Taux de remplissage moyen, Walmart 83.0% 86.5% 74.2%
Identique sur les 3 exécutions, Amazon 12 sur 25 13 sur 25 12 sur 25
Identique sur les 3 exécutions, Walmart 19 sur 25 17 sur 25 2 sur 25
Valeurs absentes du texte de la page, Amazon 36 sur 522 26 sur 526 10 sur 543
Valeurs absentes du texte de la page, Walmart 6 sur 353 10 sur 374 21 sur 445
Enregistrements ayant passé le schéma 147 sur 150 150 sur 150 130 sur 150
Latence médiane par page 13,9 s 15,6 s 13,4 s
Tokens de modèle 0 0 1 803 338
Chiffres par domaine issus des résultats enregistrés. La barre de cohérence Walmart est l'endroit où les trois voies se séparent.

Les deux voies de parseur ont passé le schéma sur 297 des 300 tentatives sans tokens de modèle. Les trois échecs du scraper nommé étaient des appels Walmart qui ont atteint le délai d'attente client. Le LLM a renvoyé un objet à chaque tentative, mais la normalisation et la validation applicative ont accepté 130 des 150 enregistrements.

Walmart a montré la plus grande séparation. Sur ses 75 captures Markdown, 21 contenaient un contrôle anti-bot, un mur de consentement ou un corps vide. Les 21 se trouvaient parmi les 50 captures des exécutions deux et trois, après que la sélection de la première exécution avait admis les URL. Le modèle a tout de même produit un objet pour chacune. Quinze titres étaient la chaîne "null", et 16 réponses ont fourni in_stock sans texte de stock à l'appui. L'enregistrement fabriqué de l'ensemble de salle à manger de l'introduction était la seule capture Walmart hors page produit à passer encore le schéma applicatif après normalisation.

Amazon demande une lecture différente. Le LLM avait une couverture des champs légèrement supérieure, et sa vérification contre le texte de la page a signalé moins de valeurs que l'un ou l'autre des parseurs. Pour 27 des 36 valeurs signalées du scraper nommé, le modèle a renvoyé null pour ce champ en lisant le Markdown de la même exécution. Les captures enregistrées comprenaient des mises en page de type liste sans buy box, de sorte que certaines divergences reflétaient un contenu différent renvoyé par des crawls distincts d'une même URL.

Le prix, la note et le nombre d'avis ont changé entre les exécutions sur 11 ou 12 URL Amazon pour chaque voie de parseur, et sur 7 à 11 URL pour la voie modèle. Les titres du modèle ont changé sur cinq URL Amazon ; ceux des parseurs sur aucune. Ces observations montrent pourquoi la cohérence doit être interprétée avec les entrées capturées. Le Markdown complet d'aucune URL n'était identique au byte près sur les trois exécutions, l'expérience n'a donc pas isolé l'aléa du modèle de la variation des pages. Parmi les neuf URL Walmart disposant d'un Markdown de page produit à chaque exécution, l'enregistrement LLM était identique pour deux d'entre elles et celui du scraper nommé pour sept.

La latence LLM plus faible sur Walmart inclut aussi des entrées courtes : interstitiels et corps vides. Une entrée en échec plus rapide ne prouve pas une extraction produit réussie plus rapide. De même, la médiane rapportée est par tentative, et non un temps par enregistrement accepté ni un débit total de lot.

Vérifier ce que signifient réellement les valeurs renvoyées

Le vérificateur de texte de page cherche une preuve normalisée de chaque valeur. Il attrape à la fois les inventions du modèle et les divergences des parseurs. Sur une page Walmart, le scraper nommé a renvoyé reviewsCount avec la valeur "122" alors que la page affichait 12 292 ; autoparse a renvoyé "12292". Sur une autre page, les deux voies ont échangé leurs résultats. La couverture des champs seule comptait ce nombre incorrect comme rempli.

python
def is_supported(field: str, value, text: str) -> bool:
    if value is None:
        return True
    if field in ("title", "brand", "id"):
        return _has_text(text, str(value))
    if field == "price":
        return _has_number(text, float(value))
    if field == "reviews_count":
        return _has_number(text, float(value)) or _has_abbreviated(text, float(value))
    if field == "rating":
        return _has_rating(text, float(value))
    if field == "currency":
        return any(cue in text for cue in CURRENCY_CUES.get(value, (value.lower(),)))
    if field == "availability":
        cues = IN_STOCK_CUES if value == "in_stock" else OUT_OF_STOCK_CUES
        return any(cue in text for cue in cues)
    return True


def invented_fields(record: dict, markdown: str, fields: list[str], url: str = "") -> list[str]:
    """The URL counts as input: an id taken from it is not invented."""
    text = page_text(markdown) + " " + url.lower()
    return [f for f in fields if record.get(f) is not None and not is_supported(f, record[f], text)]

Une vérification manuelle de trois pages Amazon et deux pages Walmart a conduit à quatre affinements. Les compteurs abrégés sont acceptés à leur précision affichée : 27 406 correspond à "27.4K", et 14 084 à "14K". La correspondance de texte ignore les espaces, ce qui permet à "HeroCosmetics" de correspondre à "Hero Cosmetics". Le "Pickup as soon as 3pm" de Walmart compte comme un indice de disponibilité en stock. Les notes Walmart sont exclues de la vérification de preuve pour toutes les voies, car le Markdown capturé montrait les notes d'autres produits plutôt que celle de l'article lui-même.

Cette exclusion ne retire pas les notes du calcul du taux de remplissage ni des totaux non nuls utilisés comme dénominateur du taux de preuve. Les décomptes rapportés et les pourcentages du graphique doivent donc être lus comme les mesures de support textuel de ce harnais, et non comme un score d'exactitude sur un ensemble de champs vérifiés à l'identique.

Deux désaccords vérifiés à la main illustrent la limite restante. Le vérificateur a accepté out_of_stock alors que seule une offre open-box était indisponible, et a accepté un nombre d'avis de 30 parce qu'un "30" sans rapport figurait ailleurs sur la page. La présence dans le texte établit qu'une valeur apparaît quelque part dans l'entrée ; l'associer au bon produit, à la bonne offre et au bon champ demande du contexte.

La sortie stricte a résolu un autre problème. Les 150 appels de modèle n'ont produit aucune erreur de parsing JSON ni objet partiel, mais la structure stricte n'a pas empêché les valeurs de stock non étayées ni un produit fabriqué de toutes pièces. Exiger chaque clé autorise toujours un null JSON honnête. L'échec s'est produit quand le modèle a rempli un champ sans preuve, pas parce que le schéma exigeait une réponse non nulle.

Pour des flux de travail pilotés par schéma apparentés, Firecrawl Extract propose une interface d'extraction dont la documentation présente désormais /agent comme son successeur. Ses recommandations pour le mode JSON préconisent une gestion explicite des null, des prompts ciblés, des schémas plus petits quand le nombre de champs atteint environ 30 ou plus, et de retirer les contraintes minItems et maxItems sur les tableaux, qui ne contrôlent pas la quantité de contenu collecté. La voie LLM mesurée dans cet article est un appel OpenAI direct sur du Markdown Crawlbase.

Les rapports de bugs montrent aussi pourquoi les échecs de sortie demandent un diagnostic séparé. Les rapports Firecrawl #1836 et #3300 concernent la gestion de Zod vers JSON Schema qui produisait des données vides ou manquantes. Le rapport #2491 concerne une intégration de modèle auto-hébergé qui ne générait aucun objet. Ces exemples décrivent des échecs de schéma et d'intégration ; ils ne mesurent pas la fréquence des hallucinations et n'établissent pas que le mode strict les corrigerait.

Normaliser et valider avant de se rabattre

Une fois les modes de défaillance visibles, la règle de routage devient simple : essayer le parseur pris en charge, normaliser sa sortie, et ne l'accepter que si elle respecte le contrat de l'application. Le normaliseur convertit les chaînes de substitution telles que "null", "none", et "n/a" en null, parse les prix et les compteurs abrégés, nettoie les libellés de marque, et mappe le texte de stock vers in_stock, out_of_stock, ou null.

Convertir les titres "null" en vraies valeurs null a transformé 15 titres bidon en rejets par le schéma applicatif. Le normaliseur efface aussi la devise quand le prix est absent. Ses formats numériques pris en charge couvrent les exemples de cette expérience, dont $1,299.99 et €39,90. D'autres locales nécessitent des règles de parsing explicites.

Le premier enregistrement accepté l'emporte. Un enregistrement rejeté passe à la voie suivante, et le modèle ne s'exécute que lorsqu'aucun parseur n'est passé.

Le routeur packagé renvoie le premier résultat accepté :

python
def route(url: str, crawl: CrawlClient, llm: LLMExtractor, run: int = 1) -> Extraction:
    """First accepted record wins. A rejected record falls through to the next path."""
    attempts = []
    if scraper_for(url):
        attempts.append(lambda: via_scraper(url, crawl, run))
    attempts.append(lambda: via_autoparse(url, crawl, run))
    attempts.append(lambda: via_markdown_llm(url, crawl, llm, run))
    result = None
    for attempt in attempts:
        result = attempt()
        if result.accepted:
            return result
    return result

Chaque fonction via_* renvoie un objet de classe Extraction à l'appelant. Une extraction réussie est suivie d'une normalisation, d'un marquage des métadonnées et d'une validation JSON Schema. Ici, accepted signifie qu'un enregistrement existe et n'a aucune erreur de validation. Si toutes les voies échouent, le routeur renvoie le dernier résultat rejeté, et write_record() n'écrit rien.

Avant un appel de modèle, via_markdown_llm rejette le Markdown de moins de 40 caractères ou contenant l'une des trois phrases de challenge reconnues. Il renvoie not_a_product_page dans ces cas :

python
def via_markdown_llm(url: str, crawl: CrawlClient, llm: LLMExtractor, run: int = 1, gate: bool = True) -> Extraction:
    res = crawl.fetch(url, "markdown", run)
    extractor = f"openai:{llm.model}"
    if not res.ok:
        return Extraction(url, "markdown_llm", extractor, None, [res.error or f"cb_status {res.cb_status}"], res.to_dict())
    if gate and unusable(res):
        # A 200 that carries a challenge or an empty body never reaches the model.
        return Extraction(url, "markdown_llm", extractor, None, ["not_a_product_page"], res.to_dict())
    out = llm.extract(url, res.markdown, run)
    if not out.ok:
        return Extraction(url, "markdown_llm", extractor, None, [out.error], res.to_dict(), out.to_dict())
    return _finish(url, "markdown_llm", f"openai:{out.model or llm.model}", out.data, res.to_dict(), out.to_dict())

Appliquée aux captures Walmart enregistrées, cette barrière aurait tenu les 21 pages inexploitables à l'écart du modèle. C'est une vérification de contenu ciblée, pas un détecteur complet de chaque interstitiel ou de chaque réponse de mauvaise page.

La décision d'acceptation du routeur n'exécute pas le vérificateur de texte de page du benchmark. Ajoutez des vérifications de preuve contextuelles à votre propre politique d'acceptation lorsqu'un prix ou une valeur de stock valide selon le schéma mais incorrect affecterait des décisions en aval. Cela garde la validation structurelle et la vérification factuelle comme des étapes d'implémentation explicites.

Les enregistrements acceptés incluent _meta.url, _meta.path, _meta.extractor, et _meta.schema_version. Les exemples incluent crawlbase:amazon-product-details, openai:gpt-4o-mini-2024-07-18, et product.v1. Ils identifient la route et le nom du modèle ou du scraper ; le nom du scraper seul n'identifie pas une révision particulière du parseur.

Les scripts de démonstration exposent chaque étape séparément : appel au scraper intégré, appel autoparse, extraction LLM, et comparaison sur trois exécutions.

Crawlbase Crawling API

Un seul endpoint pour toute la route : un scraper produit maintenu, autoparse, ou du Markdown propre pour votre propre modèle, avec le JavaScript token quand une page a besoin d'un navigateur. Les requêtes en échec ne sont pas facturées. Commencez avec jusqu'à 5 000 requêtes gratuites, sans carte.

Consommation de tokens et cache

Le cache disque permet des analyses répétées sans répéter la même acquisition. Les entrées de crawl sont indexées par voie, numéro d'exécution et URL. Les entrées de modèle incluent le modèle demandé, le numéro d'exécution, l'URL et le hash du Markdown complet. Aucun des deux caches n'expire automatiquement, et la clé de modèle n'inclut ni le prompt ni le schéma. Effacez les entrées concernées avant de mesurer un contrat d'extraction révisé ou d'obtenir des pages fraîches.

Les résultats enregistrés ont été recalculés à partir des réponses sauvegardées. Les zéro appels d'API dans les métadonnées de cette invocation décrivent le rejeu, pas l'expérience d'origine. Recalculer ces mesures exige le cache des réponses sauvegardées ; le fichier de métriques publié ne contient pas à lui seul les captures brutes.

La taille des entrées reste un coût d'échelle distinct. Les 150 appels de modèle ont consommé 1 793 923 tokens de prompt et 9 415 tokens de complétion, soit 1 803 338 au total. Amazon a atteint en moyenne 22 131 tokens par appel ; Walmart 1 914. Quarante-neuf des 75 appels Amazon dépassaient le plafond de 60 000 caractères de Markdown et ont été tronqués. Relever ce plafond envoie plus de contexte ; l'abaisser risque de retirer des champs situés vers la fin d'une page.

Forme seulement. Les deux courbes croissent avec le nombre de pages ; la courbe du modèle croît aussi avec les tokens par page.

Le diagramme est illustratif. Avec une taille d'entrée moyenne fixe, les deux approches croissent linéairement avec le nombre de pages ; la voie modèle ajoute un traitement de tokens par page. Sa pente plus raide n'indique pas un ratio de prix ou de vitesse mesuré. Le benchmark compte les tentatives logiques de crawl et les tokens de modèle, alors que les coûts d'acquisition réels dépendent aussi des nouvelles tentatives, de la complexité du domaine, du rendu et du stockage.

La version v2.11.0 de Firecrawl décrit une autre façon de réduire le travail de modèle répété : deterministicJson génère un extracteur réutilisable et le met en cache par site. Ce mécanisme répond au même objectif d'ingénierie, amortir le travail d'extraction sur des pages répétées, même s'il n'a pas été une voie mesurée dans cette expérience.

Mettre la collecte en file d'attente et récupérer les résultats par RID

Pour les lots plus importants, l'étape d'acquisition peut se terminer indépendamment du processus qui soumet les URL. La démonstration asynchrone envoie async=true&store=true, reçoit immédiatement un identifiant de requête, puis récupère plus tard le résultat depuis Cloud Storage:

python
def submit(api, url: str) -> str | None:
    from urllib.parse import urlparse

    options = {"async": "true", "store": "true"}
    name = SCRAPERS.get(urlparse(url).hostname or "")
    options.update({"scraper": name} if name else {"autoparse": "true"})
    res = api.get(url, options)
    payload = res.get("json")
    if not isinstance(payload, dict):
        try:
            payload = json.loads(res.get("body") or b"{}")
        except ValueError:
            payload = {}
    return payload.get("rid")  # the SDK puts the async rid in res["json"], not res["rid"]


def fetch_stored(storage, rid: str, attempts: int = 12, wait_s: float = 10.0) -> dict | None:
    for _ in range(attempts):
        try:
            res = storage.get(rid, {"format": "json"})
        except KeyError:  # crawlbase 1.1.0 raises this while the rid is not stored yet
            res = {}
        if res.get("status_code") == 200:
            stored = res.get("json_body")
            if isinstance(stored, dict) and stored:
                return stored
            body = res.get("body") or b""
            if body:
                return {"body": body.decode("utf-8", "replace") if isinstance(body, bytes) else body}
        time.sleep(wait_s)
    return None

Avec le SDK 1.1.0, l'identifiant asynchrone est disponible dans res["json"]["rid"]. La démonstration interroge par RID, en traitant le KeyError observé du SDK pour un élément pas encore disponible comme un résultat en attente. Dans le test à deux URL, le résultat Amazon était lisible en moins de deux minutes. Walmart était absent aux deux premières vérifications et présent à une vérification ultérieure. Ce sont des observations de complétion enregistrées, pas un intervalle de livraison garanti.

Les helpers de lot acceptent aussi une URL de callback. Crawlbase envoie le résultat terminé en POST à cet endpoint, tandis que store=true en conserve une copie pour récupération. La démonstration des callbacks Zapier illustre ce schéma de livraison. L'exemple asynchrone mappe et normalise les payloads récupérés ; appliquez la même politique de validation applicative avant de les persister comme enregistrements produit acceptés.

Stocker une page ajoute 0,5 crédit au crawl qui l'a récupérée, comme indiqué sur la page des tarifs, et la page est conservée 30 jours. La récupération est gratuite. Incluez le stockage dans votre budget aux côtés du crawl et du travail de modèle.

La facturation dépend aussi du statut de la réponse. La référence de la Crawling API liste comme facturables les requêtes avec cb_status 200 ou 207 et original_status 200, 201, 301, 302, 303, 307, 308, 404 ou 410. Un blocage 503 reconnu n'est pas facturé. Les réponses de contrôle anti-bot de cette expérience sont arrivées en 200 réussis et ont été facturées comme tels, ce qui explique pourquoi l'inspection du corps restait nécessaire même après vérification du statut.

La documentation de l'API recommande la livraison asynchrone pour les lots de plus de quelques centaines d'URL et le Crawler pour les jobs à fort volume comme des millions d'URL. Pour des exigences de débit et de service sur mesure, notre offre entreprise constitue l'étape opérationnelle suivante.

Utiliser le modèle là où l'interprétation apporte de la valeur

Un parseur maintenu est un point de départ pratique pour les pages produit prises en charge. Il supprime l'appel de modèle par page et expose un ensemble de champs connu. L'expérience montre aussi le compromis : le stock Walmart n'était pas disponible via le parseur testé, et les nombres d'avis incorrects demandaient toujours une vérification. Choisissez la route d'extraction en fonction des champs dont votre application a réellement besoin.

Un modèle est utile pour les boutiques sans parseur correspondant, les documents irréguliers comme les fils de forum et les communiqués de presse, ou les recherches ponctuelles où construire un extracteur dédié coûterait plus cher que les appels de modèle. Ces documents ont besoin de leur propre schéma et de leurs propres mappings ; ce routeur implémente un contrat spécifique aux produits. Les tutoriels ChatGPT, Gemini, et Perplexity proposent des implémentations apparentées de type récupérer puis interpréter, tandis que le tutoriel d'extraction de prix Amazon applique le schéma aux données de vente au détail.

Les modèles peuvent aussi intervenir après l'extraction déterministe. Un enregistrement produit peut alimenter une classification dans une taxonomie personnalisée ou un rapprochement de candidats entre Walmart et Amazon. L'étiquetage des réclamations a besoin du texte des avis, fournissez donc aussi ce contenu depuis un scraper ou une capture appropriés. Chaque tâche peut recevoir les champs dont elle a besoin plutôt que la page entière.

Pour une démarche récurrente de veille tarifaire, collectez et normalisez d'abord le prix, puis ajoutez l'interprétation par le modèle là où c'est nécessaire. Le guide de scraping Amazon planifié couvre l'acquisition récurrente. Garder explicites l'acquisition, l'extraction des champs, la validation et l'interprétation rend le web scraping vers JSON plus facile à inspecter quand une valeur change.

Conclusion

Sur ces 50 pages produit, les voies de scraper maintenu ont produit 297 enregistrements acceptés par le schéma sur 300 tentatives sans tokens de modèle. Markdown plus GPT-4o-mini offrait une meilleure couverture des champs sur Amazon, mais les interstitiels Walmart ont exposé le risque d'accepter des enregistrements plausibles issus d'entrées inexploitables. Les résultats enregistrés plaident pour commencer avec un parseur disponible, vérifier les champs renvoyés, et réserver l'extraction par modèle aux lacunes que votre application doit combler.

La mise en œuvre complète fournit les mappings, le routeur, le benchmark et les exemples hors ligne. Pour l'essayer sur votre propre catalogue, créez un compte Crawlbase, configurez les tokens, et inspectez un petit ensemble d'enregistrements acceptés avant d'élargir l'exécution. Les pages scraper Amazon, scraper Walmart, et plus largement la vue d'ensemble du scraping e-commerce aident à identifier la route d'acquisition pour vos cibles.

Foire aux questions (FAQ)

Quels sites disposent de scrapers Crawlbase intégrés ?

La page index des scrapers liste les extracteurs pris en charge pour Amazon, Walmart, eBay, AliExpress, Best Buy, Google, Bing, Facebook, Instagram, TikTok, LinkedIn, Reddit, Booking.com, Airbnb et d'autres sites, plus des extracteurs génériques. Choisissez le scraper adapté au type de page et envoyez son nom via scraper=. Le routeur de domaines de l'exemple ne mappe que les pages produit Amazon et Walmart ; étendez-le pour d'autres cibles.

Est-ce que autoparse fonctionne sur n'importe quelle page ?

Avec autoparse=true, Crawlbase détecte le type de page et applique un scraper pris en charge correspondant. Quand aucun scraper pris en charge ne correspond, il se rabat sur le generic-extractor, qui renvoie des données de page génériques plutôt qu'un enregistrement produit propre au détaillant. Dans ce dépôt, l'application mappe le JSON renvoyé vers son schéma produit et le valide. Si ce processus ne produit pas d'enregistrement produit acceptable, l'application effectue une requête Markdown séparée et tente une extraction LLM. Ce repli supplémentaire relève de la logique de routage de l'application.

À quelle fréquence les scrapers intégrés cassent-ils, et qui les maintient ?

Crawlbase maintient ses scrapers intégrés et les met à jour quand des changements de site affectent l'extraction. Les cassures sont signalées automatiquement et généralement corrigées en quelques heures. Si vous remarquez des champs manquants, des valeurs incorrectes ou un problème qui persiste, contactez le support Crawlbase avec l'URL concernée et un exemple de réponse pour que l'équipe puisse enquêter.

La validation JSON Schema garantit-elle l'exactitude des données extraites ?

Non. JSON Schema vérifie la structure de l'enregistrement, les champs obligatoires et les contraintes de valeur. Il ne vérifie pas que les valeurs décrivent le bon produit. Vérifiez que la page capturée contient un contenu produit exploitable, puis contrôlez les valeurs importantes contre des preuves associées au bon produit et à la bonne offre.

Commencer à construire

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

Crawlbase gère les proxies, les empreintes et les CAPTCHA pour que votre équipe livre des pipelines de données au lieu de maintenir l'infrastructure de crawl. Jusqu'à 5 000 requêtes gratuites, sans carte bancaire requise.

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