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 gwlra: retry (3 essais, backoff 5/10 s) du sitemap et des pages floorplans — un raté transitoire Scrapfly (vu 2026-08-30 ~04:20) produisait un faux « 0 trouvé ok » (médiane 1) car get_scrapfly retourne "" en silence, _building retourne [] sur non-200/contenu vide et fetch() avalait les exceptions par immeuble; désormais erreur franche si le sitemap ne donne aucune page QC après 3 essais, si une page floorplans échoue 3 fois (seul un 404/410 vaut « immeuble retiré ») ou si tous les immeubles sont en erreur. Testé: run.py sync gwlra → 1 annonce (= médiane 1)

Simon-Pierre Boucher committed 26 days ago (Aug 30, 2026) parent 7bd5af7

1 changed file +49 −14

modified louka/connectors/gwlra.py +49 −14
@@ -16,6 +16,7 @@ from __future__ import annotations
16 16
17 17 import json
18 18 import re
19 +import time
19 20
20 21 from bs4 import BeautifulSoup
21 22
@@ -46,28 +47,62 @@ class GwlraConnector(BaseConnector):
46 47 use_detail_cache = False # tout passe par Scrapfly, pages fraîches
47 48
48 49 def fetch(self) -> list[Listing]:
49 − sitemap = self.get_scrapfly(SITEMAP_URL, render_js=False)
50 − pages = {} # slug -> (ville, chemin floorplans)
51 − for city, slug in FLOORPLAN_PATH_RE.findall(sitemap):
52 − pages.setdefault(slug, (city, f"apartments/qc/{city}/{slug}"
53 − "/floorplans"))
50 + # Un raté transitoire (Scrapfly vide, vu 2026-08-30 ~04:20) produisait
51 + # un faux « 0 trouvé ok » (médiane 1) : retry du sitemap (3 essais,
52 + # backoff 5/10 s) puis erreur franche plutôt qu'un zéro silencieux.
53 + pages: dict[str, tuple[str, str]] = {} # slug -> (ville, chemin)
54 + for attempt in range(3):
55 + try:
56 + sitemap = self.get_scrapfly(SITEMAP_URL, render_js=False)
57 + except Exception:
58 + if attempt == 2:
59 + raise
60 + sitemap = ""
61 + for city, slug in FLOORPLAN_PATH_RE.findall(sitemap):
62 + pages.setdefault(slug, (city, f"apartments/qc/{city}/{slug}"
63 + "/floorplans"))
64 + if pages:
65 + break
66 + if attempt < 2:
67 + time.sleep(5 * (attempt + 1))
68 + if not pages:
69 + raise RuntimeError(
70 + "sitemap sans page floorplans QC après 3 essais")
71 +
54 72 listings: list[Listing] = []
73 + errors = 0
74 + last_exc: Exception | None = None
55 75 for slug, (city_slug, path) in sorted(pages.items()):
56 76 try:
57 77 listings.extend(self._building(slug, city_slug,
58 78 f"{BASE}/{path}"))
59 − except Exception:
60 − continue
79 + except Exception as exc:
80 + errors += 1
81 + last_exc = exc
82 + # tous les immeubles en erreur et rien récolté : erreur franche
83 + if not listings and last_exc is not None and errors == len(pages):
84 + raise last_exc
61 85 return listings
62 86
63 87 def _building(self, slug: str, city_slug: str, url: str) -> list[Listing]:
64 − # le sitemap contient des immeubles retirés (404) : vérifier le statut
65 − result = self.scrapfly(url, render_js=True)
66 − if result.get("status_code") != 200:
67 − return []
68 − html = result.get("content") or ""
69 − if not html:
70 − return []
88 + # le sitemap contient des immeubles retirés (404) : vérifier le statut.
89 + # Les ratés transitoires (5xx, contenu vide) sont retentés 3 fois puis
90 + # remontés en erreur franche — seul un 404/410 vaut « immeuble retiré ».
91 + html = ""
92 + for attempt in range(3):
93 + result = self.scrapfly(url, render_js=True)
94 + status = result.get("status_code")
95 + if status in (404, 410):
96 + return []
97 + html = result.get("content") or ""
98 + if status == 200 and html:
99 + break
100 + if attempt < 2:
101 + time.sleep(5 * (attempt + 1))
102 + else:
103 + raise RuntimeError(
104 + f"page floorplans {url} en échec après 3 essais "
105 + f"(statut {status!r}, contenu {'vide' if not html else 'ok'})")
71 106 soup = BeautifulSoup(html, "html.parser")
72 107
73 108 # JSON-LD ApartmentComplex : nom, adresse, animaux, détail des plans
74 109