Revert "[ka2] fix connecteur vision_immo: échec honnête au lieu d'un « 0 trouvé ok » qui a déjà dépublié les 6 annonces. Le sync du 2026-09-11 10:43 a rapporté found=0 ok=1 missed=6 (médiane 6, stale) : fetch() avalait toute exception sur la page liste (`except Exception: return []`) — un échec réseau transitoire devenait un succès à 0, invisible du disjoncteur. L'historique montre le coût réel : pendant la panne DNS systémique du 2026-09-10 (14:17 et 15:22), deux « 0 ok » silencieux ont abouti à removed=6 — les 6 annonces dépubliées puis re-publiées au sync suivant. Reproduit en live : la source est SAINE (page 200, 177 Ko, 6 liens /logements/), seul le silence sur erreur était fautif — même situation que thrace e5379b0 et novelo 6bf26bd, distinct du cas lienard (rollback d1571fc, source en panne). Fix : (1) fetch() laisse remonter les exceptions réseau (ingest journalise ok=0) et lève RuntimeError si 0 lien parsé ET formulaire de filtres Search & Filter absent (rendu partiel/refonte) — un vrai zéro (0 lien mais squelette présent) reste possible car la page ne liste que les unités offertes ; (2) _parse_detail() ne retourne plus {} sur exception : detail() aurait mis ce {} en cache PERMANENT (clé figée « v1 », jamais invalidée) et l'unité aurait disparu pour toujours — vérifié : cache actuel sain, 7 payloads complets. Testé : fetch() venv → 6 annonces (4½ Terrebonne 1 720 $, 4½ Lachine 1 995 $, 3½ St-Henri 1 395 $, 4½ J180 2 195 $, 5½ 2 395 $, 6½ 2 695 $), run.py sync vision_immo → found=6 ok, pm2 restart lou-ka-sync, site :8095 → 200."
This reverts commit 602ff085f8e5b57320fc15852598d73d6a057a76.
1 changed file +8 −15
modified
louka/connectors/vision_immo.py
+8 −15
@@ -41,10 +41,10 @@ class VisionImmoConnector(BaseConnector): | ||
| 41 | 41 | request_delay = 0.7 |
| 42 | 42 | |
| 43 | 43 | def _parse_detail(self, url: str) -> dict: |
| 44 | − # pas de try/except : un échec réseau doit remonter (ok=0), pas être | |
| 45 | − # caché — detail() mettrait le {} en cache permanent (clé figée « v1 ») | |
| 46 | − # et l'unité disparaîtrait pour toujours | |
| 47 | − html = self.get(url).text | |
| 44 | + try: | |
| 45 | + html = self.get(url).text | |
| 46 | + except Exception: | |
| 47 | + return {} | |
| 48 | 48 | soup = BeautifulSoup(html, "html.parser") |
| 49 | 49 | payload: dict = {} |
| 50 | 50 | |
@@ -127,19 +127,12 @@ class VisionImmoConnector(BaseConnector): | ||
| 127 | 127 | return payload |
| 128 | 128 | |
| 129 | 129 | def fetch(self) -> list[Listing]: |
| 130 | − # échec HONNÊTE : pas de try/except — une erreur réseau doit remonter | |
| 131 | − # (ok=0 dans sync_log) et non devenir un « 0 trouvé ok » qui dépublie | |
| 132 | − # les annonces (vu les 2026-09-10 15:22 removed=6 et 2026-09-11 10:43) | |
| 133 | 130 | listings: list[Listing] = [] |
| 134 | − html = self.get(LIST_URL).text | |
| 131 | + try: | |
| 132 | + html = self.get(LIST_URL).text | |
| 133 | + except Exception: | |
| 134 | + return listings | |
| 135 | 135 | links = list(dict.fromkeys(LINK_RE.findall(html))) |
| 136 | − # 0 lien peut être un VRAI zéro (la page ne liste que les unités | |
| 137 | − # offertes) : on ne lève que si le squelette de la page saine manque | |
| 138 | − # aussi (formulaire de filtres Search & Filter → rendu partiel/refonte) | |
| 139 | − if not links and "sf-field-post-meta-prix" not in html: | |
| 140 | − raise RuntimeError( | |
| 141 | − "visionimmo.com : aucun lien /logements/ et formulaire de " | |
| 142 | − "filtres absent (rendu partiel ou page restructurée)") | |
| 143 | 136 | for url, slug in links: |
| 144 | 137 | payload = self.detail(slug, "v1", |
| 145 | 138 | lambda u=url: self._parse_detail(u)) |
| 146 | 139 | |