[ka6] connecteur cusm_ir: diagnostic du mur Radware hCaptcha host-wide *.njoyn.com (2026-09-02) — non contournable (Scrapfly/BrightData/Oxylabs/Chromium réel tous bloqués), erreur explicite via _radware_hint, offres en base préservées
1 changed file +32 −2
modified
jobka/connectors/njoyn.py
+32 −2
@@ -5,7 +5,7 @@ | ||
| 5 | 5 | # Fichier : jobka/connectors/njoyn.py |
| 6 | 6 | # Rôle : Classe de plateforme Njoyn (CGI) — très répandu au secteur public |
| 7 | 7 | # québécois (santé, villes, sociétés d'État). HTML serveur + jeton. |
| 8 | −# Créé : 2026-08-18 Modifié : 2026-08-26 | |
| 8 | +# Créé : 2026-08-18 Modifié : 2026-09-04 | |
| 9 | 9 | # ============================================================================= |
| 10 | 10 | """Plateforme Njoyn (CGI). |
| 11 | 11 | |
@@ -20,6 +20,18 @@ une ligne de tableau (habillage classique) pointant vers | ||
| 20 | 20 | tenants, mettre ``USE_SCRAPFLY = True`` (contournement ASP). Les babillards |
| 21 | 21 | servis sur un domaine personnalisé (ex. emplois.santemontreal.qc.ca) se |
| 22 | 22 | consultent directement. |
| 23 | + | |
| 24 | +⚠⚠ Incident 2026-09-02 : CGI a durci Radware sur TOUT *.njoyn.com — mur | |
| 25 | +hCaptcha INTERACTIF (validate.perfdrive.com) servi à chaque nouveau | |
| 26 | +visiteur, humains compris. Toute la pile d'escalade échoue : Scrapfly | |
| 27 | +(ASP, render_js, pool résidentiel → page captcha ou 422), Bright Data | |
| 28 | +(reject_block « captcha or protection page found »), Oxylabs (Realtime | |
| 29 | +render + proxies résidentiels -cc-CA), et même un vrai Chromium headed | |
| 30 | +(le clic sur la case hCaptcha ouvre un puzzle imagé). Rien à contourner | |
| 31 | +côté code : ``_fetch_listing`` identifie le mur dans son message d'erreur | |
| 32 | +et le sync reste en échec (les offres en base ne sont pas retirées). | |
| 33 | +Surveiller la levée du mur — les tenants sur domaine personnalisé | |
| 34 | +(sante_montreal, stl_laval, villes) ne sont PAS touchés. | |
| 23 | 35 | """ |
| 24 | 36 | from __future__ import annotations |
| 25 | 37 | |
@@ -255,6 +267,23 @@ class NjoynConnector(BaseConnector): | ||
| 255 | 267 | it.get("fields", {}).get("villes et arrondissements") or "") |
| 256 | 268 | return bool(place) and is_quebec_location(place.replace("-", " ")) |
| 257 | 269 | |
| 270 | + def _radware_hint(self) -> str: | |
| 271 | + """Diagnostic post-échec : sonde la page liste en direct (une seule | |
| 272 | + requête, sans Scrapfly) pour vérifier si le domaine sert le mur | |
| 273 | + hCaptcha Radware (redirection validate.perfdrive.com). Voir le | |
| 274 | + docstring du module — incident host-wide *.njoyn.com du 2026-09-02, | |
| 275 | + aucune voie de contournement dans la pile d'escalade.""" | |
| 276 | + try: | |
| 277 | + r = self.get(self._url("joblisting")) | |
| 278 | + if "perfdrive" in (r.url or "") or "Radware" in r.text[:4000]: | |
| 279 | + return (" — cause identifiée : mur anti-bot Radware " | |
| 280 | + "(hCaptcha interactif, validate.perfdrive.com) sur " | |
| 281 | + "tout *.njoyn.com; pile d'escalade complète en échec " | |
| 282 | + "(vu 2026-09-02→), attendre la levée du mur") | |
| 283 | + except Exception: | |
| 284 | + pass | |
| 285 | + return "" | |
| 286 | + | |
| 258 | 287 | def _fetch_listing(self) -> tuple[str, list[dict]]: |
| 259 | 288 | """Page liste + items parsés, avec garde-fou : une réponse vide ou |
| 260 | 289 | anti-bot (Scrapfly en échec renvoie "" — voir base.get_scrapfly) ne |
@@ -271,7 +300,8 @@ class NjoynConnector(BaseConnector): | ||
| 271 | 300 | time.sleep(10) |
| 272 | 301 | raise RuntimeError( |
| 273 | 302 | f"njoyn {self.source_id}: page liste illisible " |
| 274 | − f"(vide ou anti-bot, {len(page)} octets) — sync avorté") | |
| 303 | + f"(vide ou anti-bot, {len(page)} octets) — sync avorté" | |
| 304 | + + self._radware_hint()) | |
| 275 | 305 | |
| 276 | 306 | def fetch(self) -> list[JobPosting]: |
| 277 | 307 | page, items = self._fetch_listing() |
| 278 | 308 | |