SPB Git forge

spb/job-ka

Public
229commits 1branches 0releases
38.1 MBsize
maindefault branch
1 h agolast push
HTML 82.1% Python 14.6% TypeScript 1.9% CSS 1% JavaScript 0.5%

[ka6] fix connecteur lassonde: garde anti faux-zéro sur la page liste — le site (derrière Sucuri depuis 2026-08) sert parfois un 200 sans la section emplois ni marqueur de challenge connu (constaté 2026-09-05 21h09, sync à 0 alors que 32 offres réelles, is_blocked muet), le conteneur 'job-result-default' est maintenant exigé avec retry+backoff sinon erreur explicite dans sync_log; sync rejoué: 32 offres (médiane 32.0)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simon-Pierre Boucher committed 18 days ago (Sep 6, 2026) parent adb8637

1 changed file +31 −2

modified jobka/connectors/lassonde.py +31 −2
@@ -13,13 +13,19 @@
13 13 # (challenge JS servi en 307 sans Location → 0 offre en direct) —
14 14 # fetch via get_resilient(render_js=True) : direct d'abord,
15 15 # escalade Scrapfly ASP+rendu JS si challenge.
16 −# Créé : 2026-08-18 Modifié : 2026-08-31
16 +# 2026-09-05 : garde anti faux-zéro sur la page liste — le site sert
17 +# parfois un 200 sans la section emplois ni marqueur Sucuri connu
18 +# (sync à 0 le 2026-09-05 21h09 alors que 32 offres réelles) ; le
19 +# conteneur `job-result-default` est maintenant exigé, retry+backoff
20 +# sinon erreur explicite dans sync_log.
21 +# Créé : 2026-08-18 Modifié : 2026-09-05
17 22 # =============================================================================
18 23 from __future__ import annotations
19 24
20 25 import html as _html
21 26 import os
22 27 import re
28 +import time
23 29
24 30 from ..schema import JobPosting
25 31 from . import _jsonld
@@ -62,10 +68,33 @@ class LassondeConnector(BaseConnector):
62 68 return {}
63 69 return {"description_html": m.group(1)[:60000]}
64 70
71 + def _fetch_list(self) -> str:
72 + # Le site sert parfois un 200 sans la section emplois et sans aucun
73 + # marqueur Sucuri connu (constaté le 2026-09-05 21h09 : sync à 0 offre
74 + # alors que la page réelle en listait 32). Une vraie page liste porte
75 + # toujours le conteneur `job-result-default` (avec le compteur
76 + # filters-list-count), même si l'employeur n'affichait aucune offre :
77 + # son absence = challenge/page tronquée, pas une liste vide — on
78 + # retente avec backoff puis on lève une erreur explicite plutôt que
79 + # de rapporter un faux « 0 offre ».
80 + last = ""
81 + for attempt in range(3):
82 + resp = self.get_resilient(self.LIST_URL, render_js=True)
83 + html = resp.text
84 + if "job-result-default" in html:
85 + return html
86 + last = (f"HTTP {resp.status_code} via "
87 + f"{getattr(resp, 'via', 'direct')}, {len(html)} octets")
88 + if attempt < 2:
89 + time.sleep(10 * (attempt + 1))
90 + raise RuntimeError(
91 + "lassonde: page liste sans la section emplois "
92 + f"(job-result-default) après 3 essais ({last})")
93 +
65 94 def fetch(self) -> list[JobPosting]:
66 95 # Sucuri Cloudproxy depuis 2026-08 : rendu JS obligatoire (challenge
67 96 # cookie), le direct est retenté d'abord à chaque cycle (gratuit)
68 − page = self.get_resilient(self.LIST_URL, render_js=True).text
97 + page = self._fetch_list()
69 98 out: list[JobPosting] = []
70 99 details_used = 0
71 100 seen: set[str] = set()
72 101