veille-sq-emploi
Veille automatisée des offres d'emploi à la Sûreté du Québec via l'API ManitouLS
Quand l'utiliser (Trigger)
Déclenchement standard selon le contexte de l'écosystème Hermès.
Mode d'emploi (Usage)
Mode d'emploi standard via l'agent Hermès. Veille SQ Emploi
Veille automatisée des offres d’emploi étudiantes à la Sûreté du Québec.
API ManitouLS
Le portail emplois.carrieres.gouv.qc.ca utilise le système ManitouLS avec une API REST publique (sans authentification pour la recherche).
Endpoints clés
| Endpoint | Méthode | Description |
|---|---|---|
/manitouLS/cache/postingDisplayFilters/fr | GET | Filtres disponibles (divisions, types, régions) |
/search/postingFilteredAI | POST | Recherche d’offres |
/search/posting/{no_offre}/{lang} | GET | Détail offre (JSON avec User-Agent + Accept: application/json) |
Recherche d’offres
# Offres étudiantes à la Sûreté du Québec (0390)
curl -s -X POST \
-H "Content-Type: application/json" \
-H "Accept-Language: fr-CA" \
-H "User-Agent: Mozilla/5.0 ...Chrome/125.0.0.0 Safari/537.36" \
-d '{"numberPerPage":50,"page":1,"filter":{"typePoste":["ÉTU"],"division":["0390"],"keyword":"","region":[],"municipality":[],"postedSince":"","qualification":[],"studyDiscipline":[],"levelEducation":[],"localisation":{"latitude":"","longitude":"","distance":20}},"sort":"date","language":"fr"}' \
"https://emplois.carrieres.gouv.qc.ca/search/postingFilteredAI"
Scan par ID direct (méthode brute-force)
L’endpoint /search/posting/{ID}/fr accepte les requêtes sans authentification
avec les bons headers et retourne le JSON complet de l’offre. C’est la méthode
privilégiée par le script V2 pour scanner les offres récentes.
Headers requis :
curl -s "https://emplois.carrieres.gouv.qc.ca/search/posting/14427/fr" \
-H "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:130.0) Gecko/20100101 Firefox/130.0" \
-H "Accept: application/json" \
-H "Accept-Language: fr,en;q=0.9" \
-H "Origin: https://emplois.carrieres.gouv.qc.ca"
Champs clés du JSON de réponse (structure section[]) :
⚠️ L’API /search/posting/{ID}/fr retourne désormais un tableau data.section[] avec des paires {id, content}. Les champs plats (data.titre, data.divisionNom) sont vides ou absents.
{
"data": {
"idOffre": 14839,
"titre": "", // plat — vide
"divisionNom": "", // plat — vide
"section": [
{ "id": "titrePoste", "content": "EMPLOI ÉTUDIANT - Agent de soutien..." },
{ "id": "dateLimite", "content": "2026-06-05 23:59" },
{ "id": "categorie", "content": "Agent de soutien aux enquêtes policières" },
{ "id": "typeDuree", "content": "Temporaire ou occasionnel" },
{ "id": "ministereOrganisme", "content": "Sûreté du Québec (0390)" },
{ "id": "descriptionActivite", "content": "HTML des activités..." },
{ "id": "profilRecherche", "content": "HTML du profil recherché..." },
{ "id": "conditionsTravail", "content": "HTML des conditions..." },
{ "id": "adressesLieuTravail", "content": "HTML des adresses..." },
{ "id": "corpsEmploiClasse", "content": "251.30 - Agent de soutien aux enquêtes policières" }
]
}
}
Section IDs utiles pour le parsing :
id | Contenu |
|---|---|
titrePoste | Titre de l’offre |
dateLimite | Date limite ISO (ex: 2026-06-05 23:59) |
categorie | Catégorie d’emploi (ex: Agent de soutien aux enquêtes policières) |
typeDuree | Durée / statut (Temporaire, Régulier, Étudiant) |
ministereOrganisme | Organisation + code division (ex: Sûreté du Québec (0390)) |
descriptionActivite | Description des activités (HTML) |
profilRecherche | Profil recherché (HTML) |
conditionsTravail | Conditions de travail (HTML) |
adressesLieuTravail | Lieux de travail (HTML) |
corpsEmploiClasse | Corps d’emploi + classe (ex: 251.30 - Agent de soutien aux enquêtes policières) |
Parsing pattern (Python) :
section = data.get('data', {}).get('section', [])
fields = {item['id']: item.get('content', '') for item in section}
titre = fields.get('titrePoste', 'N/A')
date_limite = fields.get('dateLimite', 'N/A')
organisation = fields.get('ministereOrganisme', 'N/A')
Note : la propriété data.titre (flat) était utilisée dans l’ancienne version de l’API et peut être vide. Toujours privilégier le parsing par data.section[].
Stratégie de scan : Générer une plage d’IDs numériques croissants et tester chaque ID. Les IDs inactifs retournent un JSON vide ou 404, les IDs actifs retournent l’offre complète. Le script filtre ensuite par division cible.
⚠️ Anti-pattern évité : L’endpoint /search/postingFilteredAI (POST) est
plus lent et moins fiable pour un scan large. La méthode brute-force par ID
est plus rapide avec parallélisation.
Codes division importants
0390— Sûreté du Québec0095— Ministère de la Sécurité publique0096— Ministère de la Cybersécurité et du Numérique0246— Directeur des poursuites criminelles et pénales (DPCP)0413— Bureau des enquêtes indépendantes (BEI)
Types de poste
ÉTU— ÉtudiantSTA— StageC01— RégulierOCC— Occasionnel
Architecture multi-sources (V3 — 2026)
Le système de veille est passé d’un script unique (SQ uniquement) à une architecture multi-sources qui agrège les offres depuis 3 canaux :
┌─────────────────────────────────────────────┐
│ veille_master.sh (orchestrateur) │
├─────────────────────────────────────────────┤
│ Source 1: Gouv. Québec (veille_sq.sh V2) │
│ → scan parallélisé IDs 14400+ │
│ → SQ, MSP, DPCP, BEI, MCN │
│ → ~35s pour 500 IDs (10 jobs parallèles) │
├─────────────────────────────────────────────┤
│ Source 2: Job Bank Canada (veille_jobbank) │
│ → scan HTML multi-mots-clés │
│ → Télétravail filtré │
│ → Offres fédérales + privées Québec │
│ → ~60s pour 15 mots-clés │
├─────────────────────────────────────────────┤
│ Source 3: Google Jobs (veille_google.sh) │
│ → recherche web ciblée │
│ → jobs Canada agrégés │
└─────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ auto_candidature.sh (générateur) │
│ → lister : offres non traitées │
│ → generer : prépare dossier candidature │
│ → tracker : suivi d'avancement │
│ → candidature_tracker.md │
└─────────────────────────────────────────────┘
Top sites canadiens intégrés (validés par experts/avis)
| # | Site | Source | Télétravail | API dispo |
|---|---|---|---|---|
| 1 | Guichet-Emplois (GC) | Source 2 ✅ | ✅ Oui | ✅ HTML |
| 2 | Emplois Québec (gouv.qc.ca) | Source 1 ✅ | ⚠️ Partiel | ✅ REST |
| 3 | LinkedIn Jobs | Manuel + alertes | ✅ Oui | ❌ Non |
| 4 | Indeed Canada | Google Jobs (S3) | ✅ Oui | ❌ (Cloudflare) |
| 5 | Jobillico | Manuel | ✅ Oui | ❌ Non |
| 6 | Emplois fédéraux (jobs.gc.ca) | Source 2 ✅ | ⚠️ Partiel | ⚠️ HTML |
| 7 | Glassdoor Canada | Google Jobs (S3) | ✅ Oui | ❌ (Security) |
Note : Indeed et Glassdoor sont bloqués par Cloudflare en scraping direct. Leurs offres sont accessibles via Google Jobs (Source 3) et le Guichet-Emplois (Source 2) qui les agrège.
Scoring & Ranking des offres
Après la collecte multi-sources, chaque offre est évaluée avec un score composite (0–100) basé sur 6 critères pondérés. Ce score guide les recommandations matinales.
Critères : 🏛️ Organisation (25pts) · 🤖 IA/Technologie (20pts) · 🕵️ Enquêtes (20pts) · 🏠 Télétravail (15pts) · 📊 Analyste (10pts) · ⏰ Urgence (10pts)
Patron de détection télétravail (ManitouLS) : Scanne les sections
conditionsTravail, renseignementsGeneraux, descriptionActivite dans
data.section[] pour les mots-clés : télétravail, hybride, telework,
remote, modalités de travail.
⚠️ Le télétravail n’est PAS un champ structuré de l’API. Il faut le détecter dans le contenu HTML des sections texte.
👉 Référence complète : references/offer-scoring-methodology.md (grille + algorithme Python + exemples)
Scripts
veille_master.sh — Orchestrateur multi-sources
Chemin : /home/bf/openclaw-workspace/veille_master.sh
Script principal qui lance les 3 sources en séquence et produit un rapport consolidé.
Modes :
bash veille_master.sh full # Scan complet (3 sources, max données)
bash veille_master.sh quick # Scan rapide (2 sources prioritaires)
bash veille_master.sh test # Scan minimal (vérification)
Cron : Tous les matins à 7h (cron job ID: 08edb5b184f4)
veille_sq.sh — Veille automatisée V2 (parallélisée)
Chemin : /home/bf/openclaw-workspace/veille_sq.sh (script principal)
Fichier d’état : last_id_scanned.txt (dernier ID scanné — reprise automatique)
Script Bash qui scanne par plage d’IDs les offres via l’API publique. Version V2 avec parallélisation.
Mécanisme V2 : Scan par plage d’IDs numériques (ID visible dans l’URL de l’offre, ex: 14427).
Utilise xargs -P 10 pour lancer 10 requêtes curl simultanées, supprimant le
sleep séquentiel. Chaque offre récupérée est parsée avec python3 -c en ligne.
Paramètres :
| Variable | Défaut | Description |
|---|---|---|
PARALLEL_JOBS | 10 | Requêtes simultanées (via xargs -P) |
START_ID | $(cat last_id_scanned.txt) | ID de départ — idéal en cron |
RANGE | 500 | Nombre d’IDs à scanner, peut être passé en 2e argument |
Usage :
cd /home/bf/openclaw-workspace
bash veille_sq.sh # Reprend où on s'était arrêté (last_id_scanned.txt)
bash veille_sq.sh 14400 # Forcer un ID de départ
bash veille_sq.sh 14400 1000 # Forcer ID de départ + plage de 1000 IDs
Filtrage : Détection par division dans le JSON. Alerte si la division correspond
à une des cibles configurées (SQ, MSP, DPCP, BEI, MCN) OU si le titre contient
des mots-clés comme analyste, informatique, IA, cybersécurité.
État de scan : last_id_scanned.txt est mis à jour à la fin de chaque run avec
END_ID - 1, garantissant la reprise au prochain run sans doublon.
Performance V2 (constaté) :
| Métrique | V1 (séquentiel) | V2 (xargs -P 10) | Gain |
|---|---|---|---|
| IDs par run | 200 | 500 | 2.5× |
| Temps / 20 IDs | ~10 s | ~9 s (10 jobs) | ~identique |
| Temps / 500 IDs | ~250 s | ~35 s | ~7× plus rapide |
veille_jobbank.sh — Guichet-Emplois Canada
Chemin : /home/bf/openclaw-workspace/veille_jobbank.sh
Scanne le Guichet-Emplois (Job Bank) du gouvernement du Canada avec 10+ mots-clés ciblés pour le profil criminologie/AI/analyste.
Caractéristiques :
- 15 mots-clés ciblés (core + extended)
- Filtrage télétravail intégré (
telework=true) - Détection HTML des offres avec dédoublonnage
- Exclusion des titres hors cible (finance, marketing, vente)
Usage :
bash veille_jobbank.sh full # Scan 15 mots-clés
bash veille_jobbank.sh quick # Scan 5 mots-clés prioritaires
veille_google.sh — Google Jobs
Chemin : /home/bf/openclaw-workspace/veille_google.sh
Recherche Google Jobs avec 8 combinaisons de mots-clés ciblés.
Usage :
bash veille_google.sh
auto_candidature.sh — Générateur de candidatures
Chemin : /home/bf/openclaw-workspace/auto_candidature.sh
Prépare des dossiers de candidature personnalisés pour chaque offre pertinente.
Fonctions :
bash auto_candidature.sh lister # Voir les offres disponibles
bash auto_candidature.sh generer <ID> [src] # Préparer candidature
bash auto_candidature.sh tracker # Suivi d'avancement
candidature_tracker.md — Suivi centralisé
Chemin : /home/bf/openclaw-workspace/candidature_tracker.md
Tableau de suivi avec statuts (🔴 à poster, 🟡 en préparation, 🟢 soumis, etc.)
Script functions (pattern d’export) : Comme xargs lance un sous-shell
sans héritage des fonctions bash, le script utilise un bloc main() en fin
de fichier et exporte les fonctions via export -f scan_offer si nécessaire.
Pour les fonctions complexes, une approche bash -c "..." avec tout le code
en ligne dans la commande xargs est préférée.
Pitfalls :
- La fonction de scan doit être self-contained (toutes les variables et logique
encapsulées) —
xargsne voit pas les définitions de fonctions du script parent echo "..." | xargs -P 10 -I {} bash -c '...{}...'est le pattern fiable- L’API retourne parfois 429 ou timeout sur des plages > 1000 IDs —
PARALLEL_JOBS=10est un bon compromis vitesse/stabilité - Vérifier que
last_id_scanned.txtest restauré après des tests manuels courts (ne pas reculer l’état artificiellement)
Références
| Fichier | Contenu |
|---|---|
references/api-manitouls-spec.md | Spécification complète de l’API ManitouLS (reverse-engineered) |
references/plateformes-ats-quebec.md | Mapping des portails carrière QC (Workday, Avature, Phenom…) |
references/google-sheets-formatting.md | Formatage Google Sheets via batchUpdate API |
references/gmail-smtp-setup.md | Envoi d’email avec pièces jointes via Gmail SMTP |
references/portail-altcha-captcha.md | Altcha CAPTCHA sur le nouveau portail — comportement, contournement |
references/postulation-standalone-script.md | Postulation via script Playwright autonome + SSH vers mx |
references/browser-form-state-pitfall.md | Hermes browser : browser_navigate reset le form state — pattern à éviter + JS single-shot |
references/offer-scoring-methodology.md | NOUVEAU Méthode de scoring/ranking des offres — grille 6 critères, algorithme Python, détection télétravail API |
| Une Google Sheet peut tracker les identifiants pour tous les portails carrière. Exemple : |
# Créer la feuille
sheets create --title "🔐 Identifiants - Portails Emploi" --sheet-name "Identifiants"
# Remplir (tableau 2D)
sheets update SHEET_ID "Identifiants!A1:H12" --values '[[...], [...]]'
# Formater (batchUpdate API)
# Voir references/google-sheets-formatting.md pour le payload complet
Les titres doivent toujours être alignés à gauche (horizontalAlignment: "LEFT").
La feuille utilise data-automation-id pour identifier les cellules de titre et les en-têtes.
Utiliser updateDimensionProperties (pas setDimensionProperties) pour les largeurs.
Cron job (veille quotidienne)
hermes cron create \
--name "Veille SQ - Quotidienne 7h" \
--schedule "0 7 * * *" \
--prompt "Exécute le script de veille et analyse les résultats..." \
--skills devops/veille-sq-emploi \
--toolsets terminal
⚠️ Pitfall : skill path exact. --skills doit être exactement
devops/veille-sq-emploi (le nom complet avec catégorie). Pas seulement
veille-sq-emploi ni un autre skill comme infrastructure-integration.
Si mal attaché, le cron charge le mauvais contexte et la veille échoue.
Pour corriger un cron mal attaché : hermes cron update <job_id> --skills "devops/veille-sq-emploi"
Comportement humain (obligatoire)
L’utilisateur exige que toute navigation/interaction avec le portail simule un comportement humain réaliste :
- Délais aléatoires : 1.5 à 5.5 secondes entre chaque requête API
- Rotation User-Agent : alterner entre Chrome/Edge/Mac
- Headers réalistes : inclure Sec-Fetch-*, Origin, DNT, Accept-Language, Accept-Encoding
- Pauses de navigation : 2 à 5 secondes entre les clics/actions dans le navigateur
- Aucun comportement saccadé ou automatisé rapide
- Navigation : scroll naturel, clics lents, pauses entre chaque étape
- Anti-détection :
navigator.webdriver = undefineddans les scripts Playwright - Screenshot automatique en cas d’erreur
Ces contraintes sont déjà intégrées dans les scripts de veille et le script Playwright. Pour les postulations via Claude Code, inclure explicitement l’instruction : « Travaille de façon méthodique, lente et prudente en simulant un comportement humain. »
Analyse des logs de veille — Interprétation du cron output
Quand le cron de veille s’exécute, un futur agent reçoit le log complet. Voici comment interpréter correctement les métriques de sortie :
Métrique « alertes » dans le log
Le log veille_YYYYMMDD.log contient une ligne comme :
📊 Total alertes: 65 | Dernier ID: 17480
⚠️ Ce « Total alertes » est CUMULATIF — il compte toutes les lignes [ALERTE dans alertes_offres.txt, ce qui inclut :
| Préfixe | Source | Exemple |
|---|---|---|
[ALERTE] | Gouv. Québec (SQ/MSP/DPCP/BEI) | [ALERTE] Agent admin - SQ (ID 17345) |
[ALERTE-JB] | Job Bank Canada | [ALERTE-JB] Analyste criminel... (JB123) |
[ALERTE-GOOGLE] | Google Jobs | [ALERTE-GOOGLE] Conseiller... |
Le nombre 65 inclut les alertes de TOUTES les sources et TOUS les jours précédents. Il ne représente PAS les nouvelles offres de ce run uniquement.
Déterminer ce qui est NOUVEAU dans ce run
Le log ne montre PAS directement le nombre de nouvelles alertes. Faire la différence entre « nouveau » et « cumulatif » depuis le log seul est impossible sans état précédent.
Pattern fiable : comparer les compteurs avant/après
# Dans alertes_offres.txt, chaque alerte est une ligne
# Compter le nombre de lignes AVANT le run (via last_id_scanned.txt timestamp)
# Exemple : si avant le run il y avait 43 lignes et après il y en a 65
# → 22 nouvelles alertes (toutes sources confondues)
# Pour isoler les nouvelles par source :
grep -c '\[ALERTE\]' alertes_offres.txt # Total SQ (cumulatif)
grep -c '\[ALERTE-JB\]' alertes_offres.txt # Total Job Bank (cumulatif)
grep -c '\[ALERTE-GOOGLE\]' alertes_offres.txt # Total Google (cumulatif)
Alternative : utiliser le tracker de progression de la veille
Si le log montre plusieurs phases (e.g., scan SQ → scan Job Bank → scan Google),
chaque phase imprime son propre résumé : examiner les lignes ✅ Fin veille V2 et
✅ Fin veille Job Bank pour voir les totaux partiels. Cependant, même ces totaux
sont cumulatifs — c’est le fichier alertes_offres.txt qui est la source de
vérité pour les nouvelles détections.
Technique : détection des nouvelles offres SQ uniquement
Les offres SQ sont stockées par ID numérique dans alertes_offres.txt. Pour trouver
les nouvelles depuis le dernier run :
# Lister les IDs SQ les plus récents (dernières entrées)
grep '\[ALERTE\]' alertes_offres.txt | tail -20
# Chaque ligne contient l'ID de l'offre (ex: 17345)
# Les IDs les plus récents sont en bas du fichier
En ordre chronologique inversé : les nouvelles offres sont les dernières lignes
ajoutées à alertes_offres.txt pour leur source respective.
État de scan
last_id_scanned.txt est mis à jour à la fin du run SQ avec le dernier ID scanné.
Le fichier contient un seul nombre (ex: 17480). Ne pas confondre avec le nombre
d’alertes — c’est la plage d’IDs scannée, pas le nombre d’offres trouvées.
Piège fréquent (cron)
Quand le cron rapporte « 65 alertes » et que le rapport précédent disait « 43 alertes »,
la différence (22) n’est PAS seulement les offres SQ — elle inclut les offres Job Bank
et Google de TOUS les runs précédents. Toujours préciser dans le rapport :
« Nouvelles ce run : X | Cumulatif total : Y » en extrayant les nouvelles du fichier
alertes_offres.txt plutôt que du log.
Postulation (login requise)
La recherche d’offres via l’API REST est publique (sans auth). La postulation nécessite une session authentifiée sur le portail.
⚠️ Nouvelles contraintes du portail (2026)
Le nouveau portail emplois.carrieres.gouv.qc.ca (v.20260515) a changé :
- Anti-bot fort : Headless Playwright reçoit un
403 Forbidden. Seul Camoufox/Camofox (anti-détection Firefox) fonctionne — pas decurl, pas de Playwright headless. - CAPTCHA Altcha (code image) : Le formulaire de connexion utilise
Altcha.org avec un défi image (code texte dans
une image JPEG 180×50px) en plus du défi audio. Le champ
codeChallenge.imagecontient une base64 JPEG du code à lire. L’attributauto="off"empêche la résolution automatique du PoW. CapSolver (AntiTurnstileTaskProxyLess) ne fonctionne pas — ce n’est pas un Turnstile standard. Résolution humaine obligatoire. - Première connexion : Les comptes de l’ancienne plateforme (Recrutement en
ligne / Emplois en ligne) doivent utiliser “Mot de passe oublié” pour
initialiser leur mot de passe sur le nouveau portail avant la première
connexion. L’alerte est affichée en haut de
/plateforme-emploi/connexion.
Prérequis : Accès Gmail pour 2FA
Avant toute postulation, le système de réinitialisation de mot de passe
et les confirmations de candidature passent par evinafeu@gmail.com.
Le mot de passe du portail ne fonctionne PAS pour Gmail IMAP.
Solution : App Password Google (16 caractères). Voir
references/gmail-imap-2fa-lecture.md pour la procédure complète.
Sans App Password Gmail configuré, les étapes suivantes sont bloquées :
- Lire l’email de réinitialisation (« Mot de passe oublié »)
- Lire les codes 2FA envoyés par le portail
- Confirmer la réception des candidatures soumises
Workflow de postulation (via Camofox)
⚠️ Pitfall : préférer l’API Camofox directe aux browser_* Hermes.
Les outils browser_navigate, browser_click, browser_type d’Hermes
peuvent timeout sur Camofox. Utiliser l’API REST Camofox directement
(curl/Python) avec les bons sélecteurs — voir references/portail-altcha-captcha.md.
# 1. Démarrer Camofox (si pas déjà fait)
# sudo systemctl start camofox.service
# 2. Health check
# curl -s http://localhost:9377/health
# 3. Créer un onglet (body: {"userId":"bf","sessionKey":"genspark"})
# POST /tabs → donne tabId (attend ~5-22s le temps que le navigateur démarre)
# 4. Naviguer vers connexion
# POST /tabs/{tabId}/navigate body: {"userId":"bf","url":"..."}
# 5. Accepter cookies via JS
# POST /tabs/{tabId}/evaluate
# body: {"userId":"bf","expression":"document.querySelector('.cookie-btn')?.click()"}
# 6. Remplir email (name="username") + mot de passe (name="password") via JS
# POST /tabs/{tabId}/evaluate
# 7. CAPTCHA Altcha → humain lit le code de l'image
# Voir references/altcha-captcha-portail.md
# 8. Soumettre via JS
# POST /tabs/{tabId}/evaluate
# body: {"userId":"bf","expression":"document.querySelector('form')?.requestSubmit()"}
# 9. Naviguer vers l'offre et postuler
# POST /tabs/{tabId}/navigate → puis evaluate pour uploader le CV
Voir references/altcha-captcha-portail.md pour les détails de l’interaction
Altcha.
Alternative : ALTCHA via evaluate JS (efficace + rapide)
Quand browser_click timeout sur le bouton “Se connecter” (transition
de page bloquante), utiliser l’API Camofox /evaluate avec JavaScript
pour remplir les champs ET soumettre en une étape.
⚠️ Sélecteurs critiques : Le champ email utilise name="username"
(pas type="email"). Les IDs React sont dynamiques (:r2:, :r3:) —
toujours utiliser l’attribut name pour les sélecteurs JS.
Pattern JS :
// 1. Remplir les champs via leurs attributs name (fiables)
document.querySelector('input[name="username"]').value = 'evinafeu@gmail.com';
document.querySelector('input[name="password"]').value = '$SQ_PORTAL_PASSWORD';
// 2. Vérifier l'état ALTCHA
const widget = document.querySelector('altcha-widget');
if (widget) {
console.log('state:', widget.getAttribute('state'));
}`
```javascript
// 1. Vérifier l'état ALTCHA
const widget = document.querySelector('altcha-widget');
if (widget) {
console.log('state:', widget.getAttribute('state'));
// unverified → verifying → verified
}
// 2. Si state="verified", soumettre directement
document.querySelector('form')?.requestSubmit();
// 3. Sinon, cocher dans le shadow DOM puis soumettre après PoW
if (widget && widget.shadowRoot) {
widget.shadowRoot.querySelector('input[type="checkbox"]')?.click();
}
// Attendre ~3s que le PoW se résolve, puis requestSubmit()
form.requestSubmit() évite le timeout de browser_click sur les boutons qui déclenchent une transition de page. Voir references/altcha-captcha-portail.md.
CapSolver — Résolution automatisée Altcha
CapSolver permet de résoudre les CAPTCHAs Altcha du portail
emplois.carrieres.gouv.qc.ca de façon automatisée.
⚠️ Note (2026-06-04) : Ce Altcha n’est PAS un Turnstile standard.
Le type AntiTurnstileTaskProxyLess retourne une erreur 400.
Le portail utilise un challenge Altcha custom avec codeChallenge
(image + audio), pas un défi Turnstile. Aucune solution CapSolver
connue pour ce type de défi au 2026-06-04. La résolution humaine
(image ou audio) reste la seule option fiable.
Configuration
- Obtenir une clé API — depuis https://dashboard.capsolver.com
(format :
CAP-...— 64+ caractères hex) - Définir la variable d’environnement dans
~/.hermes/.envou le profil Hermes :
CAPSOLVER_API_KEY=CAP-xxxxx-xxxxx-xxxxx-xxxxx
- Vérifier le solde après configuration — appeler l’API pour confirmer que la clé est valide et que le crédit est suffisant :
source ~/.hermes/.env
curl -s -X POST https://api.capsolver.com/getBalance \
-H "Content-Type: application/json" \
-d "{\"clientKey\":\"$CAPSOLVER_API_KEY\"}"
# Retour attendu : {"balance":6,"errorId":0}
# errorId=0 = clé valide ; balance = $ USD restant
⚠️ La clé CAPSOLVER_API_KEY doit impérativement être dans .env
(lu par le processus Hermes), pas seulement dans la config.yaml. Vérifier
avec grep CAPSOLVER ~/.hermes/.env.
Utilisation (expérimentale)
Ce Altcha utilise un type custom avec codeChallenge (image+audio),
pas un Turnstile standard. Le type AntiTurnstileTaskProxyLess
retourne une erreur 400 — aucune solution CapSolver connue à date.
Si une nouvelle solution CapSolver apparaît, le pattern d’appel serait :
import requests
payload = {
"clientKey": "CAP-xxxxx-xxxxx-xxxxx-xxxxx",
"task": {
"type": "AntiTurnstileTaskProxyLess", # À vérifier
"websiteURL": "https://emplois.carrieres.gouv.qc.ca/plateforme-emploi/connexion",
"websiteKey": "<site_key_altcha>",
"metadata": {"action": "login"}
}
}
Note : Le type de tâche exact dépend de la version d’Altcha déployée. Tester d’abord avec le dashboard CapSolver pour confirmer si un nouveau type supporte ce défi custom. En attendant, résolution humaine obligatoire.
Budget
- ~0.002 $/résolution pour Altcha (PoW simple)
- ~0.01 $/résolution pour audio challenge
- 6 $ USD de crédit ≈ 600–3000 résolutions selon le type de défi
Pitfalls (postulation)
⏱ Tab reaper : Camofox ferme les sessions vides en ~2s
La création d’un onglet prend ~22s à cause de la restauration du storage
state (cookies persistants). Pendant ce temps, le tab reaper de Camofox
(scanne toutes les ~2s) ferme les sessions sans onglet actif.
Solution : Créer l’onglet via POST /tabs AVEC une url dans le body
pour que la navigation commence immédiatement.
# ✓ CORRECT : créer l'onglet avec URL directement
curl -s -X POST http://localhost:9377/tabs \
-H 'Content-Type: application/json' \
-d '{"userId":"bf","sessionKey":"genspark","url":"https://emplois.carrieres.gouv.qc.ca/plateforme-emploi/connexion"}'
# ✗ ÉCHEC : créer sans URL puis naviguer → reaper ferme avant navigate
curl -s -X POST http://localhost:9377/tabs \
-H 'Content-Type: application/json' \
-d '{"userId":"bf","sessionKey":"genspark"}' # → timeout ~30s
🔑 userId requis sur TOUS les endpoints
Les endpoints /tabs/:tabId/navigate, /click, /evaluate, /snapshot
nécessitent userId dans le body/query même si la session est déjà ouverte.
Sans userId, Camofox retourne {\"error\":\"userId required\"}.
🔍 Input name vs React dynamic IDs
Le portail utilise React avec des IDs auto-générés (:r2:, :r3:) qui
changent entre les sessions. Toujours utiliser l’attribut name :
- Champ email :
input[name=\"username\"] - Champ password :
input[name=\"password\"]
Ces sélecteurs sont stables d’une session à l’autre.
- Ne pas exposer la clé API dans les logs ou la sortie terminal
- CapSolver nécessite une connexion Internet et un proxy stable si le portail bloque les IPs VPS
- Tester d’abord manuellement via le dashboard CapSolver (Create Task → Altcha) avant d’intégrer dans le script de postulation
- Le script
postuler_sq.pypeut être modifié pour appeler CapSolver en amont de la soumission du formulaire de connexion
Guichet-Emplois Canada — jobview a changé
L’endpoint https://www.guichetemplois.gc.ca/jobsearch/jobview/{ID} ne répond
plus directement via curl — retourne 404 ou une page vide sans structure
HTML exploitable. Les détails d’offre fédérale (titre, organisation,
localisation, salaire) ne sont plus extractibles par simple GET.
Cause probable : le site requiert désormais une session de navigation
(recherche préalable) maintenue via cookie/token. L’URL /jobview/{ID} ne
fonctionne que si l’ID provient d’une recherche récente dans la même session.
Workaround :
- Utiliser la page de recherche
?search=true&keywords=...qui liste encore les offres dans un tableau HTML avecdata-jobidsur les<a>tags - Parser la liste pour extraire titre + ID plutôt que de visiter la fiche
- Pour les détails complets, utiliser
browser_navigate()(pas curl) etbrowser_snapshot()oubrowser_console(expression=...)
API ManitouLS — section[] au lieu de champs plats
L’API /search/posting/{ID}/fr a migré d’une structure à champs plats
(data.titre, data.divisionNom, etc.) vers un tableau data.section[]
avec des paires {id, content}. Les anciens champs plats sont désormais
vides. Toujours parser via data.section[] (voir section Champs clés
ci-dessus).
Migration nécessaire : Les scripts V1 qui accédaient à data.titre
directement retournent des chaînes vides. Remplacer par l’indexation
depuis le tableau section.
Postulation via script Playwright autonome + SSH
Quand Camofox n’est pas disponible sur le VPS, générer un script Playwright
autonome par offre, le transférer sur mx (scp) et l’exécuter via SSH.
⚠️ Discipline tracker : Après avoir créé la lettre de présentation pour
une offre, toujours mettre à jour candidature_tracker.md IMMÉDIATEMENT
— ne pas attendre la soumission. Les offres avec lettre prête doivent
impérativement avoir leur ligne dans le tracker.
Voir references/postulation-standalone-script.md pour le workflow complet.
ℹ️ Complément : Agent Emploi V2 (mx — Playwright multi-sites)
Le script search_jobs_v2.py (mx, /home/bf/Bureau/search_jobs_v2.py)
complète la veille API ManitouLS avec une approche différente :
- Sites : Indeed, Jobillico, LinkedIn, Emplois Québec (Guichet-Emplois)
- Méthode : Playwright + stealth_sync + Chrome CDP (connect_over_cdp)
- Scoring : mots-clés SQ + criminologie + IA/analyste → score composite
- Wrapper :
run_job_agent_v2.sh(cron @ 8h) - Dashboard : compteurs temps réel sur
/home/bf/knowledge-share/dashboard.md - Réf. complète :
knowledge-share/innovations/mx/agent-emploi-v2.md
Différence clé avec la veille API : Ce script utilise le navigateur réel (Chrome CDP + Playwright) pour les sites sans API publique (Indeed, LinkedIn). L’API ManitouLS reste la source la plus fiable pour les offres gouvernementales SQ — les deux approches sont complémentaires.
Lettres de présentation (bonne pratique)
Pour chaque offre SQ, préparer une lettre de présentation personnalisée qui met en avant la double compétence du candidat :
- Formation criminologique (Collège Ahuntsic) — enquêtes, OSINT, rapports
- Expertise IA et analyse de données (Collège Bois-de-Boulogne)
- 21 ans Marine Nationale — discipline, 198 missions, gestion d’incidents
- Certification CCNA — compréhension des infrastructures réseau
Structure d’une lettre type :
- Objet : Candidature — [Titre du poste]
- Premier paragraphe : formation et compétences spécifiques au poste
- Deuxième paragraphe : expérience militaire et opérationnelle
- Compétences techniques (CCNA, Python, IA, etc.)
- Coordonnées et disponibilité
Workflow complet de postulation SQ
graph TD
A[Recherche offres via API ManitouLS] --> B[Préparer lettres personnalisées ~/cv_versions/]
B --> C[Lancer Camofox]
C --> D[Connexion portail + CAPTCHA Altcha]
D --> E{1re connexion?}
E -->|Oui| F[Utiliser Mot de passe oublié]
E -->|Non| G[Email + MDP + CAPTCHA]
F --> G
G --> H[Naviguer offre → Postuler → Upload CV]
H --> I[Confirmer candidature]