SPB Git forge

spb/lou-ka

Public

Lou·Ka — tous les logements à louer du Québec, un seul endroit.

232commits 1branches 0releases
172.9 MBsize
maindefault branch
2 days agolast push
HTML 98.9% Python 0.6%

[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.

Simon-Pierre Boucher committed 13 days ago (Sep 11, 2026) parent 6bf26bd

1 changed file +15 −8

modified louka/connectors/vision_immo.py +15 −8
@@ -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 − try:
45 − html = self.get(url).text
46 − except Exception:
47 − return {}
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
48 48 soup = BeautifulSoup(html, "html.parser")
49 49 payload: dict = {}
50 50
@@ -127,12 +127,19 @@ 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)
130 133 listings: list[Listing] = []
131 − try:
132 − html = self.get(LIST_URL).text
133 − except Exception:
134 − return listings
134 + html = self.get(LIST_URL).text
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)")
136 143 for url, slug in links:
137 144 payload = self.detail(slug, "v1",
138 145 lambda u=url: self._parse_detail(u))
139 146