[ka6] fix connecteur inspirationgourmande.ca: wp-json retourne 500 (erreur PHP serveur), pages HTML cachées via WP Super Cache — fallback HTML dans WooCommerceConnector: scrape accueil + catégories FR/EN (/categorie-produit/), extrait JSON-LD Product sur chaque page produit accessible
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 changed file +90 −1
modified
fabrika/connectors/woocommerce.py
+90 −1
@@ -6,6 +6,9 @@ | ||
| 6 | 6 | # ----------------------------------------------------------------------------- |
| 7 | 7 | from __future__ import annotations |
| 8 | 8 | |
| 9 | +import re | |
| 10 | +from urllib.parse import urlparse | |
| 11 | + | |
| 9 | 12 | from ..schema import Product |
| 10 | 13 | from .base import BaseConnector |
| 11 | 14 | |
@@ -44,11 +47,89 @@ class WooCommerceConnector(BaseConnector): | ||
| 44 | 47 | return scrapfly.scrapfly_json(url) |
| 45 | 48 | raise exc |
| 46 | 49 | |
| 50 | + def _html_fallback(self) -> list[Product]: | |
| 51 | + """Fallback HTML quand la Store API WooCommerce retourne 500 (erreur serveur). | |
| 52 | + | |
| 53 | + Découvre les URLs produit depuis l'accueil et les pages catégories WooCommerce, | |
| 54 | + puis extrait le JSON-LD Product de chaque page. Utilisé quand /wp-json/ est | |
| 55 | + cassé mais que les pages HTML sont servies depuis le cache (ex. WP Super Cache). | |
| 56 | + """ | |
| 57 | + from .generic import extract_product | |
| 58 | + | |
| 59 | + host = urlparse(self.base).netloc | |
| 60 | + | |
| 61 | + # Accueil + slugs de boutique courants (FR d'abord) | |
| 62 | + discovery = [self.base + "/"] | |
| 63 | + for path in ("/shop/", "/boutique/", "/boutique-en-ligne/", | |
| 64 | + "/produits/", "/products/"): | |
| 65 | + discovery.append(self.base + path) | |
| 66 | + | |
| 67 | + product_urls: set[str] = set() | |
| 68 | + | |
| 69 | + for page_url in discovery: | |
| 70 | + try: | |
| 71 | + r = self.session.get(page_url, timeout=self.timeout) | |
| 72 | + if r.status_code != 200: | |
| 73 | + continue | |
| 74 | + html = r.text | |
| 75 | + # Produits WooCommerce FR (/produit/) et EN (/product/) | |
| 76 | + found = set(re.findall( | |
| 77 | + rf'href="(https?://{re.escape(host)}/(?:produit|product)/[^"#?]+)"', | |
| 78 | + html)) | |
| 79 | + product_urls |= found | |
| 80 | + # Catégories WooCommerce FR (/categorie-produit/) et EN (/product-category/) | |
| 81 | + cat_links = set(re.findall( | |
| 82 | + rf'href="(https?://{re.escape(host)}/(?:categorie-produit|product-category)/[^"#?]+)"', | |
| 83 | + html)) | |
| 84 | + for cat_url in cat_links: | |
| 85 | + try: | |
| 86 | + r2 = self.session.get(cat_url, timeout=self.timeout) | |
| 87 | + if r2.status_code == 200: | |
| 88 | + product_urls |= set(re.findall( | |
| 89 | + rf'href="(https?://{re.escape(host)}/(?:produit|product)/[^"#?]+)"', | |
| 90 | + r2.text)) | |
| 91 | + except Exception: | |
| 92 | + pass | |
| 93 | + except Exception: | |
| 94 | + pass | |
| 95 | + | |
| 96 | + out: list[Product] = [] | |
| 97 | + for url in product_urls: | |
| 98 | + try: | |
| 99 | + r = self.session.get(url, timeout=self.timeout) | |
| 100 | + if r.status_code != 200: | |
| 101 | + continue | |
| 102 | + info = extract_product(url, r.text) | |
| 103 | + if not info: | |
| 104 | + continue | |
| 105 | + slug = url.rstrip("/").split("/")[-1][:80] | |
| 106 | + out.append(Product( | |
| 107 | + store_id=self.store_id, | |
| 108 | + external_id=slug or url, | |
| 109 | + url=url, | |
| 110 | + title=info["title"], | |
| 111 | + description=info.get("description") or "", | |
| 112 | + price=info["price"], | |
| 113 | + price_max=info["price"], | |
| 114 | + currency=info.get("currency") or "CAD", | |
| 115 | + images=[info["image"]] if info.get("image") else [], | |
| 116 | + available=info.get("available"), | |
| 117 | + )) | |
| 118 | + except Exception: | |
| 119 | + pass | |
| 120 | + return out | |
| 121 | + | |
| 47 | 122 | def fetch(self) -> list[Product]: |
| 48 | 123 | out: list[Product] = [] |
| 49 | 124 | page = 1 |
| 125 | + first_page_exc: Exception | None = None | |
| 50 | 126 | while page <= self.max_pages: |
| 51 | − items = self._get_items(f"{self.base}/wp-json/wc/store/v1/products?per_page=100&page={page}") | |
| 127 | + try: | |
| 128 | + items = self._get_items(f"{self.base}/wp-json/wc/store/v1/products?per_page=100&page={page}") | |
| 129 | + except Exception as exc: | |
| 130 | + if page == 1: | |
| 131 | + first_page_exc = exc | |
| 132 | + break | |
| 52 | 133 | if not isinstance(items, list) or not items: |
| 53 | 134 | break |
| 54 | 135 | for it in items: |
@@ -125,4 +206,12 @@ class WooCommerceConnector(BaseConnector): | ||
| 125 | 206 | if len(items) < 100: |
| 126 | 207 | break |
| 127 | 208 | page += 1 |
| 209 | + | |
| 210 | + if first_page_exc is not None and not out: | |
| 211 | + # Store API inaccessible dès la 1ʳᵉ page → fallback HTML + JSON-LD | |
| 212 | + html_out = self._html_fallback() | |
| 213 | + if html_out: | |
| 214 | + return html_out | |
| 215 | + raise first_page_exc | |
| 216 | + | |
| 128 | 217 | return out |
| 129 | 218 | |