]+)>", h):
if "tag=rent" not in block:
continue
m = re.search(r"\bid=([\w-]+)", block)
if not m:
continue
lid = m.group(1)
it = items.setdefault(lid, {"island": island})
m = re.search(r'alt="([^"]+)"', block)
if m:
it["name"] = _text(m.group(1))
m = re.search(r'cap="(\d+)\s*chambres?\s*\((\d+)\s*pers',
block)
if m:
it["bedrooms"] = float(m.group(1))
it["capacity"] = float(m.group(2))
m = re.search(r"\bdayrate=(\d+(?:\.\d+)?)", block)
if m:
it["dayrate"] = float(m.group(1))
m = re.search(r"href=(https://alouerauxiles\.com/[\w-]+)\b",
block)
if m:
it["url"] = m.group(1)
return items
# -- page détail ------------------------------------------------------
def _detail(self, lid: str) -> dict:
h = self.get(f"{SITE}/php/page_fr.php", params={"id": lid}).text
d: dict = {}
m = re.search(r"
([^<]+)", h)
if m:
d["type_label"] = _text(m.group(1))
m = re.search(r'title="(\d+)\s*personnes', h)
if m:
d["capacity"] = float(m.group(1))
m = re.search(r'title="(\d+)\s*chambres?"', h)
if m:
d["bedrooms"] = float(m.group(1))
m = re.search(r'title="(\d+)\s*salle\(?s?\)?\s*de\s*bain', h)
if m:
d["bathrooms"] = float(m.group(1))
m = re.search(r'title="\s*animaux([^"]*)"', h)
if m:
d["pets"] = "non" if "non" in m.group(1).lower() else "oui"
# description : bloc txtdiv (nom + texte de présentation)
m = re.search(r"(?s)
(.*?)
", h)
if m:
frag = re.sub(r"(?s)
.*?", " ", m.group(1))
d["description"] = _text(frag)[:4000]
m = re.search(r"(?s)
([^<]+)",
h[h.find("txtdiv"):] if "txtdiv" in h else "")
if m:
d["title"] = _text(m.group(1))
m = re.search(r"CITQ\D{0,12}(\d{6})", h, re.I)
if m:
d["citq"] = m.group(1)
# adresse : rue + « G4T 3H6 l'Étang-du-Nord » → ville après le code
m = re.search(r"(?s)Adresse\s*:\s*(.*?)(?:
", m.group(1))]
lines = [x for x in lines if x]
if lines:
d["address"] = lines[0]
for x in lines:
pm = re.search(r"[A-Z]\d[A-Z]\s?\d[A-Z]\d\s+(.{3,40})$", x)
if pm:
d["city"] = pm.group(1).strip()
break
# commodités : lignes « - … » entre « Commodités : » et « À proximité »
m = re.search(r"(?s)Commodités\s*:(.*?)(?:À proximité|Contact\s*:|$)",
h)
if m:
amens = [_text(x).lstrip("- ").strip()
for x in re.split(r"
", m.group(1))]
d["amenities"] = [a for a in amens if 2 <= len(a) <= 80][:50]
# photos du dossier de l'annonce (originaux img/, pas les vignettes)
imgs: list[str] = []
for u in re.findall(r"[\"'=](?:\.\./)?(pages/idlm/rent/[\w-]+/img/"
r"[^\"'\s>]+\.(?:jpe?g|png|webp))", h, re.I):
full = f"{SITE}/{u}"
if full not in imgs:
imgs.append(full)
d["images"] = imgs[:20]
return d
# -- contrat ----------------------------------------------------------
def fetch(self) -> list[StListing]:
listings: list[StListing] = []
for lid, it in self._island_items().items():
key = f"{it.get('name', '')}|{it.get('capacity', '')}|" \
f"{it.get('dayrate', '')}|{it.get('bedrooms', '')}"
try:
det = self.detail(lid, key, lambda i=lid: self._detail(i))
except Exception:
det = {}
title = det.get("title") or it.get("name") or ""
if not title:
continue
type_label = (det.get("type_label") or "").lower()
ptype = ""
for needle, canon in _TYPES.items():
if needle in type_label:
ptype = canon
break
price = it.get("dayrate")
city = det.get("city") or it["island"].replace("-", " ").title()
listings.append(StListing(
source=self.source_id,
external_id=lid,
url=it.get("url") or f"{SITE}/php/page_fr.php?id={lid}",
title=title,
property_type=ptype or "Maison",
address=det.get("address") or "",
city=city,
region="Îles-de-la-Madeleine",
price_night=price,
price_label=(f"à partir de {price:.0f} $ / nuit"
if price else ""),
capacity=det.get("capacity") or it.get("capacity"),
bedrooms=det.get("bedrooms") or it.get("bedrooms"),
bathrooms=det.get("bathrooms"),
pets=det.get("pets"),
citq=det.get("citq") or "",
description=det.get("description") or "",
amenities=det.get("amenities") or [],
details={"ile": it["island"]},
images=det.get("images") or [],
))
return listings