[ka6] fix connecteur stl_laval: garde-fou njoyn — une page liste vide/anti-bot (Scrapfly en échec renvoie "") ne passe plus pour un babillard à 0 offre; retente une fois puis lève une erreur (njoyn.py)
1 changed file +21 −4
modified
jobka/connectors/njoyn.py
+21 −4
@@ -5,7 +5,7 @@ | |||||
| 5 | # Fichier : jobka/connectors/njoyn.py | 5 | # Fichier : jobka/connectors/njoyn.py | ||
| 6 | # Rôle : Classe de plateforme Njoyn (CGI) — très répandu au secteur public | 6 | # Rôle : Classe de plateforme Njoyn (CGI) — très répandu au secteur public | ||
| 7 | # québécois (santé, villes, sociétés d'État). HTML serveur + jeton. | 7 | # québécois (santé, villes, sociétés d'État). HTML serveur + jeton. | ||
| 8 | −# Créé : 2026-08-18 Modifié : 2026-08-25 | 8 | +# Créé : 2026-08-18 Modifié : 2026-08-26 | ||
| 9 | # ============================================================================= | 9 | # ============================================================================= | ||
| 10 | """Plateforme Njoyn (CGI). | 10 | """Plateforme Njoyn (CGI). | ||
| 11 | 11 | ||||
@@ -27,6 +27,7 @@ import hashlib | |||||
| 27 | import html as _html | 27 | import html as _html | ||
| 28 | import os | 28 | import os | ||
| 29 | import re | 29 | import re | ||
| 30 | +import time | ||||
| 30 | 31 | ||||
| 31 | from ..schema import JobPosting, clean_html | 32 | from ..schema import JobPosting, clean_html | ||
| 32 | from .base import BaseConnector | 33 | from .base import BaseConnector | ||
@@ -254,10 +255,26 @@ class NjoynConnector(BaseConnector): | |||||
| 254 | it.get("fields", {}).get("villes et arrondissements") or "") | 255 | it.get("fields", {}).get("villes et arrondissements") or "") | ||
| 255 | return bool(place) and is_quebec_location(place.replace("-", " ")) | 256 | return bool(place) and is_quebec_location(place.replace("-", " ")) | ||
| 256 | 257 | ||||
| 258 | + def _fetch_listing(self) -> tuple[str, list[dict]]: | ||||
| 259 | + """Page liste + items parsés, avec garde-fou : une réponse vide ou | ||||
| 260 | + anti-bot (Scrapfly en échec renvoie "" — voir base.get_scrapfly) ne | ||||
| 261 | + doit JAMAIS passer pour un babillard vide. Toute page Njoyn légitime, | ||||
| 262 | + même sans offre, contient le formulaire xweb.asp — sinon on retente | ||||
| 263 | + une fois puis on lève une erreur (sync en échec, pas « 0 trouvé »).""" | ||||
| 264 | + for attempt in range(2): | ||||
| 265 | + page = self._html(self._url("joblisting")) | ||||
| 266 | + items = (self._parse_articles(page) or self._parse_table(page) | ||||
| 267 | + or self._parse_rows(page)) | ||||
| 268 | + if items or re.search(r"xweb\.asp", page, re.I): | ||||
| 269 | + return page, items | ||||
| 270 | + if attempt == 0: | ||||
| 271 | + time.sleep(10) | ||||
| 272 | + raise RuntimeError( | ||||
| 273 | + f"njoyn {self.source_id}: page liste illisible " | ||||
| 274 | + f"(vide ou anti-bot, {len(page)} octets) — sync avorté") | ||||
| 275 | + | ||||
| 257 | def fetch(self) -> list[JobPosting]: | 276 | def fetch(self) -> list[JobPosting]: | ||
| 258 | − page = self._html(self._url("joblisting")) | 277 | + page, items = self._fetch_listing() | ||
| 259 | − items = (self._parse_articles(page) or self._parse_table(page) | ||||
| 260 | − or self._parse_rows(page)) | ||||
| 261 | items = self._paginate(page, items) | 278 | items = self._paginate(page, items) | ||
| 262 | items = [it for it in items if self._keep(it)] | 279 | items = [it for it in items if self._keep(it)] | ||
| 263 | out: list[JobPosting] = [] | 280 | out: list[JobPosting] = [] | ||
| 264 | 281 | ||||