[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"theme-db":3,"$fIk4v7armkCi6AHMgkGaMviQ6K2tRu9bjT-iZ-L5l7pQ":22,"$fKnz2vuX4bZz1LbUTiuFsvSZ3e07l5_5fqNYp4Tzdhi8":61,"megamenu":142,"$fZ105vVa-0Q-a1tsZuCh1SMLX2ZzhuBj12AFPSXbdI78":199,"footer-db":213,"header-db":231},{"theme":4},{"colors":5,"typography":13,"ui":17,"defaultColorMode":21},{"primary":6,"secondary":7,"background":8,"foreground":9,"muted":10,"headerBg":11,"footerBg":12,"topBarBg":9,"topBarText":11},"#4F46E5","#0D9488","#F9FAFB","#111827","#6B7280","#ffffff","#020617",{"fontFamily":14,"fontUrl":15,"baseFontSize":16},"Inter, system-ui, sans-serif","https:\u002F\u002Ffonts.googleapis.com\u002Fcss2?family=Inter:wght@400;500;600;700&family=Playfair+Display:ital,wght@0,400;0,700;0,800;0,900;1,400;1,700&display=swap","16px",{"borderRadius":18,"contentWidth":19,"shadow":20},"lg","7xl",true,"light",{"title":23,"slug":24,"metaDescription":25,"category":26,"tags":27,"difficulty":34,"psVersions":35,"content":39,"faq":40,"tldr":56,"readingTime":57,"generatedAt":58,"publishDate":58,"relatedArticles":59,"sourceCategory":60},"Extraire les données produits PrestaShop avec Scrapy (Python)","extraire-donnees-produits-prestashop-scrapy-python","Guide complet pour extraire et synchroniser les données produits PrestaShop avec Scrapy en Python. Scraping, export CSV, synchronisation de stocks.","api",[28,29,30,31,32,33],"scrapy","python","web-scraping","synchronisation-stocks","export-csv","automatisation","avance",[36,37,38],"1.6","1.7","8.x","\u003Ch2>Pourquoi extraire des données depuis un PrestaShop ?\u003C\u002Fh2>\n\u003Cp>Dans le quotidien d'un développeur e-commerce, certaines situations exigent de récupérer massivement des données depuis une boutique PrestaShop : migration vers une nouvelle instance, synchronisation de stocks entre deux sites, audit SEO des fiches produits, ou encore constitution d'un catalogue CSV pour un marketplace tiers.\u003C\u002Fp>\n\u003Cp>Si l'API Webservice de PrestaShop reste la voie royale pour accéder aux données de manière structurée, il arrive que celle-ci soit désactivée, mal configurée, ou que l'on doive travailler sur un site dont on n'a pas l'accès back-office. Dans ces cas précis, le \u003Cstrong>web scraping\u003C\u002Fstrong> avec un framework Python comme \u003Cstrong>Scrapy\u003C\u002Fstrong> offre une alternative robuste.\u003C\u002Fp>\n\u003Cblockquote>\u003Cp>\u003Cstrong>Important :\u003C\u002Fstrong> Le scraping doit toujours être réalisé sur des sites dont vous êtes propriétaire ou pour lesquels vous disposez d'une autorisation explicite. Respectez le fichier \u003Ccode>robots.txt\u003C\u002Fcode> et les conditions d'utilisation du site cible.\u003C\u002Fp>\u003C\u002Fblockquote>\n\u003Ch2>Prérequis et installation de Scrapy\u003C\u002Fh2>\n\u003Ch3>Environnement recommandé\u003C\u002Fh3>\n\u003Cul>\n\u003Cli>**Python 3.10+** (Python 2 n'est plus supporté)\u003C\u002Fli>\n\u003Cli>**Scrapy 2.11+** (version stable actuelle)\u003C\u002Fli>\n\u003Cli>Un environnement virtuel pour isoler les dépendances\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3>Installation\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-bash\">\n# Créer un environnement virtuel dédié\npython3 -m venv ~\u002Fscrapy-env\nsource ~\u002Fscrapy-env\u002Fbin\u002Factivate\n\n# Installer Scrapy\npip install Scrapy\n\n# Vérifier l'installation\nscrapy version\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Sur Ubuntu\u002FDebian, certaines dépendances système peuvent être nécessaires :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-bash\">\nsudo apt install python3-dev libxml2-dev libxslt1-dev zlib1g-dev libffi-dev libssl-dev\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>Cas n°1 : Extraire le catalogue produits en CSV\u003C\u002Fh2>\n\u003Ch3>Créer le projet Scrapy\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-bash\">\nmkdir -p ~\u002Fmes_crawlers && cd ~\u002Fmes_crawlers\nscrapy startproject prestashop_scraper\ncd prestashop_scraper\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Scrapy génère cette arborescence :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-\">\nprestashop_scraper\u002F\n├── scrapy.cfg\n└── prestashop_scraper\u002F\n    ├── __init__.py\n    ├── items.py\n    ├── middlewares.py\n    ├── pipelines.py\n    ├── settings.py\n    └── spiders\u002F\n        └── __init__.py\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Définir le modèle de données (`items.py`)\u003C\u002Fh3>\n\u003Cp>On déclare les champs que l'on souhaite extraire de chaque fiche produit :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-python\">\n# -*- coding: utf-8 -*-\nfrom scrapy.item import Item, Field\n\n\nclass PrestashopProductItem(Item):\n    \"\"\"Modèle de données pour un produit PrestaShop.\"\"\"\n    url = Field()\n    id_product = Field()\n    name = Field()\n    reference = Field()\n    price = Field()\n    quantity = Field()\n    balise_title = Field()\n    meta_description = Field()\n    description_courte = Field()\n    description = Field()\n    category = Field()\n    image_url = Field()\n    ean13 = Field()\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Créer le spider (`spiders\u002Fproducts_spider.py`)\u003C\u002Fh3>\n\u003Cp>Le spider est le composant qui navigue sur le site et extrait les données. Voici un exemple adapté au thème classic de PrestaShop :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-python\">\n# -*- coding: utf-8 -*-\nimport scrapy\nfrom prestashop_scraper.items import PrestashopProductItem\n\n\nclass ProductsSpider(scrapy.Spider):\n    name = 'products'\n    allowed_domains = ['votre-boutique.com']\n    start_urls = ['https:\u002F\u002Fvotre-boutique.com\u002F2-accueil']\n\n    custom_settings = {\n        'DOWNLOAD_DELAY': 1,  # 1 seconde entre chaque requête\n        'CONCURRENT_REQUESTS': 1,  # Une requête à la fois\n        'ROBOTSTXT_OBEY': True,\n        'USER_AGENT': 'CatalogBot\u002F1.0 (contact@votre-domaine.com)',\n    }\n\n    def parse(self, response):\n        \"\"\"Parse les pages de listing et suit la pagination.\"\"\"\n        # Liens vers les fiches produits\n        product_links = response.css('a.product-thumbnail::attr(href)').getall()\n        for link in product_links:\n            yield scrapy.Request(link, callback=self.parse_product)\n\n        # Pagination : suivre la page suivante\n        next_page = response.css('a.next::attr(href)').get()\n        if next_page:\n            yield scrapy.Request(next_page, callback=self.parse)\n\n        # Sous-catégories\n        subcategories = response.css('#subcategories a::attr(href)').getall()\n        for subcat in subcategories:\n            yield scrapy.Request(subcat, callback=self.parse)\n\n    def parse_product(self, response):\n        \"\"\"Extrait les données d'une fiche produit.\"\"\"\n        item = PrestashopProductItem()\n\n        item['url'] = response.url\n        item['balise_title'] = response.css('title::text').get('').strip()\n        item['meta_description'] = response.xpath(\n            '\u002F\u002Fmeta[@name=\"description\"]\u002F@content'\n        ).get('').strip()\n        item['name'] = response.css('h1.product-detail-name::text').get('').strip()\n\n        # Prix : nettoyage du format\n        raw_price = response.css('span.current-price-value::attr(content)').get()\n        item['price'] = raw_price if raw_price else '0'\n\n        # Référence produit\n        item['reference'] = response.css(\n            'span.product-reference-value::text'\n        ).get('').strip()\n\n        # Description\n        item['description_courte'] = response.css(\n            'div.product-description-short'\n        ).get('').strip()\n\n        # Image principale\n        item['image_url'] = response.css(\n            'img.js-qv-product-cover::attr(src)'\n        ).get('')\n\n        # ID produit depuis l'URL ou le DOM\n        id_input = response.css('input[name=\"id_product\"]::attr(value)').get()\n        item['id_product'] = id_input if id_input else ''\n\n        yield item\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Adapter les sélecteurs selon la version PrestaShop\u003C\u002Fh3>\n\u003Cp>Les sélecteurs CSS varient considérablement d'une version à l'autre :\u003C\u002Fp>\n\u003Ctr>\u003Cth>Élément\u003C\u002Fth>\u003Cth>PrestaShop 1.6 (default)\u003C\u002Fth>\u003Cth>PrestaShop 1.7\u002F8.x (classic)\u003C\u002Fth>\u003C\u002Ftr>\n\u003Ctr>\u003Cth>Lien produit listing\u003C\u002Fth>\u003Cth>`a.product_img_link`\u003C\u002Fth>\u003Cth>`a.product-thumbnail`\u003C\u002Fth>\u003C\u002Ftr>\n\u003Ctr>\u003Cth>Nom produit\u003C\u002Fth>\u003Cth>`h1[itemprop=\"name\"]`\u003C\u002Fth>\u003Cth>`h1.product-detail-name`\u003C\u002Fth>\u003C\u002Ftr>\n\u003Ctr>\u003Cth>Prix\u003C\u002Fth>\u003Cth>`span#our_price_display`\u003C\u002Fth>\u003Cth>`span.current-price-value`\u003C\u002Fth>\u003C\u002Ftr>\n\u003Ctr>\u003Cth>Pagination suivante\u003C\u002Fth>\u003Cth>`li.pagination_next a`\u003C\u002Fth>\u003Cth>`a.next`\u003C\u002Fth>\u003C\u002Ftr>\n\u003Ctr>\u003Cth>Image produit\u003C\u002Fth>\u003Cth>`img#bigpic`\u003C\u002Fth>\u003Cth>`img.js-qv-product-cover`\u003C\u002Fth>\u003C\u002Ftr>\n\u003Cp>\u003Cstrong>Conseil :\u003C\u002Fstrong> Avant de coder votre spider, inspectez le DOM du site cible avec les outils développeur de votre navigateur. Les thèmes personnalisés modifient souvent ces sélecteurs.\u003C\u002Fp>\n\u003Ch3>Configurer l'export CSV (`settings.py`)\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-python\">\n# Activer l'export CSV automatique\nFEEDS = {\n    'produits_%(time)s.csv': {\n        'format': 'csv',\n        'encoding': 'utf-8',\n        'fields': [\n            'id_product', 'name', 'reference', 'price',\n            'balise_title', 'meta_description', 'url', 'image_url'\n        ],\n        'overwrite': True,\n    },\n}\n\n# Respecter le robots.txt\nROBOTSTXT_OBEY = True\n\n# Délai entre les requêtes (en secondes)\nDOWNLOAD_DELAY = 1\n\n# Désactiver les cookies pour éviter les sessions inutiles\nCOOKIES_ENABLED = False\n\n# Logging\nLOG_LEVEL = 'INFO'\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Lancer le crawl\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-bash\">\nscrapy crawl products\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Le fichier CSV sera généré à la racine du projet avec un horodatage.\u003C\u002Fp>\n\u003Ch2>Cas n°2 : Synchroniser les stocks entre deux boutiques\u003C\u002Fh2>\n\u003Cp>Un besoin fréquent consiste à maintenir les stocks synchronisés entre un site source (fournisseur) et un site destination (revendeur). Voici une approche complète.\u003C\u002Fp>\n\u003Ch3>Modèle de données pour les stocks (`items.py`)\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-python\">\nclass PrestashopStockItem(Item):\n    \"\"\"Modèle simplifié pour la synchronisation de stocks.\"\"\"\n    id_product = Field()\n    quantity = Field()\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Spider de récupération des stocks\u003C\u002Fh3>\n\u003Cp>Si le site source expose les quantités (ce qui est souvent le cas sur les thèmes par défaut via les attributs \u003Ccode>data-*\u003C\u002Fcode> ou les microdonnées), on peut les extraire :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-python\">\nclass StockSpider(scrapy.Spider):\n    name = 'stock_sync'\n    allowed_domains = ['site-source.com']\n    start_urls = ['https:\u002F\u002Fsite-source.com\u002Fsitemap.xml']\n\n    custom_settings = {\n        'DOWNLOAD_DELAY': 2,\n        'ITEM_PIPELINES': {\n            'prestashop_scraper.pipelines.StockSyncPipeline': 300,\n        },\n    }\n\n    def parse(self, response):\n        \"\"\"Parse le sitemap pour trouver toutes les URLs produits.\"\"\"\n        response.selector.remove_namespaces()\n        urls = response.xpath('\u002F\u002Floc\u002Ftext()').getall()\n        for url in urls:\n            if '\u002Fproduit\u002F' in url or '\u002Fproduct\u002F' in url:\n                yield scrapy.Request(url, callback=self.parse_stock)\n\n    def parse_stock(self, response):\n        \"\"\"Extrait l'ID produit et la quantité disponible.\"\"\"\n        item = PrestashopStockItem()\n\n        item['id_product'] = response.css(\n            'input[name=\"id_product\"]::attr(value)'\n        ).get()\n\n        # La quantité est souvent dans un attribut data-stock\n        quantity = response.css(\n            'span.product-availability::attr(data-stock)'\n        ).get()\n\n        if not quantity:\n            # Fallback : chercher dans le JSON-LD\n            import json\n            ld_json = response.xpath(\n                '\u002F\u002Fscript[@type=\"application\u002Fld+json\"]\u002Ftext()'\n            ).getall()\n            for block in ld_json:\n                try:\n                    data = json.loads(block)\n                    if data.get('@type') == 'Product':\n                        offers = data.get('offers', {})\n                        availability = offers.get('availability', '')\n                        quantity = '1' if 'InStock' in availability else '0'\n                except json.JSONDecodeError:\n                    continue\n\n        item['quantity'] = int(quantity) if quantity else 0\n        yield item\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Pipeline de mise à jour via l'API Webservice (`pipelines.py`)\u003C\u002Fh3>\n\u003Cp>Plutôt que de manipuler la base de données directement, la bonne pratique consiste à utiliser l'API Webservice PrestaShop du site destination :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-python\">\nimport requests\nimport xml.etree.ElementTree as ET\n\n\nclass StockSyncPipeline:\n    \"\"\"Met à jour les stocks sur le site destination via l'API Webservice.\"\"\"\n\n    def __init__(self):\n        self.api_url = 'https:\u002F\u002Fsite-destination.com\u002Fapi'\n        self.api_key = 'VOTRE_CLE_WEBSERVICE'\n\n    def process_item(self, item, spider):\n        if not item.get('id_product'):\n            return item\n\n        id_product = item['id_product']\n        quantity = item['quantity']\n\n        try:\n            # Récupérer le stock_available correspondant\n            url = f\"{self.api_url}\u002Fstock_availables\"\n            params = {\n                'filter[id_product]': id_product,\n                'filter[id_product_attribute]': 0,\n                'display': 'full',\n            }\n            resp = requests.get(\n                url, params=params,\n                auth=(self.api_key, ''),\n                timeout=10\n            )\n\n            if resp.status_code == 200:\n                root = ET.fromstring(resp.content)\n                stock_id = root.find('.\u002F\u002Fstock_available\u002Fid').text\n\n                # Mettre à jour la quantité\n                update_url = f\"{self.api_url}\u002Fstock_availables\u002F{stock_id}\"\n                xml_body = f\"\"\"&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;\n                &lt;prestashop&gt;\n                    &lt;stock_available&gt;\n                        &lt;id&gt;{stock_id}&lt;\u002Fid&gt;\n                        &lt;quantity&gt;{quantity}&lt;\u002Fquantity&gt;\n                    &lt;\u002Fstock_available&gt;\n                &lt;\u002Fprestashop&gt;\"\"\"\n\n                requests.put(\n                    update_url,\n                    data=xml_body,\n                    auth=(self.api_key, ''),\n                    headers={'Content-Type': 'application\u002Fxml'},\n                    timeout=10\n                )\n                spider.logger.info(\n                    f\"Stock mis à jour : produit {id_product} → {quantity}\"\n                )\n\n        except requests.RequestException as e:\n            spider.logger.error(\n                f\"Erreur sync produit {id_product}: {e}\"\n            )\n\n        return item\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Automatiser avec un cron\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-bash\">\n# Synchronisation toutes les 6 heures\n0 *\u002F6 * * * cd ~\u002Fmes_crawlers\u002Fprestashop_scraper && \u002Fhome\u002Fuser\u002Fscrapy-env\u002Fbin\u002Fscrapy crawl stock_sync &gt;&gt; \u002Fvar\u002Flog\u002Fstock_sync.log 2&gt;&1\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>La meilleure alternative : l'API Webservice PrestaShop\u003C\u002Fh2>\n\u003Cp>Avant de recourir au scraping, vérifiez si l'API Webservice n'est pas une meilleure solution pour votre cas d'usage. PrestaShop embarque une API REST native bien plus fiable :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-python\">\nimport requests\n\nAPI_URL = 'https:\u002F\u002Fvotre-boutique.com\u002Fapi'\nAPI_KEY = 'VOTRE_CLE_WEBSERVICE'\n\n# Récupérer tous les produits en JSON\nresponse = requests.get(\n    f'{API_URL}\u002Fproducts',\n    auth=(API_KEY, ''),\n    params={'output_format': 'JSON', 'display': 'full'},\n    timeout=30\n)\n\nproducts = response.json().get('products', [])\nfor product in products:\n    print(f\"ID: {product['id']} | Nom: {product['name'][0]['value']}\")\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>Avantages de l'API vs scraping :\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cul>\n\u003Cli>Données structurées et fiables (pas de parsing HTML fragile)\u003C\u002Fli>\n\u003Cli>Accès aux déclinaisons, combinaisons, attributs spécifiques\u003C\u002Fli>\n\u003Cli>Opérations CRUD complètes (lecture ET écriture)\u003C\u002Fli>\n\u003Cli>Indépendant du thème front-office\u003C\u002Fli>\n\u003Cli>Performances supérieures (pas de rendu HTML)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>L'API nécessite un accès back-office pour générer la clé, mais c'est toujours l'approche à privilégier quand elle est disponible.\u003C\u002Fp>\n\u003Ch2>Bonnes pratiques et pièges à éviter\u003C\u002Fh2>\n\u003Ch3>Respecter le site cible\u003C\u002Fh3>\n\u003Cul>\n\u003Cli>**`DOWNLOAD_DELAY` :** Toujours mettre au minimum 1 seconde entre chaque requête pour ne pas surcharger le serveur\u003C\u002Fli>\n\u003Cli>**`ROBOTSTXT_OBEY` :** Toujours à `True` sauf sur vos propres sites de test\u003C\u002Fli>\n\u003Cli>**User-Agent :** Déclarez un User-Agent identifiable avec un email de contact\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3>Gérer les pièges techniques\u003C\u002Fh3>\n\u003Cul>\n\u003Cli>**Sessions PHP :** Désactivez les cookies (`COOKIES_ENABLED = False`) pour éviter de créer des milliers de sessions\u003C\u002Fli>\n\u003Cli>**Pagination infinie :** Limitez la profondeur avec `DEPTH_LIMIT = 5`\u003C\u002Fli>\n\u003Cli>**Doublons :** Scrapy déduplique automatiquement les URLs, mais vérifiez les paramètres GET inutiles\u003C\u002Fli>\n\u003Cli>**Encodage :** PrestaShop 1.6 peut servir du ISO-8859-1 sur certaines configurations ; forcez l'UTF-8 dans vos settings\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3>Thèmes personnalisés\u003C\u002Fh3>\n\u003Cp>Les sélecteurs CSS fournis dans cet article correspondent aux thèmes par défaut. Un thème personnalisé nécessitera d'adapter chaque sélecteur. Utilisez le shell interactif Scrapy pour tester vos sélecteurs avant de lancer un crawl complet :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-bash\">\nscrapy shell 'https:\u002F\u002Fvotre-boutique.com\u002Fun-produit'\n&gt;&gt;&gt; response.css('h1::text').get()\n&gt;&gt;&gt; response.css('span.price::text').get()\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>Conclusion\u003C\u002Fh2>\n\u003Cp>Le web scraping avec Scrapy reste un outil puissant dans la boîte à outils du développeur PrestaShop, notamment pour les migrations, les audits SEO ou la veille concurrentielle. Cependant, pour la synchronisation de données entre deux boutiques que vous administrez, l'API Webservice sera toujours plus fiable, plus rapide et plus maintenable. Réservez le scraping aux situations où l'API n'est pas une option.\u003C\u002Fp>",[41,44,47,50,53],{"q":42,"a":43},"Le web scraping d'un site PrestaShop est-il légal ?","Le scraping est légal lorsqu'il porte sur des données publiquement accessibles et que vous respectez les conditions d'utilisation du site ainsi que le fichier robots.txt. En revanche, scraper des données personnelles, contourner des protections d'accès ou surcharger un serveur peut constituer une infraction. Pour vos propres sites, aucune restriction ne s'applique.",{"q":45,"a":46},"Pourquoi utiliser Scrapy plutôt que BeautifulSoup ou Selenium pour scraper PrestaShop ?","Scrapy est un framework complet qui gère nativement la concurrence, la pagination, le respect du robots.txt, l'export multi-format et les pipelines de traitement. BeautifulSoup est une simple librairie de parsing HTML qui nécessite d'écrire tout le reste manuellement. Selenium simule un vrai navigateur, ce qui est beaucoup plus lent et gourmand en ressources. Pour un catalogue de plusieurs milliers de produits, Scrapy est l'outil le plus adapté.",{"q":48,"a":49},"Comment adapter les sélecteurs CSS quand le thème PrestaShop est personnalisé ?","Utilisez le shell interactif Scrapy (commande 'scrapy shell URL') pour tester vos sélecteurs en temps réel. Inspectez le DOM avec les outils développeur de votre navigateur pour identifier les classes et attributs spécifiques à votre thème. Les attributs data-* et les microdonnées JSON-LD sont souvent plus stables que les classes CSS qui changent d'un thème à l'autre.",{"q":51,"a":52},"Comment synchroniser les stocks de produits avec déclinaisons entre deux PrestaShop ?","Pour les produits avec déclinaisons (combinaisons), l'API Webservice PrestaShop est fortement recommandée plutôt que le scraping. Chaque déclinaison possède son propre stock_available identifié par le couple id_product + id_product_attribute. Via l'API, filtrez les stock_availables par produit et mettez à jour chaque combinaison individuellement. Le scraping front-office ne permet généralement pas d'accéder aux stocks par déclinaison de manière fiable.",{"q":54,"a":55},"Scrapy peut-il gérer les protections anti-bot de PrestaShop ?","PrestaShop n'intègre pas de protection anti-bot native, mais certains hébergeurs ou modules de sécurité (comme un WAF ou un captcha) peuvent bloquer les requêtes automatisées. Pour vos propres sites, désactivez temporairement ces protections ou whitelistez l'IP de votre crawler. Configurez un User-Agent réaliste et respectez un délai raisonnable entre les requêtes pour éviter tout blocage.","Guide complet pour extraire les données produits d'une boutique PrestaShop avec le framework Python Scrapy : export CSV du catalogue et synchronisation de stocks entre deux sites, avec comparatif API Webservice vs scraping.",8,"2026-03-21T14:27:57.000Z",[],"PrestaShop pour les développeurs",{"columns":62},[63,79,109,130],{"title":64,"links":65},"Plateforme",[66,70,73,76],{"label":67,"href":68,"external":69},"Offre Starter (2 500 €)","\u002Foffre-starter",false,{"label":71,"href":72,"external":69},"Devenir Ambassadeur","\u002Fambassadeur",{"label":74,"href":75,"external":69},"Modules PrestaShop","\u002Fmodules",{"label":77,"href":78,"external":20},"CodeMyShop.com","https:\u002F\u002Fcodemyshop.com",{"title":80,"links":81},"Le Synedre",[82,85,88,91,94,97,100,103,106],{"label":83,"href":84,"external":69},"L'histoire","\u002Fsynedre",{"label":86,"href":87,"external":69},"Constitution","\u002Fsynedre\u002Fconstitution",{"label":89,"href":90,"external":69},"L'équipe","\u002Fequipe",{"label":92,"href":93,"external":69},"Le réacteur en direct","\u002Freacteur",{"label":95,"href":96,"external":69},"Le Drill (entraînement)","\u002Fdrill",{"label":98,"href":99,"external":69},"Protocole de réunion","\u002Fsynedre\u002Freunion",{"label":101,"href":102,"external":69},"Les agents IA","\u002Fagents-ia",{"label":104,"href":105,"external":69},"La Conduite","\u002Fsynedre\u002Fconduite",{"label":107,"href":108,"external":69},"Charte plateforme","\u002Fsynedre\u002Fcharte",{"title":110,"links":111},"Ressources",[112,115,118,121,124,127],{"label":113,"href":114,"external":69},"Blog","\u002Fblog",{"label":116,"href":117,"external":69},"Academy","\u002Facademy",{"label":119,"href":120,"external":69},"Dictionnaire","\u002Fdictionnaire",{"label":122,"href":123,"external":69},"Expertise PrestaShop","\u002Fexpertise",{"label":125,"href":126,"external":69},"Flywheel","\u002Fflywheel",{"label":128,"href":129,"external":69},"Manifeste","\u002Fmanifeste",{"title":131,"links":132},"À propos",[133,136,139],{"label":134,"href":135,"external":69},"Alexandre Carette","\u002Fa-propos",{"label":137,"href":138,"external":69},"Dossier de presse","\u002Fpresse",{"label":140,"href":141,"external":69},"Contact","\u002Fcontact",{"items":143},[144,153,159,165,173,181,187,193],{"id":145,"type":146,"label":147,"href":123,"icon":149,"description":149,"badge":149,"groupTitle":149,"style":149,"gridColumns":149,"cssClass":149,"psCategoryId":149,"showPsChildren":69,"position":150,"children":151,"psChildren":152},41,"link",{"fr":148},"Expertise",null,0,[],[],{"id":154,"type":146,"label":155,"href":114,"icon":149,"description":149,"badge":149,"groupTitle":149,"style":149,"gridColumns":149,"cssClass":149,"psCategoryId":149,"showPsChildren":69,"position":156,"children":157,"psChildren":158},42,{"fr":113},1,[],[],{"id":160,"type":146,"label":161,"href":75,"icon":149,"description":149,"badge":149,"groupTitle":149,"style":149,"gridColumns":149,"cssClass":149,"psCategoryId":149,"showPsChildren":69,"position":162,"children":163,"psChildren":164},43,{"fr":74},2,[],[],{"id":166,"type":146,"label":167,"href":169,"icon":149,"description":149,"badge":149,"groupTitle":149,"style":149,"gridColumns":149,"cssClass":149,"psCategoryId":149,"showPsChildren":69,"position":170,"children":171,"psChildren":172},44,{"fr":168},"Outils IA","\u002Foutils-ia",3,[],[],{"id":174,"type":146,"label":175,"href":68,"icon":149,"description":149,"badge":149,"groupTitle":149,"style":177,"gridColumns":149,"cssClass":149,"psCategoryId":149,"showPsChildren":69,"position":178,"children":179,"psChildren":180},45,{"fr":176},"Offre Starter ✨",{"highlight":20},4,[],[],{"id":182,"type":146,"label":183,"href":117,"icon":149,"description":149,"badge":149,"groupTitle":149,"style":149,"gridColumns":149,"cssClass":149,"psCategoryId":149,"showPsChildren":69,"position":184,"children":185,"psChildren":186},46,{"fr":116},5,[],[],{"id":188,"type":146,"label":189,"href":135,"icon":149,"description":149,"badge":149,"groupTitle":149,"style":149,"gridColumns":149,"cssClass":149,"psCategoryId":149,"showPsChildren":69,"position":190,"children":191,"psChildren":192},47,{"fr":131},6,[],[],{"id":194,"type":146,"label":195,"href":141,"icon":149,"description":149,"badge":149,"groupTitle":149,"style":149,"gridColumns":149,"cssClass":149,"psCategoryId":149,"showPsChildren":69,"position":196,"children":197,"psChildren":198},48,{"fr":140},7,[],[],{"academy":200,"blog":201,"expertise":212},[],[202,206,209],{"title":203,"url":204,"score":156,"type":205},"Construire une usine à contenu SEO avec PrestaShop, Claude, Python","\u002Fblog\u002Fprestashop\u002Fdeveloppement\u002Fpipeline-seo-claude-python","blog",{"title":207,"url":208,"score":156,"type":205},"PrestaShop headless avec Nuxt 3 : pourquoi séparer back et front","\u002Fblog\u002Fprestashop\u002Farchitecture\u002Fprestashop-headless-nuxt-separation-front-back",{"title":210,"url":211,"score":156,"type":205},"PrestaShop headless : Nuxt 3, pas Next.js — le choix souverain","\u002Fblog\u002Fprestashop\u002Farchitecture\u002Fprestashop-headless-nuxt-nextjs-souverainete",[],{"footer":214},{"theme":215,"description":149,"hours":149,"logo":216,"contact":219,"social":220,"bottomBar":230},"dark",{"src":217,"href":218,"alt":134},"\u002Flogo-ac.svg","\u002F",{"email":149,"phone":149,"address":149,"cta":149},[221,224,227],{"platform":222,"href":223,"label":222},"linkedin","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Falexandre-carette\u002F",{"platform":225,"href":226,"label":225},"malt","https:\u002F\u002Fwww.malt.fr\u002Fprofile\u002Falexandrecarette",{"platform":228,"href":229,"label":228},"github","https:\u002F\u002Fgithub.com\u002Fprest4cafe",{"copyright":149},{"header":232},{"logo":233,"topBar":236,"contactEmail":239,"features":240,"navBar":149},{"src":217,"alt":234,"text":134,"href":218,"class":235},"Alexandre Carette — Architecte E-commerce Souverain","h-10 w-10",{"message":149,"showLanguages":69,"align":237,"languages":238},"left",[],"contact@alexandrecarette.fr",{"showSearch":69,"showWishlist":69,"showLogin":20,"showContact":69,"showCart":69,"stickyHeader":20,"headerLayout":241},"inline"]