job2apply 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- job2apply-1.0.0/.gitattributes +4 -0
- job2apply-1.0.0/.gitignore +12 -0
- job2apply-1.0.0/.gitlab-ci.yml +28 -0
- job2apply-1.0.0/LICENSE +21 -0
- job2apply-1.0.0/PKG-INFO +382 -0
- job2apply-1.0.0/README.md +350 -0
- job2apply-1.0.0/cv/.gitkeep +0 -0
- job2apply-1.0.0/pyproject.toml +92 -0
- job2apply-1.0.0/src/job2apply/__init__.py +0 -0
- job2apply-1.0.0/src/job2apply/adzuna.py +110 -0
- job2apply-1.0.0/src/job2apply/cli.py +292 -0
- job2apply-1.0.0/src/job2apply/config.py +169 -0
- job2apply-1.0.0/src/job2apply/france_travail.py +116 -0
- job2apply-1.0.0/src/job2apply/google_jobs.py +369 -0
- job2apply-1.0.0/src/job2apply/installation.py +85 -0
- job2apply-1.0.0/src/job2apply/journal.py +147 -0
- job2apply-1.0.0/src/job2apply/linkedin.py +419 -0
- job2apply-1.0.0/src/job2apply/matching.py +59 -0
- job2apply-1.0.0/src/job2apply/modeles/env +10 -0
- job2apply-1.0.0/src/job2apply/modeles/profile.yaml +113 -0
- job2apply-1.0.0/src/job2apply/modeles/veille-emploi.md +86 -0
- job2apply-1.0.0/src/job2apply/sources.py +56 -0
- job2apply-1.0.0/src/job2apply/store.py +64 -0
- job2apply-1.0.0/tests/conftest.py +89 -0
- job2apply-1.0.0/tests/test_adzuna.py +85 -0
- job2apply-1.0.0/tests/test_cli.py +49 -0
- job2apply-1.0.0/tests/test_espace.py +76 -0
- job2apply-1.0.0/tests/test_france_travail.py +104 -0
- job2apply-1.0.0/tests/test_google_jobs.py +173 -0
- job2apply-1.0.0/tests/test_google_jobs_reseau.py +105 -0
- job2apply-1.0.0/tests/test_installation.py +68 -0
- job2apply-1.0.0/tests/test_journal.py +109 -0
- job2apply-1.0.0/tests/test_linkedin.py +413 -0
- job2apply-1.0.0/tests/test_linkedin_reseau.py +146 -0
- job2apply-1.0.0/tests/test_matching.py +75 -0
- job2apply-1.0.0/tests/test_sources.py +70 -0
- job2apply-1.0.0/tests/test_store.py +60 -0
- job2apply-1.0.0/tests/test_villes.py +43 -0
- job2apply-1.0.0/uv.lock +630 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
.env
|
|
2
|
+
.venv/
|
|
3
|
+
config/profile.yaml
|
|
4
|
+
# La commande Claude Code est versionnee dans src/cv_auto/modeles/ ; ce qu'installe
|
|
5
|
+
# `cv-auto install-command --projet` dans le depot n'en est qu'une copie.
|
|
6
|
+
.claude/commands/
|
|
7
|
+
__pycache__/
|
|
8
|
+
*.pyc
|
|
9
|
+
data/
|
|
10
|
+
candidatures/
|
|
11
|
+
cv/*.pdf
|
|
12
|
+
cv/*.docx
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
stages: [test, deploy]
|
|
2
|
+
|
|
3
|
+
variables:
|
|
4
|
+
UV_LINK_MODE: copy
|
|
5
|
+
|
|
6
|
+
verifier:
|
|
7
|
+
stage: test
|
|
8
|
+
image: ghcr.io/astral-sh/uv:python3.12-bookworm-slim
|
|
9
|
+
script:
|
|
10
|
+
# --extra google-jobs : sans lui, ty ne resout pas l'import playwright de
|
|
11
|
+
# google_jobs.py, qui n'est installe que via cet extra.
|
|
12
|
+
- uv sync --extra google-jobs
|
|
13
|
+
- uv run ruff format --check .
|
|
14
|
+
- uv run ruff check .
|
|
15
|
+
- uv run ty check
|
|
16
|
+
- uv run pytest
|
|
17
|
+
|
|
18
|
+
publier:
|
|
19
|
+
stage: deploy
|
|
20
|
+
image: ghcr.io/astral-sh/uv:python3.12-bookworm-slim
|
|
21
|
+
rules:
|
|
22
|
+
- if: $CI_COMMIT_TAG =~ /^v/
|
|
23
|
+
id_tokens:
|
|
24
|
+
PYPI_ID_TOKEN:
|
|
25
|
+
aud: pypi
|
|
26
|
+
script:
|
|
27
|
+
- uv build --no-sources
|
|
28
|
+
- uvx twine upload dist/*
|
job2apply-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NyxAether
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
job2apply-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: job2apply
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Veille d'offres d'emploi et preparation de candidatures adaptees
|
|
5
|
+
Project-URL: Repository, https://gitlab.com/NyxHemera/job2apply
|
|
6
|
+
Project-URL: Issues, https://gitlab.com/NyxHemera/job2apply/-/issues
|
|
7
|
+
Author-email: NyxAether <contact.nyxhemera@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: candidature,cli,emploi,france-travail,offres,veille
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Natural Language :: French
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Office/Business
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Requires-Dist: beautifulsoup4>=4.12
|
|
24
|
+
Requires-Dist: cyclopts>=4.25.2
|
|
25
|
+
Requires-Dist: python-dotenv>=1.0
|
|
26
|
+
Requires-Dist: pyyaml>=6.0
|
|
27
|
+
Requires-Dist: requests>=2.32
|
|
28
|
+
Requires-Dist: rich>=15.0.0
|
|
29
|
+
Provides-Extra: google-jobs
|
|
30
|
+
Requires-Dist: playwright>=1.44; extra == 'google-jobs'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# job2apply
|
|
34
|
+
|
|
35
|
+
Veille quotidienne d'offres d'emploi, tri selon un profil, et preparation de
|
|
36
|
+
candidatures adaptees (lettre de motivation ciblee + CV) soumises a validation avant
|
|
37
|
+
tout envoi.
|
|
38
|
+
|
|
39
|
+
## Fonctionnement
|
|
40
|
+
|
|
41
|
+
1. `job2apply fetch` interroge l'API France Travail et les sources d'appoint activees
|
|
42
|
+
(LinkedIn, Adzuna, Google Jobs), deduplique via `data/seen.json`, applique un
|
|
43
|
+
pre-filtre par mots-cles, et ecrit `data/shortlist.json`.
|
|
44
|
+
2. La commande Claude Code `/veille-emploi` reprend cette shortlist, fait le vrai tri
|
|
45
|
+
de fond, redige une lettre par offre dans `candidatures/`, puis demande la
|
|
46
|
+
validation offre par offre.
|
|
47
|
+
3. L'envoi reste manuel pour l'instant (pas d'auth email configuree).
|
|
48
|
+
|
|
49
|
+
Tous ces chemins sont relatifs a un *espace de travail* : un dossier par recherche
|
|
50
|
+
d'emploi, distinct du code (voir plus bas).
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
uv tool install job2apply # la commande job2apply, disponible partout
|
|
56
|
+
job2apply install-command # la commande Claude Code /veille-emploi
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`job2apply install-command` copie `/veille-emploi` dans `~/.claude/commands/`, ou Claude
|
|
60
|
+
Code la trouve depuis n'importe quel dossier. Une commande deja presente et modifiee
|
|
61
|
+
n'est pas ecrasee sans `--forcer`, et `--projet` l'installe dans l'espace de travail
|
|
62
|
+
courant plutot que chez l'utilisateur.
|
|
63
|
+
|
|
64
|
+
`uv tool install --with-executables-from playwright "job2apply[google-jobs]"` ajoute la
|
|
65
|
+
source Google Jobs, qui pilote un navigateur via Playwright ; sans cet extra, `job2apply
|
|
66
|
+
fetch` la saute simplement. Il faut ensuite lancer une fois `playwright install
|
|
67
|
+
chromium`.
|
|
68
|
+
|
|
69
|
+
### Depuis les sources
|
|
70
|
+
|
|
71
|
+
Pour developper sur le depot lui-meme :
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
git clone <url-du-depot> job2apply
|
|
75
|
+
uv tool install -e ./job2apply # garde le lien avec les sources, commande disponible partout
|
|
76
|
+
uv sync # ou : travailler avec `uv run job2apply`, sans rien installer globalement
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Puis un espace de travail :
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
mkdir ~/recherche-data-engineer && cd ~/recherche-data-engineer
|
|
83
|
+
job2apply init
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`init` y depose `config/profile.yaml` et `.env` a remplir, et les dossiers `data/`,
|
|
87
|
+
`candidatures/` et `cv/`. Relance sur un espace existant, il ne remplace rien.
|
|
88
|
+
|
|
89
|
+
Identifiants France Travail : creer un compte gratuit sur https://francetravail.io,
|
|
90
|
+
puis une application abonnee a l'API « Offres d'emploi v2 ». Le `client_id` et le
|
|
91
|
+
`client_secret` vont dans le `.env` de l'espace (jamais commite).
|
|
92
|
+
|
|
93
|
+
## Espaces de travail
|
|
94
|
+
|
|
95
|
+
Un espace de travail est un dossier contenant `config/profile.yaml` — c'est tout ce qui
|
|
96
|
+
le definit. Il porte le profil, les identifiants d'API, la memoire des offres deja vues
|
|
97
|
+
(`data/seen.json`), le CV et les candidatures preparees. Deux recherches d'emploi
|
|
98
|
+
menees en parallele, ou deux personnes sur la meme machine, vivent dans deux espaces et
|
|
99
|
+
ne se marchent pas dessus.
|
|
100
|
+
|
|
101
|
+
L'outil determine a quel espace il s'applique, dans cet ordre :
|
|
102
|
+
|
|
103
|
+
1. l'option `--espace <chemin>` ;
|
|
104
|
+
2. la variable d'environnement `JOB2APPLY_HOME` ;
|
|
105
|
+
3. le premier dossier parent, en partant du dossier courant, qui contient
|
|
106
|
+
`config/profile.yaml` — donc lancer la commande depuis un sous-dossier de l'espace
|
|
107
|
+
fonctionne ;
|
|
108
|
+
4. a defaut, le dossier courant.
|
|
109
|
+
|
|
110
|
+
Le depot lui-meme peut servir d'espace : `job2apply init` a sa racine, et le profil ainsi
|
|
111
|
+
que `data/` y sont deja ignores par git.
|
|
112
|
+
|
|
113
|
+
L'option globale `--verbeux` (comme `--espace`, avant la sous-commande) descend le
|
|
114
|
+
journal en DEBUG, y compris sur la console — voir [Journal](#journal).
|
|
115
|
+
|
|
116
|
+
## Configuration
|
|
117
|
+
|
|
118
|
+
Tout le filtrage et la redaction s'appuient sur `config/profile.yaml` : mots-cles de
|
|
119
|
+
recherche, localisation, types de contrat, termes bonus/eliminatoires, points forts
|
|
120
|
+
utilises dans les lettres. Le CV de base va dans `cv/`.
|
|
121
|
+
|
|
122
|
+
Aucune donnee personnelle n'est codee en dur dans le code : changer de profil (autre
|
|
123
|
+
metier, autre region, autre candidat) se fait entierement dans ce fichier. Les modeles
|
|
124
|
+
copies par `job2apply init` — profil, `.env` et commande Claude Code — vivent dans le
|
|
125
|
+
paquet, sous `src/job2apply/modeles/`, pour rester disponibles une fois l'outil installe
|
|
126
|
+
loin du depot.
|
|
127
|
+
|
|
128
|
+
### Plusieurs villes
|
|
129
|
+
|
|
130
|
+
`recherche.villes` accepte une liste : chaque entree porte `nom` (la localite en clair,
|
|
131
|
+
pour Adzuna, LinkedIn et Google Jobs) et `commune` (son code INSEE, pour France Travail).
|
|
132
|
+
|
|
133
|
+
```yaml
|
|
134
|
+
recherche:
|
|
135
|
+
villes:
|
|
136
|
+
- nom: "Nantes"
|
|
137
|
+
commune: "44109"
|
|
138
|
+
- nom: "Paris"
|
|
139
|
+
commune: "75056"
|
|
140
|
+
distance_km: 10
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Toutes les sources interrogent chaque ville, une requete par ville et par mot-cle. Une
|
|
144
|
+
offre remontee par plusieurs villes n'est conservee qu'une fois, attribuee a la premiere
|
|
145
|
+
ville de la liste qui l'a trouvee — c'est ce que lit son champ `ville_recherche`, affiche
|
|
146
|
+
dans une colonne supplementaire de la table des qu'au moins deux villes sont
|
|
147
|
+
configurees. `distance_km`, `pays`, `departements`, `types_contrat` et `publiee_depuis`
|
|
148
|
+
restent des reglages globaux, communs a toutes les villes.
|
|
149
|
+
|
|
150
|
+
L'ancienne ecriture a une seule ville (`commune:` et `lieu:` scalaires) reste acceptee
|
|
151
|
+
et vaut une ville unique.
|
|
152
|
+
|
|
153
|
+
Multiplier les villes multiplie le nombre de requetes, LinkedIn en premier lieu : voir
|
|
154
|
+
« Le nombre de requetes est la ressource rare » plus bas.
|
|
155
|
+
|
|
156
|
+
## Adzuna (source d'appoint)
|
|
157
|
+
|
|
158
|
+
Adzuna est un agregateur d'offres avec une API publique et une inscription libre sur
|
|
159
|
+
https://developer.adzuna.com : l'`app_id` et l'`app_key` sont delivres immediatement et
|
|
160
|
+
vont dans `.env`. La source est optionnelle — sans identifiants, `job2apply fetch` la
|
|
161
|
+
saute sans rien signaler.
|
|
162
|
+
|
|
163
|
+
Elle se pilote par les memes cles de `config/profile.yaml` que France Travail :
|
|
164
|
+
`mots_cles`, `villes`, `distance_km`, `types_contrat`, `publiee_depuis`. Une cle lui est
|
|
165
|
+
propre, car Adzuna ne connait pas le code INSEE : `pays` (`fr` par defaut). Chaque ville
|
|
166
|
+
lui est passee par son `nom` en clair (par defaut `identite.ville`), pas son `commune`.
|
|
167
|
+
|
|
168
|
+
Adzuna ne publie pas d'email de contact : la candidature passe par l'URL de l'annonce.
|
|
169
|
+
|
|
170
|
+
## LinkedIn (source d'appoint)
|
|
171
|
+
|
|
172
|
+
LinkedIn n'ouvre plus son API d'offres aux particuliers, mais sa recherche reste
|
|
173
|
+
consultable sans compte : c'est l'espace « invite », que la source lit directement en
|
|
174
|
+
HTTP. Ni identifiants ni navigateur — contrairement a Google Jobs, ces pages sont
|
|
175
|
+
servies sans JavaScript — mais c'est du scraping tout de meme : le `robots.txt` de
|
|
176
|
+
LinkedIn l'interdit explicitement (voir plus bas), d'ou l'activation explicite.
|
|
177
|
+
|
|
178
|
+
Elle est donc opt-in, dans `config/profile.yaml` :
|
|
179
|
+
|
|
180
|
+
```yaml
|
|
181
|
+
linkedin:
|
|
182
|
+
actif: true
|
|
183
|
+
max_offres: 50 # par mot-cle ; jusqu'a 60, une seule requete suffit
|
|
184
|
+
lire_descriptions: true
|
|
185
|
+
delai_s: 1.0
|
|
186
|
+
langue: "fr-FR"
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Elle reprend `mots_cles`, `villes`, `distance_km` et `publiee_depuis` de la section
|
|
190
|
+
`recherche`. `types_contrat` ne lui est pas transmis : le filtre de LinkedIn porte sur
|
|
191
|
+
le rythme de travail (temps plein, stage...) et non sur le CDI / CDD francais, et
|
|
192
|
+
l'appliquer ecarterait des offres valables. Le champ `contrat` reprend donc ce rythme
|
|
193
|
+
tel quel, sauf pour les quelques cas qui se recoupent (stage, alternance, interim).
|
|
194
|
+
|
|
195
|
+
### Le nombre de requetes est la ressource rare
|
|
196
|
+
|
|
197
|
+
LinkedIn limite les visiteurs sur deux plans a la fois, mesures sur des fiches reelles :
|
|
198
|
+
|
|
199
|
+
| Delai entre requetes | Cadence | Resultat |
|
|
200
|
+
| --- | --- | --- |
|
|
201
|
+
| 0.5 s | ~86 req/min | bloque des la **12e requete** |
|
|
202
|
+
| 1.0 s | ~48 req/min | une veille entiere passe |
|
|
203
|
+
| 2.0 s | ~27 req/min | 150 requetes sans incident |
|
|
204
|
+
|
|
205
|
+
Le blocage (`429`) se leve en une vingtaine de secondes, mais le volume compte aussi :
|
|
206
|
+
apres environ 280 requetes en un quart d'heure, meme 1 s ne passe plus. Aucun delai ne
|
|
207
|
+
rend donc une veille trop bavarde sure — c'est le nombre de requetes qu'il faut tenir
|
|
208
|
+
bas. Deux mecanismes s'en chargent :
|
|
209
|
+
|
|
210
|
+
- **La liste passe par la page de resultats**, qui rend une soixantaine d'offres en une
|
|
211
|
+
requete, la ou le fragment de defilement en donne dix. Le fragment ne sert plus qu'a
|
|
212
|
+
depasser cette soixantaine, ou a prendre le relais si la page cesse d'etre lisible.
|
|
213
|
+
- **Les fiches des offres deja vues ne sont pas relues.** Elles seraient de toute facon
|
|
214
|
+
ecartees par `data/seen.json` juste apres : leur description couterait une requete
|
|
215
|
+
pour un resultat jete.
|
|
216
|
+
|
|
217
|
+
Sur un profil a cinq mots-cles, cela donne **85 requetes le premier jour** (77 offres,
|
|
218
|
+
environ deux minutes) puis **8 requetes les jours suivants** (une vingtaine de
|
|
219
|
+
secondes), la ou lire chaque liste par dix et relire chaque fiche en demandait pres de
|
|
220
|
+
trois cents. En cas de blocage malgre tout, la source patiente puis reessaie ; si les
|
|
221
|
+
fiches restent refusees, elle finit la recolte sans descriptif plutot que de s'arreter
|
|
222
|
+
(les offres remontent alors en revue manuelle) et le signale sur stderr.
|
|
223
|
+
|
|
224
|
+
Chaque offre inconnue est ouverte pour en lire le descriptif complet, ce qui permet au
|
|
225
|
+
scoring de trancher. `lire_descriptions: false` supprime ces requetes, au prix du tri
|
|
226
|
+
automatique. Les cartes de resultat, elles, ne portent aucun extrait de description :
|
|
227
|
+
la fiche est le seul moyen d'obtenir le texte de l'annonce.
|
|
228
|
+
|
|
229
|
+
Comme Google Jobs, c'est du scraping : aucune CGU n'a ete signee faute de compte, mais
|
|
230
|
+
le `robots.txt` de LinkedIn interdit explicitement `/jobs-guest/`, son bloc
|
|
231
|
+
`User-agent: *` interdit le site entier, et ses conditions proscrivent l'acces
|
|
232
|
+
automatise. Le risque pratique se limite a un blocage temporaire par adresse IP.
|
|
233
|
+
La source depend aussi de la mise en page de LinkedIn : `uv run pytest -m reseau` le
|
|
234
|
+
detecte, et les selecteurs a mettre a jour sont en tete de `src/job2apply/linkedin.py`.
|
|
235
|
+
|
|
236
|
+
LinkedIn ne publie pas d'email de contact : la candidature part de la page de l'offre.
|
|
237
|
+
|
|
238
|
+
## Google Jobs (source d'appoint)
|
|
239
|
+
|
|
240
|
+
L'onglet « Emplois » de la recherche Google agrege les offres de la plupart des
|
|
241
|
+
jobboards francais, sans compte ni cle d'API. Il n'existe en revanche aucune API pour
|
|
242
|
+
l'interroger — la Cloud Talent Solution sert aux employeurs qui indexent leurs propres
|
|
243
|
+
postes — donc la source lit la page de resultats avec un vrai navigateur, pilote par
|
|
244
|
+
Playwright. Deux consequences a connaitre avant de l'activer :
|
|
245
|
+
|
|
246
|
+
- **Une fenetre de navigateur s'ouvre.** Google exige JavaScript depuis 2025, et
|
|
247
|
+
reconnait un navigateur headless : en mode invisible, il sert son CAPTCHA des la
|
|
248
|
+
premiere requete. Le profil navigateur est conserve dans `data/google-profile/` pour
|
|
249
|
+
ne repondre qu'une fois a la banniere de consentement.
|
|
250
|
+
- **C'est du scraping, contrairement aux autres sources.** L'absence de compte veut dire
|
|
251
|
+
qu'aucune CGU n'a ete signee, mais le `robots.txt` de Google interdit `/search` et
|
|
252
|
+
ses conditions d'utilisation proscrivent l'acces automatise aux resultats. Le risque
|
|
253
|
+
pratique se limite a un blocage temporaire par CAPTCHA, d'ou l'activation explicite.
|
|
254
|
+
|
|
255
|
+
Elle est donc opt-in, dans `config/profile.yaml` :
|
|
256
|
+
|
|
257
|
+
```yaml
|
|
258
|
+
google_jobs:
|
|
259
|
+
actif: true
|
|
260
|
+
max_offres: 30
|
|
261
|
+
headless: false
|
|
262
|
+
langue: "fr"
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Installation : voir la section [Installation](#installation) plus haut (`--with-executables-from
|
|
266
|
+
playwright`), ou `uv sync --extra google-jobs` dans le depot, puis `playwright install
|
|
267
|
+
chromium` une fois. Sans Playwright, ou avec `actif: false`, `job2apply fetch` saute
|
|
268
|
+
simplement la source.
|
|
269
|
+
|
|
270
|
+
Elle reprend les cles `mots_cles`, `villes`, `pays` et `publiee_depuis` de la section
|
|
271
|
+
`recherche`. Chaque offre est ouverte pour en lire le descriptif complet, ce qui permet
|
|
272
|
+
au scoring de trancher comme sur les autres sources ; le champ `via` retient le
|
|
273
|
+
jobboard d'origine, et `url_postulation` pointe directement vers lui. Google ne decrit
|
|
274
|
+
pas le contrat en CDI / CDD mais en rythme de travail (« A plein temps »,
|
|
275
|
+
« Prestataire ») : le champ `contrat` reprend donc son libelle tel quel.
|
|
276
|
+
|
|
277
|
+
Cette source depend de la mise en page de Google, qui change sans preavis. Le test
|
|
278
|
+
`uv run pytest -m reseau` le detecte (voir « Tests »), et les selecteurs a mettre a
|
|
279
|
+
jour sont regroupes en tete de `src/job2apply/google_jobs.py`.
|
|
280
|
+
|
|
281
|
+
## Pourquoi pas Indeed
|
|
282
|
+
|
|
283
|
+
Indeed n'a plus d'API de recherche d'offres accessible. L'API Publisher a ete fermee en
|
|
284
|
+
2023, et ce qui reste est reserve aux partenaires valides :
|
|
285
|
+
|
|
286
|
+
- Le connecteur MCP officiel (`https://mcp.indeed.com/claude/mcp`) fonctionne dans
|
|
287
|
+
l'application Claude, mais refuse Claude Code : le flux OAuth aboutit, puis le serveur
|
|
288
|
+
repond `invalid_client` / « Client not allowed » car l'identite du CLI n'est pas sur
|
|
289
|
+
sa liste blanche.
|
|
290
|
+
- L'API GraphQL sous-jacente (`https://apis.indeed.com/graphql`) se comporte pareil.
|
|
291
|
+
N'importe qui peut y enregistrer un client OAuth et obtenir un jeton portant le scope
|
|
292
|
+
`job_seeker.jobs.search`, mais l'appel repond alors `403 Client is not authorized.`
|
|
293
|
+
L'enregistrement et le consentement sont ouverts, l'acces aux donnees ne l'est pas.
|
|
294
|
+
|
|
295
|
+
Les revendeurs tiers qui exposent des offres Indeed en JSON sont des scrapers, dont on
|
|
296
|
+
ne se sert pas. Si Indeed rouvre un jour, la source se rebranche via `job2apply ingest
|
|
297
|
+
--source indeed` sans toucher au reste du code.
|
|
298
|
+
|
|
299
|
+
## Ajouter une source
|
|
300
|
+
|
|
301
|
+
`job2apply ingest --source <nom>` lit une liste d'offres JSON sur stdin et les injecte
|
|
302
|
+
dans le pipeline. Les champs sont tolerants aux alias courants (`job_id`/`id`,
|
|
303
|
+
`title`/`titre`, `company_name`/`entreprise`, `apply_url`/`url`...), seuls un
|
|
304
|
+
identifiant et un titre sont requis. C'est le point d'entree pour toute nouvelle source
|
|
305
|
+
— connecteur MCP, export CSV converti, saisie manuelle — sans toucher au reste du code.
|
|
306
|
+
|
|
307
|
+
Une source interrogee a chaque veille merite en revanche son propre module, sur le
|
|
308
|
+
modele de `france_travail.py`, `adzuna.py`, `linkedin.py` et `google_jobs.py` : une
|
|
309
|
+
fonction `search()` qui lit `config/profile.yaml` (et recoit des `Credentials` si la
|
|
310
|
+
source en demande), une fonction `normalize()` vers le format commun, et un branchement dans la commande
|
|
311
|
+
`fetch` de `cli.py`.
|
|
312
|
+
|
|
313
|
+
## Usage
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
job2apply init [chemin] # cree un espace de travail
|
|
317
|
+
job2apply install-command # installe /veille-emploi dans Claude Code
|
|
318
|
+
job2apply fetch # veille complete, dans l'espace courant
|
|
319
|
+
job2apply --espace ~/autre-recherche fetch # veille d'un autre espace
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Puis, dans Claude Code lance depuis l'espace : `/veille-emploi`.
|
|
323
|
+
|
|
324
|
+
Dans le depot sans installation globale, prefixer par `uv run` : `uv run job2apply fetch`.
|
|
325
|
+
|
|
326
|
+
## Journal
|
|
327
|
+
|
|
328
|
+
Le tableau et les resumes affiches par `job2apply` ne changent pas ; en parallele, un
|
|
329
|
+
journal de diagnostic s'ecrit dans `data/job2apply.log` (rotation a 1 Mo, 3 archives) :
|
|
330
|
+
requetes envoyees a chaque source, limitations de debit de LinkedIn, tracebacks des
|
|
331
|
+
sources en echec. Les anomalies (niveau WARNING et au-dessus) sont aussi rappelees sur
|
|
332
|
+
la console.
|
|
333
|
+
|
|
334
|
+
`job2apply --verbeux <commande>` descend les deux canaux en DEBUG. La section `logs` de
|
|
335
|
+
`config/profile.yaml` permet un reglage plus fin (niveau du fichier, niveau de la
|
|
336
|
+
console, chemin du fichier, ou desactivation complete) ; voir les commentaires du
|
|
337
|
+
modele. `--verbeux` l'emporte toujours sur le profil.
|
|
338
|
+
|
|
339
|
+
## Tests
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
uv run pytest # suite rapide, hors reseau
|
|
343
|
+
uv run pytest -m reseau # interroge vraiment LinkedIn (~30 s) et Google Jobs
|
|
344
|
+
# (ouvre une fenetre, ~15 s)
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
La suite par defaut ne touche a rien d'exterieur et couvre le tri, la deduplication et
|
|
348
|
+
la lecture de chaque source. Les tests marques `reseau` sont a relancer regulierement :
|
|
349
|
+
ils sont le seul filet contre un changement de mise en page chez LinkedIn ou Google, que
|
|
350
|
+
les tests de lecture pure ne peuvent pas voir. Leur echec n'implique pas toujours une
|
|
351
|
+
regression — Google peut opposer son CAPTCHA, LinkedIn limiter le debit — et les
|
|
352
|
+
messages d'assertion distinguent les deux cas.
|
|
353
|
+
|
|
354
|
+
## Verification
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
uv run ruff format . # formatage
|
|
358
|
+
uv run ruff check . # lint
|
|
359
|
+
uv run ty check # types
|
|
360
|
+
uv run pytest # tests
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Le lint reprend six familles de regles, declarees dans `pyproject.toml` : pycodestyle
|
|
364
|
+
(E), Pyflakes (F), pyupgrade (UP), flake8-bugbear (B), flake8-simplify (SIM) et isort
|
|
365
|
+
(I). Le formateur est celui de ruff, configure pour ecrire en LF afin de ne pas
|
|
366
|
+
reintroduire les CRLF que `.gitattributes` bannit. Le controle de types passe par `ty`,
|
|
367
|
+
encore jeune : ses diagnostics valent d'etre lus, pas forcement suivis a la lettre.
|
|
368
|
+
|
|
369
|
+
## Desinstallation
|
|
370
|
+
|
|
371
|
+
```bash
|
|
372
|
+
uv tool uninstall job2apply # l'executable et son environnement
|
|
373
|
+
rm ~/.claude/commands/veille-emploi.md # la commande Claude Code
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
L'installation ne pose que ces deux elements. En mode editable (`uv tool install -e`),
|
|
377
|
+
elle ne contient aucune copie du code : le depot reste intact et `uv run job2apply` y
|
|
378
|
+
fonctionne toujours.
|
|
379
|
+
|
|
380
|
+
Les espaces de travail survivent volontairement — ce sont des donnees, pas du logiciel :
|
|
381
|
+
profil, identifiants, memoire des offres vues et candidatures preparees. Les supprimer
|
|
382
|
+
est une decision separee, dossier par dossier.
|