Commit
[ka2] fix connecteur solaris: retry anti-hoquet — la page sert parfois « 6 unités libres » (data-count=6) avec une liste vide/tronquée. 2e occurrence en 3 jours (2026-10-16 07:12 et 2026-10-19 20:08, sync_log 169676) : le garde-fou anti zéro silencieux levait une erreur franche (correct, pas de dépublication) mais marquait le connecteur stale à chaque hoquet. Fix minimal : fetch() réessaie jusqu'à 3 fois (pause 4 s/8 s) avec cache-buster ?lkb=<ts> sur les tentatives 2-3 (site derrière Cloudflare — pattern du fix brio 2c52899) avant de lever l'erreur ; cache-buster absent de la 1re tentative pour préserver la clé de rejeu des fixtures (URL nue). Garde-fous inchangés : section data-live="units" absente ou 0 unité avec data-count>0 = erreur FRANCHE après les retries ; data-count=0 = vrai zéro accepté. Testé : pytest solaris 2 passed, run.py sync solaris → found=6 ok (0,2 s, médiane 6.0), pm2 restart lou-ka-sync, site :8095 → 200.
modified
louka/connectors/solaris.py
+21 −1
@@ -13,6 +13,7 @@ from __future__ import annotations | ||
| 13 | 13 | |
| 14 | 14 | import html as _html |
| 15 | 15 | import re |
| 16 | +import time | |
| 16 | 17 | |
| 17 | 18 | from ..schema import Listing |
| 18 | 19 | from .base import BaseConnector |
@@ -47,9 +48,28 @@ class SolarisConnector(BaseConnector): | ||
| 47 | 48 | max_images = 10 |
| 48 | 49 | |
| 49 | 50 | def fetch(self) -> list[Listing]: |
| 51 | + # Le serveur sert PARFOIS la page avec l'en-tête « N unités libres » | |
| 52 | + # (data-count>0) mais une liste d'unités vide/tronquée — hoquets | |
| 53 | + # constatés 2026-10-16 et 2026-10-19. Retry avec cache-buster | |
| 54 | + # (site derrière Cloudflare, pattern du fix brio 2c52899) avant de | |
| 55 | + # lever l'erreur franche. Cache-buster seulement sur les tentatives | |
| 56 | + # 2-3 : la clé de rejeu des fixtures reste l'URL nue. | |
| 57 | + last_err: RuntimeError | None = None | |
| 58 | + for attempt in range(3): | |
| 59 | + if attempt: | |
| 60 | + time.sleep(4 * attempt) | |
| 61 | + url = PLANS_URL if attempt == 0 else \ | |
| 62 | + f"{PLANS_URL}?lkb={int(time.time())}" | |
| 63 | + try: | |
| 64 | + return self._fetch_once(url) | |
| 65 | + except RuntimeError as err: | |
| 66 | + last_err = err | |
| 67 | + raise last_err | |
| 68 | + | |
| 69 | + def _fetch_once(self, url: str) -> list[Listing]: | |
| 50 | 70 | # le serveur n'annonce pas de charset → requests décoderait en |
| 51 | 71 | # latin-1 et casserait « ½ », « ² » et les espaces insécables des prix. |
| 52 | − raw = self.get(PLANS_URL).content.decode("utf-8", "replace") | |
| 72 | + raw = self.get(url).content.decode("utf-8", "replace") | |
| 53 | 73 | |
| 54 | 74 | m = SECTION_RE.search(raw) |
| 55 | 75 | if not m: |
| 56 | 76 | |