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
3 days agolast push
HTML 98.9% Python 0.6%
5.8 KB · 158 lines python
Raw Blame History
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