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 brio: contourner la variante brotli figée du cache Cloudflare qui rendait la page illisible (faux « 0 trouvé ok »). Le sync du 2026-09-07 14:21 a rapporté found=0 ok=1 (missed=3, médiane 3) : reproduit en live — le cache CDN d'immeublesbrio.com sert pour /appartements-a-louer-val-belair/ une variante brotli figée (content-encoding: br, cf-cache-status HIT, age ~11 j, expire 2026-10-08) MÊME quand le client n'annonce que gzip (Vary ignoré) ; requests ne décode pas br → .text binaire → 0 hotspot-info parsé, invisible du disjoncteur d'ingest.py. La source est saine : un paramètre cache-buster force un MISS et l'origine renvoie du gzip valide (49 hotspots). Fix : _html() vérifie un marqueur attendu ('hotspot-info' / 'partir de'), retente avec cache-buster si absent, puis RuntimeError franche ; garde supplémentaire si aucun hotspot-info (une page complète en contient toujours, Disponible ET Loué — même pattern que gwlra/le_fjord/logiluxx). Testé : run.py sync brio → found=3 ok (App. 109 5½ 1 850 $, App. 303 4½ 1 625 $, App. 408 3½ 1 500 $, missed remis à 0), pm2 restart lou-ka-sync, site :8095 → 200.

Simon-Pierre Boucher committed 17 days ago (Sep 8, 2026) parent 1814e48

1 changed file +31 −3

modified louka/connectors/brio.py +31 −3
@@ -10,6 +10,7 @@
10 10 from __future__ import annotations
11 11
12 12 import re
13 +import time
13 14
14 15 from bs4 import BeautifulSoup
15 16
@@ -28,6 +29,26 @@ class BrioConnector(BaseConnector):
28 29 source_id = "brio"
29 30 request_delay = 0.6
30 31
32 + def _html(self, url: str, marker: str) -> str:
33 + """HTML de `url`, avec contournement du cache CDN empoisonné.
34 +
35 + Vu 2026-09-07 : le cache Cloudflare du site sert une variante
36 + brotli figée (content-encoding: br, cf-cache-status HIT) même si
37 + le client n'annonce que gzip — requests ne peut pas la décoder et
38 + .text devient du binaire (0 annonce parsée, faux « 0 trouvé ok »).
39 + Si le marqueur attendu est absent, on force un MISS via un
40 + paramètre cache-buster ; s'il manque toujours, échec franc.
41 + """
42 + html = self.get(url).text
43 + if marker in html:
44 + return html
45 + html = self.get(url, params={"lkb": int(time.time())}).text
46 + if marker not in html:
47 + raise RuntimeError(
48 + f"brio : contenu inattendu (marqueur {marker!r} absent, "
49 + f"cache CDN illisible ?) — {url}")
50 + return html
51 +
31 52 def fetch(self) -> list[Listing]:
32 53 # 1) Page d'accueil : prix « à partir de » par type (ex. « 3½ à
33 54 # partir de 1500$ »), services de l'immeuble (blurbs Divi) et
@@ -36,7 +57,7 @@ class BrioConnector(BaseConnector):
36 57 services: list[str] = []
37 58 contact: dict = {}
38 59 try:
39 − home = self.get(HOME_URL).text
60 + home = self._html(HOME_URL, "partir de")
40 61 home_txt = re.sub(r"<[^>]+>", " ", home)
41 62 for m in re.finditer(r"(\d)\s*½\s*à\s*partir\s*de\s*([\d\s ]+)\$",
42 63 home_txt):
@@ -58,9 +79,16 @@ class BrioConnector(BaseConnector):
58 79 pass
59 80
60 81 # 2) Page des appartements : hotspots par étage
61 − html = self.get(LIST_URL).text
82 + html = self._html(LIST_URL, "hotspot-info")
62 83 soup = BeautifulSoup(html, "html.parser")
63 84
85 + infos = soup.select("div.hotspot-info")
86 + if not infos:
87 + # une page complète contient toujours les hotspots (Disponible
88 + # ET Loué) — leur absence = rendu partiel, pas un inventaire vide
89 + raise RuntimeError("brio : aucun hotspot-info dans la page des "
90 + "appartements (rendu partiel ?)")
91 +
64 92 # Galerie générale de l'immeuble (photos des pièces)
65 93 gallery = []
66 94 for img in soup.select("img"):
@@ -71,7 +99,7 @@ class BrioConnector(BaseConnector):
71 99 gallery = list(dict.fromkeys(gallery))[:8]
72 100
73 101 listings: list[Listing] = []
74 − for info in soup.select("div.hotspot-info"):
102 + for info in infos:
75 103 try:
76 104 title_el = info.select_one(".hotspot-title")
77 105 if not title_el:
78 106