Zoro.com est un grand distributeur de fournitures industrielles proposant des millions de produits : outillage, équipements, visserie, équipements de sécurité et pièces MRO, chaque fiche portant une marque, un prix, un numéro Zoro et un état de stock. Ces fiches constituent un signal public et fiable pour quiconque fait du suivi de prix, de la recherche de catalogue ou de l'analyse concurrentielle dans le secteur industriel, ce qui explique pourquoi les distributeurs, les équipes achats et les analystes les surveillent.
Ce guide vous montre comment extraire des données produits Zoro avec Python. Vous construisez un petit scraper fonctionnel qui récupère les pages de recherche et de produits Zoro via la Crawling API, analyse un enregistrement structuré pour chaque article, gère la pagination et exporte les résultats en JSON et CSV. L'ensemble de la procédure reste limité aux données de catalogue publiques : marques, titres, prix, numéros Zoro et disponibilité que tout le monde peut voir sur une page de recherche ou de produit sans être connecté.
Ce que vous allez construire
Un script Python qui prend une URL de recherche Zoro, récupère la page rendue via la Crawling API et extrait un enregistrement structuré par produit. Nous utilisons une recherche de boîte à outils comme exemple fil conducteur, la même requête qu'utilisait l'ancien tutoriel, et nous extrayons ces champs de chaque fiche :
- Marque le nom du fabricant affiché sur la fiche produit.
- Titre le nom du produit tel qu'il est référencé.
- Prix le prix affiché, quand le produit en indique un.
- Numéro Zoro l'identifiant de type SKU tiré de l'URL du produit, par exemple G6893443.
- Disponibilité l'état du stock, tel que « en stock » ou « en réapprovisionnement ».
- Lien l'URL absolue vers la page de détail du produit.
Pourquoi une requête simple échoue sur Zoro
Si vous pointez un client HTTP brut vers une URL de recherche Zoro, vous obtenez rarement la grille de produits escomptée. Deux facteurs jouent contre vous. Premièrement, Zoro rend une grande partie des fiches côté client : la page envoie une coquille légère et remplit les cartes produits au fur et à mesure de l'exécution du JavaScript, de sorte que le HTML initial contient souvent les articles que vous souhaitez analyser. Deuxièmement, Zoro détecte le trafic automatisé. Les plages d'adresses IP de datacenter et les schémas de requêtes qui ne ressemblent pas à un vrai navigateur se voient opposer une page de défi ou un blocage pur avant même d'atteindre les fiches.
Un scraper Zoro fonctionnel a donc besoin de deux choses en une seule requête : un navigateur qui rende la page et une adresse IP que Zoro perçoit comme un vrai acheteur. Vous pouvez assembler cela vous-même avec un navigateur sans interface graphique et un pool de proxies résidentiels rotatifs, mais maintenir cette infrastructure représente l'essentiel du travail. La Crawling API réunit ces deux aspects en un seul appel : vous lui envoyez l'URL, elle rend la page derrière une adresse IP résidentielle de confiance, gère la rotation et la résolution des CAPTCHA, puis vous renvoie le HTML final à analyser.
Prérequis
Quelques éléments doivent être en place avant d'écrire le moindre code. Aucun ne prend longtemps.
Python de base. Vous devez être à l'aise pour écrire et exécuter un script Python et installer des paquets avec pip. Si vous débutez avec ce langage, le guide sur le web scraping avec Python couvre le niveau présupposé par ce tutoriel.
Python 3.8 ou supérieur. Vérifiez votre version avec python --version (ou python3 --version). Si vous ne l'avez pas, installez-le depuis python.org et assurez-vous que Python est dans votre PATH système.
Un compte Crawlbase et un token. Créez un compte gratuit, ouvrez votre tableau de bord et copiez votre token depuis la page de documentation du compte. Le niveau gratuit comprend jusqu'à 5 000 requêtes sans carte bancaire, ce qui est largement suffisant pour construire et tester ce scraper. Comme Zoro est rendu avec JavaScript, utilisez votre token JavaScript pour ces requêtes. Traitez le token comme un mot de passe et ne le mettez pas dans le contrôle de version.
Mettre en place le projet
Créez un environnement virtuel pour isoler les dépendances du projet, puis installez les deux bibliothèques nécessaires au scraper. crawlbase est le client officiel de la Crawling API, et beautifulsoup4 analyse le HTML renvoyé pour extraire chaque champ des cartes produits via des sélecteurs CSS.
python --version python -m venv zoro_env source zoro_env/bin/activate pip install crawlbase beautifulsoup4
Sous Windows, activez l'environnement avec zoro_env\Scripts\activate au lieu de la commande source. Les deux bibliothèques installées, créez le fichier de script que le reste du guide complète progressivement :
touch zoro_scraper.py
Comprendre la page de recherche Zoro
Une recherche Zoro se trouve à une URL stable construite à partir du paramètre de requête q, par exemple https://www.zoro.com/search?q=tool+box, et pagine avec un paramètre page ajouté sous la forme &page=2. La page présente une grille de cartes produits, une par article, chacune portant les mêmes champs : une marque, un titre, un prix, une image miniature et un lien vers la page de détail du produit.
Avant d'écrire des sélecteurs, ouvrez une page de recherche Zoro dans votre navigateur, faites un clic droit sur une carte produit et choisissez Inspecter. Dans l'explorateur DOM, vous pouvez lire les sélecteurs CSS de chaque champ :
-
Nom de la marque dans un
<span>avec la classebrand-name. -
Titre du produit dans un
<div>avec la classeproduct-title. -
Prix dans un
<div>avec la classeprice. -
URL du produit dans le
hrefd'un<a>imbriqué dansdiv.product-title. -
Image du produit dans le
srcd'un<img>portantdata-za="product-image".
Les cartes produits elles-mêmes se trouvent dans un conteneur section[data-za="product-cards-list"], chacune étant un div.search-product-card, qui est la cible de la boucle. Le numéro Zoro n'a pas son propre élément sur la carte : c'est le segment SKU dans l'URL du produit (le G6893443 dans /i/G6893443/), vous le déduisez donc du lien plutôt que de le sélectionner directement.
Étape 1 : Récupérer la page de recherche rendue
Commencez par obtenir la page finale. Importez la classe CrawlingAPI, initialisez-la avec votre token, définissez l'URL de recherche et faites la requête. Vérifier le code de statut avant d'analyser permet de détecter les échecs clairement plutôt qu'en silence.
from crawlbase import CrawlingAPI api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) def crawl(page_url): options = {"ajax_wait": "true", "page_wait": 5000} response = api.get(page_url, options) if response["status_code"] == 200: return response["body"].decode("utf-8") print(f"Request failed: {response['status_code']}") return None if __name__ == "__main__": search_url = "https://www.zoro.com/search?q=tool+box" html = crawl(search_url) print(html[:500] if html else "No HTML returned")
Les deux options d'attente sont importantes pour une grille qui se remplit au chargement de la page. ajax_wait indique à l'API d'attendre la fin du contenu asynchrone, et page_wait maintient l'attente un nombre fixe de millisecondes (5000 ici, car les pages de fiches Zoro peuvent être lentes) pour que les cartes à rendu tardif apparaissent avant que la page ne soit capturée. Exécutez le script et vous devriez voir de vraies balises de fiche, pas une coquille de défi. Cela confirme que le rendu fonctionne avant même d'écrire un seul sélecteur.
La grille de produits de Zoro nécessite une page rendue derrière une adresse IP de confiance, en un seul appel. La Crawling API prend votre token, exécute la page de recherche dans un vrai navigateur avec les options ajax_wait et page_wait que vous venez de définir, effectue la rotation des adresses IP résidentielles côté serveur et gère la résolution des CAPTCHA, puis vous remet le HTML finalisé. Vous évitez de devoir gérer vous-même une flotte de navigateurs sans interface graphique et un pool de proxies. Pointez-la vers une URL de recherche sur le niveau gratuit de 5 000 requêtes d'abord.
Étape 2 : Analyser les cartes produits avec BeautifulSoup
Le HTML rendu en main, chargez-le dans BeautifulSoup, trouvez chaque carte produit et extrayez chaque champ par son sélecteur. Chaque carte se trouve dans le conteneur section[data-za="product-cards-list"] sous forme de div.search-product-card. Lisez la marque, le titre, le prix, l'image et le lien sur la carte, puis déduisez le numéro Zoro à partir du lien. Encapsulez chaque carte dans un try/except pour qu'une fiche mal formée ne fasse pas planter l'exécution.
import re from bs4 import BeautifulSoup BASE = "https://www.zoro.com" def text_of(card, selector): el = card.select_one(selector) return el.get_text(strip=True) if el else None def zoro_number(url): match = re.search(r"/i/(G\d+)/", url) return match.group(1) if match else None def scrape_listings(html): soup = BeautifulSoup(html, "html.parser") cards = soup.select('section[data-za="product-cards-list"] > div.search-product-card') results = [] for card in cards: try: anchor = card.select_one("div.product-title a") href = anchor["href"] if anchor else "" link = BASE + href if href.startswith("/") else href img = card.select_one('img[data-za="product-image"]') results.append({ "brand": text_of(card, "span.brand-name"), "title": text_of(card, "div.product-title"), "price": text_of(card, "div.price"), "zoro_number": zoro_number(link), "availability": text_of(card, "div.availability"), "image_url": img["src"] if img else None, "link": link, }) except Exception as e: print(f"Skipped a card: {e}") return results
L'assistant text_of interroge un élément à l'intérieur d'une carte et renvoie None quand il est absent, au lieu de lancer une exception sur un appel .get_text() sur rien. Cela rend l'extraction résiliente lorsqu'un champ est absent, ce qui est courant car toutes les cartes n'affichent pas un prix ou un badge de disponibilité. L'assistant zoro_number extrait le SKU préfixé G de l'URL du produit avec une courte expression régulière, et le lien est normalisé en URL absolue car Zoro sert un href relatif.
Les noms de classes comme brand-name, product-title et price peuvent changer quand Zoro remodèle son balisage, tandis que les marqueurs structurels comme l'attribut data-za="product-cards-list" tendent à être plus durables. Considérez les sélecteurs ci-dessus comme un modèle de départ, pas comme un contrat. Quand un champ revient None pour chaque carte, réinspectez la page de recherche en direct dans les outils de développement de votre navigateur et mettez à jour le sélecteur. La maintenance périodique des sélecteurs est normale pour tout scraper en production.
Étape 3 : Gérer la pagination et exporter en JSON et CSV
Une seule page de recherche est une démonstration ; un vrai travail parcourt l'ensemble des résultats. Zoro pagine avec le paramètre page, il suffit donc d'ajouter &page=N et de boucler jusqu'à ce qu'une page ne renvoie plus de cartes. Connectez maintenant la récupération, l'analyse et la boucle de pagination dans un script exécutable, puis écrivez les enregistrements en JSON et CSV pour pouvoir les charger dans un notebook ou un tableur.
import csv import json import re import time from crawlbase import CrawlingAPI from bs4 import BeautifulSoup api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) BASE = "https://www.zoro.com" FIELDS = ["brand", "title", "price", "zoro_number", "availability", "image_url", "link"] def crawl(page_url): options = {"ajax_wait": "true", "page_wait": 5000} response = api.get(page_url, options) if response["status_code"] == 200: return response["body"].decode("utf-8") print(f"Request failed: {response['status_code']}") return None def text_of(card, selector): el = card.select_one(selector) return el.get_text(strip=True) if el else None def zoro_number(url): match = re.search(r"/i/(G\d+)/", url) return match.group(1) if match else None def scrape_listings(html): soup = BeautifulSoup(html, "html.parser") cards = soup.select('section[data-za="product-cards-list"] > div.search-product-card') results = [] for card in cards: try: anchor = card.select_one("div.product-title a") href = anchor["href"] if anchor else "" link = BASE + href if href.startswith("/") else href img = card.select_one('img[data-za="product-image"]') results.append({ "brand": text_of(card, "span.brand-name"), "title": text_of(card, "div.product-title"), "price": text_of(card, "div.price"), "zoro_number": zoro_number(link), "availability": text_of(card, "div.availability"), "image_url": img["src"] if img else None, "link": link, }) except Exception as e: print(f"Skipped a card: {e}") return results def scrape_all_pages(search_url, max_pages=5): all_rows = [] for page in range(1, max_pages + 1): page_url = f"{search_url}&page={page}" print(f"Scraping page {page}...") html = crawl(page_url) if not html: break rows = scrape_listings(html) if not rows: print("No more products. Stopping.") break all_rows.extend(rows) time.sleep(2) return all_rows def export(rows, name="zoro_listings"): with open(f"{name}.json", "w", encoding="utf-8") as f: json.dump(rows, f, indent=2, ensure_ascii=False) with open(f"{name}.csv", "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=FIELDS) writer.writeheader() writer.writerows(rows) print(f"Saved {len(rows)} products to {name}.json and {name}.csv") def main(): search_url = "https://www.zoro.com/search?q=tool+box" rows = scrape_all_pages(search_url, max_pages=3) export(rows) if __name__ == "__main__": main()
Exécutez le script complet avec python zoro_scraper.py. Il parcourt jusqu'à max_pages pages de recherche, analyse une ligne par produit et écrit à la fois zoro_listings.json et zoro_listings.csv. La boucle scrape_all_pages s'arrête tôt quand une page ne renvoie aucune carte, et le time.sleep(2) entre les requêtes cadence l'exécution. La liste partagée FIELDS maintient l'ordre des colonnes CSV en accord avec les clés du dictionnaire, de sorte que les deux exports ne divergent jamais.
À quoi ressemble la sortie
Vous obtenez une liste propre d'enregistrements produits, dans l'ordre des pages, prête à être écrite en JSON, CSV ou en base de données.
[ { "brand": "Apex Tool Group", "title": "3 Drawer Tool Box", "price": "$149.99 /ea", "zoro_number": "G6893443", "availability": "In Stock", "image_url": "https://www.zoro.com/static/cms/product/prev/KDT83151xx1200.jpg", "link": "https://www.zoro.com/apex-tool-group-3-drawer-tool-box-83151/i/G6893443/" }, { "brand": "Dewalt", "title": "Rolling Tool Box, Plastic, Black, 28 in W", "price": "$43.19 /ea", "zoro_number": "G3778857", "availability": "In Stock", "image_url": "https://www.zoro.com/static/cms/product/prev/Z1wK0zqcpEx-.JPG", "link": "https://www.zoro.com/dewalt-rolling-tool-box-plastic-black-28-in-w-dwst28100/i/G3778857/" } ]
Extraire des pages de produits individuels
Le scraper de recherche vous donne la grille de fiches. Quand vous avez besoin de détails plus approfondis (une description complète, le tableau des spécifications, toutes les images), suivez le link de chaque enregistrement vers sa page produit et analysez-la. Zoro expose ces éléments sur une page produit :
-
Titre du produit dans un
<h1>avecdata-za="product-name". -
Prix dans un
<div>avecdata-za="product-price". -
Description dans
div.product-description div.description-text. -
Lignes de spécifications dans un
<table>à l'intérieur dediv.product-details-info, avec deux cellules<td>par ligne. -
Images du produit les balises
<img>avec la classeproduct-imageà l'intérieur dediv.product-images.
import re from bs4 import BeautifulSoup def clean(value): return re.sub(r"\s+", " ", value).strip() if value else None def scrape_product_page(product_url): html = crawl(product_url) if not html: return {} soup = BeautifulSoup(html, "html.parser") title = soup.select_one('h1[data-za="product-name"]') price = soup.select_one('div[data-za="product-price"]') desc = soup.select_one("div.product-description div.description-text") specs = {} for row in soup.select("div.product-details-info table tr"): cells = row.find_all("td") if len(cells) == 2: specs[clean(cells[0].text)] = clean(cells[1].text) images = [img["src"] for img in soup.select("div.product-images img.product-image") if img.get("src")] return { "title": clean(title.text) if title else None, "price": clean(price.text) if price else None, "zoro_number": zoro_number(product_url), "description": clean(desc.text) if desc else None, "specifications": specs, "image_urls": images, "url": product_url, }
Ce code réutilise les mêmes assistants crawl et zoro_number du scraper de fiches, vous pouvez donc lui passer n'importe quel link des résultats de recherche. L'assistant clean réduit les séquences d'espaces à un seul espace, ce qui est important pour les tableaux de spécifications et les descriptions multi-lignes de Zoro. Les spécifications reviennent sous forme d'un dictionnaire clé-valeur plat, une entrée par ligne de tableau à deux cellules, ce qui se charge proprement dans un DataFrame ou un ensemble de colonnes de base de données.
Rester non bloqué
Même avec le rendu géré, Zoro surveille le trafic qui ressemble à un scraper. Quelques bonnes habitudes maintiennent une exécution saine, et elles s'appliquent à n'importe quelle cible commerciale difficile.
- Cadencez vos requêtes. Espacez les requêtes avec un délai entre les pages plutôt que de tout crawler à pleine vitesse. Planifiez les tâches plus lourdes pendant les heures creuses pour alléger la charge sur les serveurs de Zoro.
- Misez sur la rotation. Un pool d'adresses IP résidentielles répartit les requêtes sur de nombreuses adresses d'utilisateurs réels, de sorte qu'aucune ne déclenche seule une limite de débit. La Crawling API gère cela pour vous ; si vous construisez votre propre infrastructure, c'est la partie à soigner.
- Ne conservez que ce dont vous avez besoin. Stockez les champs du catalogue que votre projet utilise et éliminez le reste. Revérifiez vos sélecteurs périodiquement pour que le scraper suive les changements de balisage.
Pour le guide complet sur l'évitement des blocages, voir comment scraper des sites web sans se faire bloquer. Si le suivi des prix est votre objectif, le guide sur le web scraping pour l'intelligence tarifaire montre comment transformer ces instantanés en flux de tendances, et la vue d'ensemble du web scraping e-commerce couvre le schéma général applicable aux sites de vente au détail. Pour une cible similaire dans le domaine des fournitures MRO et de bureau, voir comment scraper Office Depot.
Est-il légal de scraper Zoro ?
La légalité du scraping de Zoro dépend des conditions d'utilisation de Zoro, de votre juridiction et de ce que vous faites des données. Les conditions de Zoro imposent des limites à l'accès automatisé, de sorte que le scraping peut aller à l'encontre de ces conditions quelle que soit la précaution de votre outillage. Aucun code présenté ici ne change cela ; il se contente de faire fonctionner la partie technique. Lisez les conditions d'utilisation de Zoro et son robots.txt, et considérez-les tous deux comme la limite de ce que vous collectez. Pour un usage commercial ou concurrentiel, la situation juridique se complique, et consulter un expert juridique sur votre cas spécifique est la démarche sensée.
Quelques lignes directrices à suivre. Ne collectez que les données publiques : les marques, titres, prix, numéros Zoro, disponibilités et liens de fiches que tout le monde peut voir sur une page de recherche ou de produit sans compte. Maintenez votre volume de requêtes suffisamment bas pour ne pas surcharger les serveurs de Zoro, et évitez les données personnelles, y compris tout ce qui est lié à des acheteurs, critiques ou vendeurs identifiables au-delà de ce qui est publiquement affiché. Ne redistribuez pas en masse les photographies de produits ni les descriptions protégées par le droit d'auteur de Zoro. Si vous envisagez de réutiliser les données commercialement, obtenez une autorisation ou un accord officiel plutôt que de supposer que le silence vaut consentement.
Ce guide est délibérément limité aux pages de catalogue publiques parce que c'est la ligne qui rend le travail défendable. Il ne couvre rien derrière une connexion, les données de compte ou de commande, les informations personnelles, ni aucune tentative de contourner une authentification ou un CAPTCHA que vous n'êtes pas autorisé à passer. Si Zoro propose un flux de données commercial, un programme partenaire ou une API officielle pour votre cas d'utilisation, c'est le bon outil pour les grands volumes, la structure garantie ou les droits commerciaux. Si votre projet nécessite plus que les données de catalogue public, un canal officiel ou un accord de données est la voie correcte, pas un scraper plus élaboré.
Points clés
- Les fiches Zoro sont des données de catalogue public. Chaque page de recherche et de produit porte une marque, un titre, un prix, un numéro Zoro et une disponibilité, ce qui les rend si utiles pour le suivi des prix et la recherche sur les fournitures industrielles.
-
Vous avez besoin du rendu et d'une adresse IP de confiance ensemble. Zoro remplit sa grille de produits côté client et challenge le trafic automatisé, la Crawling API rend donc la page derrière une adresse IP résidentielle en un seul appel avec
ajax_waitetpage_wait. -
BeautifulSoup effectue l'extraction. Bouclez sur les cartes
div.search-product-card, mappez marque, titre, prix, disponibilité et lien vers les sélecteurs actuels, et déduisez le numéro Zoro de l'URL avec une courte expression régulière. -
Paginhez et exportez en JSON et CSV. Ajoutez
&page=Net bouclez jusqu'à ce qu'une page ne renvoie aucune carte ; une liste de champs partagée maintient les exports JSON et CSV synchronisés. - Restez sur les données publiques. Respectez les conditions d'utilisation et le robots.txt de Zoro, cadencez vos requêtes, préférez un flux officiel pour les données sous licence ou en volume, et ne touchez jamais aux comptes, commandes ou informations personnelles.
Foire aux questions
Pourquoi une requête simple ne renvoie-t-elle aucun produit de Zoro ?
Deux raisons. Zoro remplit une grande partie de sa grille de produits côté client au chargement de la page, de sorte qu'une requête brute obtient souvent une coquille sans les fiches. De plus, Zoro challenge ou bloque le trafic qui ne ressemble pas à un vrai navigateur. Rendre la page via la Crawling API derrière une adresse IP de confiance résout les deux problèmes, c'est pourquoi le scraper présenté ici y route sa requête avec ajax_wait et un page_wait de 5000 millisecondes.
Quels champs puis-je extraire d'une page de recherche Zoro ?
Sur chaque carte produit, vous pouvez lire la marque (span.brand-name), le titre (div.product-title), le prix (div.price), l'image miniature (img[data-za="product-image"]) et le lien produit. Le numéro Zoro est le SKU préfixé G dans ce lien, que vous extrayez avec une courte expression régulière. Les pages produits ajoutent une description complète, un tableau de spécifications et un ensemble d'images plus large.
Comment gérer la pagination sur Zoro ?
Zoro pagine avec le paramètre de requête page. Ajoutez &page=2, &page=3, etc. à l'URL de recherche et bouclez, en vous arrêtant quand une page ne renvoie aucune carte produit ou quand vous atteignez un nombre de pages maximal que vous avez défini. Ajoutez un court délai entre les requêtes pour ne pas crawler à pleine vitesse.
Comment obtenir le numéro Zoro d'un produit ?
Le numéro Zoro est le segment SKU dans l'URL du produit, le G6893443 dans /i/G6893443/. Le scraper le dérive avec une expression régulière sur le lien plutôt qu'en sélectionnant un élément séparé, ce qui le rend disponible aussi bien pour les enregistrements de résultats de recherche que pour les pages de produits individuels.
Puis-je stocker les données extraites en CSV plutôt qu'en JSON ?
Oui. Le script écrit les deux : un fichier JSON pour la structure imbriquée et un CSV pour les tableurs. Une liste partagée FIELDS pilote l'en-tête et l'ordre des colonnes CSV de sorte qu'il reste aligné avec les clés du dictionnaire. Vous pouvez également charger les enregistrements directement dans une base de données si vous le préférez.
Comment éviter de se faire bloquer en scrapant Zoro ?
Maintenez un faible taux de requêtes par adresse IP, ajoutez un délai entre les pages et routez via des adresses IP résidentielles rotatoires pour qu'aucune adresse unique ne déclenche une limite de débit. La Crawling API gère la rotation, un pool d'adresses IP de confiance et la gestion des CAPTCHA pour vous ; si vous construisez votre propre infrastructure, c'est là qu'il faut investir. Surveillez les codes de statut et reculez quand vous commencez à voir des défis.
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.
