Lou·Ka — tous les logements à louer du Québec, un seul endroit.
HTML 98.9%
Python 0.6%
1# -----------------------------------------------------------------------------2# Lou-Ka — Location court terme3# connectors/domesstcome.py : Dômes St-Côme (domesstcome.com) — 4 dômes4# géodésiques avec spa privé et vue panoramique à Saint-Côme (Lanaudière).5#6# Méthode : sitemap Wix (index) → dynamic-domes_*-sitemap.xml → 4 URLs7# /domes/<slug> + lastmod (clé du cache détail). Pages Wix statiques :8# - <h1> = nom du dôme ; sous-titre « Vue panoramique | Spa privé » ;9# - « À partir de 370$/nuit » → price_night ;10# - sections LITS / AUTRES / CUISINE / À L'EXTÉRIEUR / SALLE DE BAIN11# (texte riche Wix) → amenities ; beds = nb de « Lit … » sous LITS ;12# - CITQ (6 chiffres) dans le pied de page ;13# - images : médias wixstatic ~mv2 servis en grand (w ≥ 900) — la galerie14# Pro Gallery est chargée en JS, seuls les héros sont statiques.15# -----------------------------------------------------------------------------16from __future__ import annotations1718import html as _html19import re2021from ..schema import StListing22from .base import StConnector2324SITE = "https://www.domesstcome.com"25SITEMAP = SITE + "/sitemap.xml"2627_SECTIONS = ("LITS", "AUTRES", "CUISINE", "À L'EXTÉRIEUR", "SALLE DE BAIN")2829_TAG_RE = re.compile(r"<[^>]+>")303132def _lines(fragment: str) -> list[str]:33 """HTML riche Wix → lignes de texte propres (CSS inline filtré)."""34 txt = re.sub(r"\|(?:\s*\|)+", "\n",35 re.sub(r"\s+", " ", _TAG_RE.sub("|", fragment)))36 out = []37 for x in txt.split("\n"):38 x = _html.unescape(x).strip(" |").strip()39 x = re.sub(r"\s*\|\s*", " | ", x)40 if x and len(x) > 2 and "{" not in x and "--" not in x:41 out.append(x)42 return out434445class DomesStCome(StConnector):46 source_id = "domesstcome"47 request_delay = 1.04849 # -- page détail ----------------------------------------------------------50 def _detail(self, url: str) -> dict:51 h = self.get(url).text52 d: dict = {}5354 m = re.search(r"(?s)<h1[^>]*>(.*?)</h1>", h)55 if m:56 d["title"] = _html.unescape(57 re.sub(r"\s+", " ", _TAG_RE.sub(" ", m.group(1)))).strip()5859 m = re.search(r"À partir de\s*(\d+)\s*\$\s*/\s*nuit", h)60 if m:61 d["price_night"] = float(m.group(1))6263 m = re.search(r"CITQ\D{0,25}(\d{6})", h)64 if m:65 d["citq"] = m.group(1)6667 # sous-titre (« Vue panoramique | Spa privé ») → description68 i, j = h.find("<h1"), h.find("LITS")69 if 0 <= i < j:70 head = [x for x in _lines(h[i:j])71 if x != d.get("title") and "À partir de" not in x72 and "Détails" not in x]73 if head:74 d["description"] = head[0][:500]7576 # sections LITS…SALLE DE BAIN → amenities (entêtes exclues)77 i = h.find("LITS")78 j = h.find("Réserver", max(i, 0))79 beds = 080 amen: list[str] = []81 if 0 <= i < j:82 unescaped_secs = {s for s in _SECTIONS}83 current = ""84 for x in _lines(h[i:j]):85 if x.upper() in unescaped_secs:86 current = x.upper()87 continue88 if x in amen or len(x) > 80:89 continue90 amen.append(x)91 if current == "LITS" and re.match(r"Lit\b", x, re.I):92 beds += 193 if amen:94 d["amenities"] = amen95 if beds:96 d["beds"] = float(beds)9798 # images : médias wixstatic servis en grand (héros)99 big: dict[str, int] = {}100 for mid, w in re.findall(r"static\.wixstatic\.com/media/"101 r"([\w~%.]+)/v1/fill/w_(\d+)", h):102 w = int(w)103 if w >= 900:104 big[mid] = max(big.get(mid, 0), w)105 imgs = [f"https://static.wixstatic.com/media/{mid}"106 for mid in big][:15]107 if imgs:108 d["images"] = imgs109 return d110111 # -- contrat --------------------------------------------------------------112 def fetch(self) -> list[StListing]:113 index = self.get(SITEMAP).text114 entries: list[tuple[str, str]] = []115 for sub in re.findall(r"<loc>([^<]+)</loc>", index):116 if "dynamic-domes" not in sub:117 continue118 xml = self.get(sub).text119 entries += re.findall(r"(?s)<url>\s*<loc>([^<]+)</loc>"120 r"(?:\s*<lastmod>([^<]*)</lastmod>)?", xml)121122 listings: list[StListing] = []123 vus: set[str] = set()124 for url, lastmod in entries:125 m = re.match(r"https://www\.domesstcome\.com/domes/([^/]+)/?$", url)126 if not m:127 continue128 slug = m.group(1)129 if slug in vus:130 continue131 vus.add(slug)132133 det = self.detail(slug, lastmod or "v1",134 lambda u=url: self._detail(u))135 title = det.get("title") or ""136 if not title:137 continue138139 price = det.get("price_night")140 listings.append(StListing(141 source=self.source_id,142 external_id=slug,143 url=url,144 title=title,145 property_type="Dôme",146 city="Saint-Côme",147 region="Lanaudière",148 price_night=price,149 price_label=f"À partir de {price:g} $ / nuit" if price else "",150 beds=det.get("beds"),151 citq=det.get("citq") or "",152 description=det.get("description") or "",153 amenities=det.get("amenities") or [],154 details={"domain": "Dômes St-Côme"},155 images=det.get("images") or [],156 ))157 return listings158