SPB Git forge

spb/immo-ka

Public

Immo-Ka — agrégateur des propriétés à vendre au Québec (73 connecteurs, ~40 000 annonces, React+FastAPI)

112commits 1branches 0releases
125.4 MBsize
maindefault branch
13 days agolast push
Python 47.5% HTML 27.9% TypeScript 15.5% CSS 7.2% JavaScript 2%

feat: duproprio — fiche complète (dimensions des pièces, caractéristiques, remarques du proprio, description longue, année/superficies)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Simon-Pierre Boucher committed 1 mo ago (Aug 13, 2026) parent d81a95a

1 changed file +61 −1

modified immoka/connectors/duproprio.py +61 −1
@@ -51,7 +51,7 @@ class DuProprioConnector(BaseConnector):
51 51 by_id[lst.external_id] = lst
52 52 listings = list(by_id.values())
53 53 du.enrich(self, listings, DETAIL_LIMIT, _parse_dp_detail,
54 − key="v1", fetch_html=self._fetch_detail)
54 + key="v2", fetch_html=self._fetch_detail)
55 55 return listings
56 56
57 57 # -- sitemaps --------------------------------------------------------------
@@ -149,6 +149,66 @@ def _parse_dp_detail(html: str) -> dict:
149 149 ms = re.search(r"(\d+)\s*salle[s]?\s*de\s*bain", text, re.I)
150 150 if ms:
151 151 out["bathrooms"] = int(ms.group(1))
152 +
153 + details: dict = {}
154 + features: list[str] = []
155 +
156 + # --- Dimensions des pièces (tableau des pièces) --------------------------
157 + rooms = []
158 + for blk in re.split(r'listing-rooms-details__table__item-container',
159 + html)[1:41]:
160 + blk = blk.split("item-container")[0]
161 + def cell(marker):
162 + m = re.search(marker + r'[^>]*>(.*?)</div>', blk, re.S)
163 + return re.sub(r"\s+", " ", _h_unescape(m.group(1))).strip() if m else ""
164 + nom = cell(r'item--room"')
165 + niveau = cell(r'item--storey"').replace("Étage :", "").strip()
166 + dim = cell(r'item--dimensions"').replace("Dimensions :", "").strip()
167 + rev = cell(r'item--flooring"').replace("Plancher :", "").strip()
168 + if nom:
169 + rooms.append({"nom": nom, "niveau": niveau,
170 + "dimensions": dim, "revetement": rev})
171 + if rooms:
172 + details["pieces"] = rooms[:30]
173 +
174 + # --- Caractéristiques de la propriété (rangées pointillées) ---------------
175 + for lm, vm in re.findall(
176 + r'listing-box__dotted-row">\s*<div>(.*?)</div>\s*<div>\s*</div>\s*'
177 + r'<div>(.*?)</div>', html, re.S):
178 + label = re.sub(r"\s+", " ", _h_unescape(lm)).strip()
179 + value = re.sub(r"\s+", " ", _h_unescape(vm)).strip()
180 + if not label or not value or label == "Prix demandé":
181 + continue
182 + if label == "Année de construction":
183 + my = re.search(r"(19|20)\d{2}", value)
184 + if my:
185 + out["year_built"] = int(my.group(0))
186 + elif label in ("Superficie du terrain", "Superficie habitable",
187 + "Aire habitable", "Superficie du bâtiment"):
188 + mp = re.search(r"([\d\s,.]+)\s*pi", value)
189 + if mp:
190 + try:
191 + sqft = float(mp.group(1).replace(" ", "").replace(",", ""))
192 + out["lot_sqft" if "terrain" in label else "area_sqft"] = sqft
193 + except ValueError:
194 + pass
195 + features.append(f"{label} : {value}")
196 + details[label] = value
197 +
198 + # --- Remarques du proprio (texte long, souvent plus riche que le JSON-LD) --
199 + mrq = re.search(r'listing-owners-comments__description[^>]*>(.*?)</div>',
200 + html, re.S)
201 + if mrq:
202 + remarques = re.sub(r"\s+", " ", _h_unescape(mrq.group(1))).strip()
203 + if remarques:
204 + details["remarques_proprio"] = remarques[:6000]
205 + if len(remarques) > len(out.get("description") or ""):
206 + out["description"] = remarques[:6000]
207 +
208 + if features:
209 + out["features"] = features
210 + if details:
211 + out["details"] = details
152 212 return out
153 213
154 214
155 215