SPB Git forge

spb/sorti-ka

Public

Toutes les sorties et tous les événements du Québec, un seul endroit — 7 connecteurs, fiches SSR, design Groupe KA.

58commits 1branches 0releases
13.7 MBsize
maindefault branch
17 days agolast push
HTML 82.9% Python 15.2% TypeScript 0.9% JavaScript 0.7%
4.4 KB · 99 lines python
Raw Blame History
1# -----------------------------------------------------------------------------2# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)3# Auteur : Simon-Pierre Boucher — contact@spboucher.ai4# connectors/abitibi.py : Tourisme Abitibi-Témiscamingue (ATR, vague ATR 2026-08)5#   Source    : abitibi-temiscamingue.org/festivals-et-evenements/ (domaine6#               tourisme-abitibi-temiscamingue.org → redirection) — répertoire7#               officiel des festivals et événements de l'ATR (WordPress,8#               gisement Tourinsoft : images mto.media.tourinsoft.eu).9#   Extraction: pages liste RENDUES SERVEUR /festivals-et-evenements/[page/N/]10#               (~3 pages × 12 cartes vérifié 2026-08-25) : lien11#               /evenements/<slug>/, image, ville, titre, plage de dates FR12#               (« Du 26 au 30 août 2026 »). Le site est derrière un challenge13#               Cloudflare (403 + « Just a moment ») → chaque GET passe par14#               l'AUTO-ESCALADE anti-bot de BaseConnector.get() (Oxylabs →15#               Scrapfly ASP → Bright Data) ; budget borné : ABT_PAGE_MAX16#               pages/sync, AUCUNE fiche détail (la carte porte tout ce que la17#               source publie de structuré ; wp-json est aussi derrière le18#               challenge et n'expose pas de CPT événement).19#   Accès     : pages publiques de l'ATR — requêtes throttlées et plafonnées20#               (coût par requête), identification honnête, lien vers la fiche21#               originale. Ni prix, ni heure, ni description structurés sur la22#               carte → jamais inventés.23# -----------------------------------------------------------------------------24from __future__ import annotations2526import os27import re2829from ..normalize import clean_text, parse_date_range_fr30from ..schema import Event31from .base import BaseConnector3233BASE = "https://abitibi-temiscamingue.org"34LIST_URL = BASE + "/festivals-et-evenements/"35ABT_PAGE_MAX = int(os.environ.get("ABT_PAGE_MAX", "4"))3637# une carte du répertoire : post-id, image de fond, lien, ville, titre, date38_CARD_RE = re.compile(39    r'data-post-id="(\d+)">\s*'40    r'<div class="quanto-blog-thumb">\s*'41    r'<a class="aspect-ratio" href="(https://abitibi-temiscamingue\.org/'42    r'evenements/[^"]+)"\s*style="background-image: url\(\'([^\']*)\'\);">'43    r'</a>.*?<li><span>([^<]*?)\s*<svg.*?'44    r'<p class="h6"><a href="\2">(.*?)</a></p>\s*'45    r'(?:<p class="h6 repertoire-preview-date"[^>]*>([^<]*)</p>)?', re.S)464748class AbitibiConnector(BaseConnector):49    source_id = "abitibi"50    request_delay = 1.0          # chaque requête peut coûter un crédit Scrapfly51    timeout = 1205253    def _cards(self, html: str) -> list[dict]:54        out = []55        for m in _CARD_RE.finditer(html):56            pid, url, img, city, title, date_raw = m.groups()57            start, end = parse_date_range_fr(date_raw or "")58            out.append({59                "external_id": pid,60                "url": url,61                "title": clean_text(title),62                "city": clean_text(city),63                "start_date": start, "end_date": end,64                "image": (img or "").strip(),65            })66        return out6768    def fetch(self) -> list[Event]:69        events: list[Event] = []70        seen: set[str] = set()71        for page in range(1, ABT_PAGE_MAX + 1):72            url = LIST_URL if page == 1 else f"{LIST_URL}page/{page}/"73            try:74                cards = self._cards(self.get(url).text)75            except Exception:76                break77            new = [c for c in cards if c["external_id"] not in seen]78            if not new:79                break80            for c in new:81                seen.add(c["external_id"])82                if not c["title"]:83                    continue84                events.append(Event(85                    source=self.source_id,86                    external_id=c["external_id"],87                    url=c["url"],88                    title=c["title"],89                    raw_categories=["Festivals et événements", c["title"]],90                    city=c["city"],91                    tourist_region="Abitibi-Témiscamingue",92                    start_date=c["start_date"],93                    end_date=c["end_date"],94                    image=c["image"],95                ))96            if len(cards) < 12:      # page incomplète (12/page) = dernière97                break98        return events99