Come estrarre gli annunci di Google Jobs con Python

Azienda, località e link per candidarsi da Google Jobs in JSON, e la forma di risposta che manda in crash il codice di parsing ingenuo su un normalissimo HTTP 200. Testato ad agosto 2026.

Vuoi gli annunci di lavoro per un ruolo e un luogo, nome azienda, località, un link per candidarsi, a una scala oltre il cliccare le card di Google una alla volta: una bacheca di nicchia, un feed di market intelligence, o uno script che traccia chi assume per un certo ruolo nelle settimane successive.

La richiesta

Una chiamata a /v1/jobs prende una query di ricerca nello stesso modo in cui la digiteresti su Google, più country, language e domain opzionali se ti servono risultati ristretti a un mercato specifico.

La forma di risposta che ti rompe il codice se la ignori

Google non restituisce sempre un modulo jobs per ogni query, e quando non lo fa, /v1/jobs risponde comunque con un HTTP 200. Il corpo è {"jobs_results": ["There are no jobs for this query"]}, una lista che contiene un’unica stringa invece di oggetti job. Lo abbiamo riprodotto il 27 agosto 2026: la query “product manager” ha restituito quel messaggio a ogni tentativo, mentre “product manager jobs” ha restituito dieci annunci reali per quella che è funzionalmente la stessa ricerca. Un ciclo che assume che jobs_results sia una lista di dict, for job in results: print(job["title"]), lancia un TypeError nel momento in cui arriva alla stringa, perché non puoi indicizzare una stringa con un’altra stringa. La correzione è controllare che ogni voce sia un dict con un title prima di trattarla come un job, non solo che la lista non sia vuota:

import requests

API_KEY = "faf_live_..."  # la tua chiave FastAF

resp = requests.get(
    "https://api.fastaf.co/v1/jobs",
    params={"query": "product manager jobs"},
    headers={"Authorization": f"Bearer {API_KEY}"},
    timeout=60,
)
resp.raise_for_status()
raw_results = resp.json()["jobs_results"]

# Google non ha sempre un modulo jobs per una query. Quando non ce l'ha,
# jobs_results è ["There are no jobs for this query"], una stringa, non un job.
listings = [r for r in raw_results if isinstance(r, dict) and "title" in r]

if not listings:
    print("No jobs module for this query.")

for job in listings:
    apply_options = job.get("apply_options", [])
    apply_via = apply_options[0]["title"] if apply_options else None
    print(f'{job["title"]} @ {job["company_name"]} ({job["location"]})')
    print(f'  apply via {apply_via}, share link: {job["share_link"]}')

Abbiamo eseguito questo su una ricerca reale “data engineer” scrivendo il post (agosto 2026): tutte e dieci le voci in jobs_results erano oggetti job veri, quindi listings è risultata della stessa lunghezza della lista grezza. Il controllo non costa nulla quando il modulo è presente; si limita a fermare il crash quando non lo è.

Ogni annuncio nel nostro campione portava una lista apply_options, coppie {title, link} come {"title": "LinkedIn", "link": "/goto?..."}, una voce per ogni sito che elenca l’annuncio: la pagina carriere dell’azienda stessa, poi quali che siano tra Indeed, LinkedIn, ZipRecruiter, Glassdoor e bacheche simili che l’hanno ripreso a loro volta. Il link di ciascuna è un percorso relativo, non un URL pronto da aprire, quindi salvalo insieme al suo title invece di assumere che funzioni come href a sé stante. Ogni annuncio portava anche un share_link separato, un URL assoluto completo che rimanda a quello stesso annuncio sulla pagina di ricerca lavoro di Google. Quello è l’unico campo nel payload che è un URL completo di per sé, il che lo rende la scelta naturale per un link “vedi online” o una chiave stabile per deduplicare un annuncio tra due esecuzioni di ricerca.

Cos’altro c’è in un annuncio

Oltre a titolo, azienda e località, ogni voce porta via (il sito a cui Google attribuisce l’annuncio), extensions (tag brevi come “Full-time” o “20 days ago”), una description completa, e job_highlights, una lista di punti elenco raggruppati sotto intestazioni come “Qualifications”, “Responsibilities” e “Benefits”, quando il parser di Google le ha trovate nell’annuncio. Non ogni annuncio ha ogni gruppo: nel nostro campione, metà degli annunci saltava del tutto “Benefits” e uno aveva solo “Responsibilities”, quindi leggi job_highlights come una lista da cercare per intestazione, non un insieme fisso di campi con nome.

Quanto costa

/v1/jobs costa 5 crediti a chiamata, che restituisca dieci annunci o la stringa di nessun risultato. I 500 crediti del piano gratuito coprono 100 chiamate. Sul pacchetto Scale ($99 per 300.000 crediti) sono 60.000 chiamate, $1,65 per 1.000.

La versione breve

Costruisci il controllo isinstance e title nel tuo parsing fin dall’inizio, perché la risposta senza lavori è un 200 normale e ben formato, non un errore che il tuo try/except vedrà mai. A parte questo, apply_options ti porta all’annuncio e share_link ti dà un URL stabile per l’annuncio stesso. Prendi 500 crediti gratis, senza carta e passaci le tue combinazioni di titolo e località.