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