Como extrair vagas do Google Jobs com Python

Empresa, localização e links para se candidatar do Google Jobs como JSON, e o formato de resposta que quebra código de parsing ingênuo em um HTTP 200 comum. Testado em agosto de 2026.

Você quer vagas para um cargo e um lugar, nome da empresa, localização, um link para se candidatar, numa escala além de clicar nos cards de vaga do Google um a um: um quadro de vagas de nicho, um feed de inteligência de mercado, ou um script que acompanha quem está contratando para um cargo nas semanas seguintes.

A requisição

Uma chamada ao /v1/jobs recebe uma busca do mesmo jeito que você digitaria no Google, mais os parâmetros opcionais country, language e domain se você precisar de resultados restritos a um mercado específico.

O formato de resposta que quebra seu código se você pular isso

O Google não retorna um módulo de vagas para toda busca, e quando não retorna, o /v1/jobs mesmo assim responde com um HTTP 200. O corpo é {"jobs_results": ["There are no jobs for this query"]}, uma lista contendo uma única string em vez de objetos de vaga. Reproduzimos isso em 27 de agosto de 2026: a busca “product manager” retornou essa mensagem em toda tentativa, enquanto “product manager jobs” retornou dez vagas reais para o que é, na prática, a mesma busca. Um loop que assume que jobs_results é uma lista de dicts, for job in results: print(job["title"]), lança um TypeError no momento em que chega na string, porque você não pode indexar uma string com outra string. A correção é checar se cada item é um dict com um title antes de tratá-lo como vaga, não só se a lista não está vazia:

import requests

API_KEY = "faf_live_..."  # sua chave 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"]

# O Google nem sempre tem um modulo de vagas para uma busca. Quando nao tem,
# jobs_results e ["There are no jobs for this query"], uma string, nao uma vaga.
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"]}')

Rodamos isso contra uma busca real por “data engineer” enquanto escrevíamos o post (agosto de 2026): as dez entradas em jobs_results eram objetos de vaga de verdade, então listings saiu do mesmo tamanho que a lista bruta. A checagem não custa nada quando o módulo está presente; ela só evita a quebra quando não está.

Toda vaga na nossa amostra trazia uma lista apply_options, pares de {title, link} como {"title": "LinkedIn", "link": "/goto?..."}, um item por site que lista a vaga: a própria página de carreiras da empresa, depois qualquer um entre Indeed, LinkedIn, ZipRecruiter, Glassdoor e quadros parecidos que também tenham pegado a vaga. O link de cada um é um caminho relativo, não uma URL pronta para abrir, então guarde-o junto com o title em vez de assumir que funciona como um href sozinho. Toda vaga também trazia um share_link separado, uma URL absoluta completa de volta para essa mesma vaga na própria página de busca de empregos do Google. Esse é o único campo no payload que já é uma URL completa por si só, o que faz dele a escolha natural para um link de “ver online” ou uma chave estável para deduplicar uma vaga entre duas execuções de busca.

O que mais tem em uma vaga

Além de título, empresa e localização, cada item traz via (o site ao qual o Google atribui a vaga), extensions (tags curtas como “Full-time” ou “20 days ago”), uma description completa, e job_highlights, uma lista de bullets agrupados sob títulos como “Qualifications”, “Responsibilities” e “Benefits” quando o parser do Google os encontrou na vaga. Nem toda vaga tem todos os grupos: na nossa amostra, metade das vagas pulou “Benefits” completamente e uma tinha só “Responsibilities”, então leia job_highlights como uma lista para buscar por título, não um conjunto fixo de campos nomeados.

Quanto custa

/v1/jobs custa 5 créditos por chamada, quer retorne dez vagas ou a string de sem-resultados. Os 500 créditos do plano gratuito cobrem 100 chamadas. No pacote Scale ($99 por 300.000 créditos) isso dá 60.000 chamadas, $1,65 por 1.000.

Resumindo

Construa a checagem de isinstance e title no seu parsing desde o início, porque a resposta de sem-vagas é um 200 normal e bem formado, não um erro que seu try/except algum dia vai ver. Fora isso, apply_options te leva até a vaga e share_link te dá uma URL estável para a vaga em si. Pegue 500 créditos grátis, sem cartão e rode suas próprias combinações de cargo e localização por ele.