Em algum momento, lá pela quinquagésima vez que copiei e colei uma vaga do Indeed para uma planilha, comecei a questionar minhas escolhas de carreira. Se você já tentou extrair dados estruturados do Indeed de forma programática, conhece a piada: o erro 403 não é um bug — é um recurso do sistema de defesa do Indeed.
O Indeed é o maior site de vagas do mundo, com cerca de , a qualquer momento e operação em . Isso o torna uma das fontes mais ricas de dados do mercado de trabalho no planeta — e uma das mais difíceis de fazer scrape. O scraper de código aberto JobFunnel (com milhares de estrelas no GitHub) foi literalmente em dezembro de 2025, depois de anos perdendo a guerra contra bots. Nas palavras do próprio mantenedor: "Todos os usuários conseguem fazer scrape de algumas vagas, mas rapidamente são atingidos por CAPTCHA, e a extração falha, não retornando vagas." Outro colaborador relatou ter recebido um CAPTCHA . Então, sim — este não é um alvo trivial de scraping. Neste guia, vou mostrar todos os métodos práticos para fazer scrape do Indeed com Python, mostrar como realmente sobreviver ao bloqueio 403 e — para quem preferir pular toda a depuração — demonstrar uma alternativa sem código usando .
O que significa fazer scrape do Indeed com Python?
Web scraping, em essência, é a extração automática de dados estruturados de páginas da web. Quando falamos em fazer scrape do Indeed com Python, queremos dizer escrever um script que visita as páginas de resultados de busca e as páginas de detalhes das vagas do Indeed, lê o HTML subjacente (ou os dados incorporados) e extrai campos como título da vaga, empresa, localização, salário e descrição para um formato útil — CSV, banco de dados, Google Sheets.
As bibliotecas Python mais usadas para isso são Requests (para chamadas HTTP), BeautifulSoup (para análise de HTML) e Selenium ou Playwright (para automação de navegador). Mas o Indeed não é um site estático simples. Ele é um híbrido: HTML renderizado no servidor com um bloco JSON incorporado, protegido por Cloudflare Bot Management. Isso significa que o seu scraper precisa lidar com conteúdo renderizado por JavaScript, nomes de classes CSS que mudam e proteções agressivas contra bots — tudo isso antes de você analisar um único título de vaga.
Também não existe uma API oficial, gratuita e somente leitura do Indeed em 2026. A antiga Publisher Jobs API foi descontinuada por volta de 2020, e o que restou é apenas para empregadores (Job Sync, Sponsored Jobs). Então, fazer scraping ou pagar por um fornecedor terceirizado de dados são as únicas opções realistas.
Por que fazer scrape dos dados de vagas do Indeed?
O caso de negócio para fazer scrape do Indeed é simples: navegar manualmente por milhares de anúncios é impraticável, e os dados dentro dessas vagas têm valor real.

| Caso de uso | Quem se beneficia | Exemplo |
|---|---|---|
| Geração de leads | Equipes de vendas e recrutamento | Criar listas de empresas contratando com dados de contato |
| Pesquisa do mercado de trabalho | Analistas, equipes de RH | Identificar habilidades em alta e faixas salariais por região |
| Inteligência competitiva | Empregadores, agências de recrutamento | Monitorar padrões de contratação e ofertas salariais da concorrência |
| Automação pessoal de busca de emprego | Candidatos | Agrupar vagas que correspondam aos seus critérios em diferentes locais |
| Dados de treinamento para modelos de ML | Cientistas de dados | Criar modelos de previsão salarial a partir de histórico de vagas |
A própria pesquisa do Indeed Hiring Lab que os dados de anúncios acompanham de perto os números do BLS JOLTS e podem servir como um indicador quase em tempo real das condições do mercado de trabalho nos EUA. Fundos de hedge usam a velocidade de publicação de vagas como um sinal de dados alternativos. Equipes de RH comparam remuneração usando faixas salariais coletadas por scraping. E recrutadores criam listas de prospecção a partir de empresas que estão contratando ativamente.
Uma observação prática: os dados salariais no Indeed estão melhorando, mas ainda são incompletos. Em meados de 2025, cerca de incluíam informações salariais, mas apenas cerca de informavam um valor exato — o restante eram faixas. Toda análise salarial baseada em dados do Indeed precisa levar essa escassez em conta.
Como escolher o método para fazer scrape do Indeed com Python
Não existe uma única maneira "certa" de fazer scrape do Indeed. A melhor abordagem depende do seu nível de habilidade, da quantidade de dados de que você precisa e do quanto de manutenção você está disposto a aceitar. Testei os quatro principais métodos e aqui está a comparação:
| Critério | BS4 + Requests | Selenium | JSON oculto (window.mosaic) | Sem código (Thunderbit) |
|---|---|---|---|---|
| Dificuldade | Iniciante | Intermediário | Intermediário-avançado | Nenhuma (2 cliques) |
| Velocidade | Rápido | Lento (renderização no navegador) | Rápido | Rápido (scraping na nuvem) |
| Conteúdo renderizado por JS | Não | Sim | Sim (dados incorporados) | Sim |
| Resistência a anti-bot | Baixa | Média (detectável) | Média-alta | Alta (tratada automaticamente) |
| Manutenção quando o HTML muda | Alta (seletores quebram) | Alta | Média (estrutura JSON mais estável) | Nenhuma (IA se adapta) |
| Melhor para | Protótipos rápidos | Páginas dinâmicas, conteúdo com login | Dados estruturados em volume | Não desenvolvedores, resultados rápidos |
Este guia passa por cada método. Se você é desenvolvedor Python, vai querer ler as seções de BS4, JSON oculto e Selenium. Se você não programa (ou só está cansado de depurar erros 403), pule direto para a seção do Thunderbit.
Antes de começar
- Dificuldade: Iniciante a intermediário (seções em Python); nenhuma (seção Thunderbit)
- Tempo necessário: ~20–60 minutos para configurar Python e fazer o primeiro scrape; ~2 minutos com Thunderbit
- O que você vai precisar: Python 3.9+, um editor de código, navegador Chrome e (para o caminho sem código) a
Configurando seu ambiente Python para fazer scrape do Indeed
Antes de escrever qualquer código de scraping, deixe seu ambiente pronto.
Instale as bibliotecas necessárias
Crie um ambiente virtual e instale os pacotes de que você vai precisar:
1python -m venv indeed_env
2source indeed_env/bin/activate # No Windows: indeed_env\Scripts\activate
3# Para a abordagem HTTP + parsing
4pip install requests beautifulsoup4 lxml httpx
5# Para a abordagem de JSON oculto (recomendada)
6pip install curl_cffi parsel tenacity
7# Para a abordagem de automação de navegador
8pip install selenium
Algumas observações:
curl_cffié o padrão de 2026 para fazer scrape de sites protegidos por Cloudflare. Ele imita impressões TLS reais de navegador, algo querequestsehttpxpuros não conseguem fazer. Mais adiante explico por que isso importa na seção de anti-bot.- Selenium 4.6+ vem com o Selenium Manager, então você não precisa mais baixar manualmente o ChromeDriver — ele gerencia o binário do navegador automaticamente.
- Use
lxmlcomo backend de parsing do BeautifulSoup. Ele é cerca de do que ohtml.parserda biblioteca padrão.
Crie a estrutura do seu projeto
Mantenha simples:
1indeed_scraper/
2├── scraper.py
3├── requirements.txt
4└── output/
Todos os exemplos de código abaixo usam scraper.py como base.
Como fazer scrape do Indeed com Python usando BeautifulSoup
Esta é a abordagem para iniciantes: use requests para buscar a página e BeautifulSoup para analisar o HTML. É a mais rápida de configurar, mas também a mais frágil no Indeed.
Passo 1: monte a URL de busca do Indeed
As URLs de busca do Indeed seguem um padrão previsível:
1https://www.indeed.com/jobs?q=<query>&l=<location>&start=<offset>
Por exemplo, buscando por "data analyst" em "Austin, TX" a partir da primeira página:
1from urllib.parse import urlencode
2params = {
3 "q": "data analyst",
4 "l": "Austin, TX",
5 "start": 0,
6}
7url = f"https://www.indeed.com/jobs?{urlencode(params)}"
8print(url)
9# https://www.indeed.com/jobs?q=data+analyst&l=Austin%2C+TX&start=0
O Indeed pagina em incrementos de 10, com limite máximo de 1.000 resultados (start <= 990). Qualquer offset acima de 990 retorna silenciosamente a mesma página.
Passo 2: envie uma requisição HTTP com os headers corretos
O Indeed bloqueia imediatamente requisições com strings padrão de user-agent do Python. Você precisa de headers realistas:
1import requests
2headers = {
3 "User-Agent": (
4 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
5 "(KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36"
6 ),
7 "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
8 "Accept-Language": "en-US,en;q=0.9",
9 "Accept-Encoding": "gzip, deflate, br",
10 "Referer": "https://www.indeed.com/",
11}
12response = requests.get(url, headers=headers, timeout=30)
13print(response.status_code)
Se você receber 200, por enquanto está tudo certo. Se receber 403, o Cloudflare identificou você. (Mais adiante explico como sobreviver a isso.)
Passo 3: analise as vagas no HTML
Use BeautifulSoup para selecionar os elementos dos cards de vaga. Dê preferência aos atributos data-testid — eles são mais estáveis do que os nomes de classes CSS aleatórios do Indeed:
1from bs4 import BeautifulSoup
2soup = BeautifulSoup(response.text, "lxml")
3cards = soup.find_all("div", attrs={"data-testid": "slider_item"})
4jobs = []
5for card in cards:
6 title_el = card.find("h2", class_="jobTitle")
7 title = title_el.get_text(strip=True) if title_el else None
8 company = card.find(attrs={"data-testid": "company-name"})
9 location = card.find(attrs={"data-testid": "text-location"})
10 link = title_el.find("a")["href"] if title_el and title_el.find("a") else None
11 jobs.append({
12 "title": title,
13 "company": company.get_text(strip=True) if company else None,
14 "location": location.get_text(strip=True) if location else None,
15 "url": f"https://www.indeed.com{link}" if link else None,
16 })
17print(f"Encontradas {len(jobs)} vagas")
Passo 4: trate a paginação
Percorra as páginas incrementando o parâmetro start:
1import time, random
2all_jobs = []
3for page in range(0, 50, 10): # Primeiras 5 páginas
4 params["start"] = page
5 url = f"https://www.indeed.com/jobs?{urlencode(params)}"
6 response = requests.get(url, headers=headers, timeout=30)
7 # ... analisar como acima ...
8 all_jobs.extend(jobs)
9 time.sleep(random.uniform(3, 6))
Limitações dessa abordagem
Vou ser direto: BS4 + Requests é o método mais fraco para o Indeed em 2026. O requests puro usa a biblioteca TLS da stdlib do Python, que produz uma que o Cloudflare identifica imediatamente como "não é um navegador". Ele também não suporta HTTP/2, que é o protocolo servido pelo Indeed. É bem provável que você seja bloqueado depois de algumas páginas. E os seletores CSS? O Indeed troca nomes de classes como css-1m4cuuf e jobsearch-JobComponent-embeddedBody-1n0gh5s — então qualquer seletor que dependa deles é uma bomba-relógio.
Use este método para protótipos rápidos em uma única página. Para qualquer coisa em escala, use a abordagem de JSON oculto.
Como fazer scrape do Indeed com Python usando dados JSON ocultos
Este é o método que eu recomendo para a maioria dos desenvolvedores Python. Em vez de analisar elementos HTML frágeis, você extrai dados estruturados de uma variável JavaScript incorporada no código-fonte da página do Indeed: window.mosaic.providerData["mosaic-provider-jobcards"].
Cada campo de que você precisa — título da vaga, empresa, localização, salário, chave da vaga, data da publicação, indicador de remoto — já está nesse bloco JSON. Não é necessário executar JavaScript. O esquema está , o que o torna muito mais resiliente do que seletores DOM.
Passo 1: busque o HTML da página
Use curl_cffi em vez de requests — ele imita impressões TLS reais de navegador, o que é essencial para sobreviver ao Cloudflare:
1from curl_cffi import requests as cffi_requests
2response = cffi_requests.get(
3 "https://www.indeed.com/jobs?q=python+developer&l=Remote&start=0",
4 impersonate="chrome124",
5 headers={
6 "Accept-Language": "en-US,en;q=0.9",
7 "Referer": "https://www.indeed.com/",
8 },
9 timeout=30,
10)
11print(response.status_code, len(response.text))
Por que curl_cffi? Ele é uma binding Python sobre o curl-impersonate, que reproduz exatamente o ClientHello TLS, o frame HTTP/2 SETTINGS e a ordem dos headers de navegadores reais. É o único cliente HTTP Python ativamente mantido que derrota em uma única chamada. Os alvos de impersonação suportados incluem chrome120, chrome124, chrome131, variantes do Safari e do Edge.
Passo 2: extraia o JSON com uma expressão regular
O bloco JSON fica embutido em uma tag <script>. Extraia-o com regex:
1import re, json
2MOSAIC_RE = re.compile(
3 r'window\.mosaic\.providerData\["mosaic-provider-jobcards"\]=(\{.+?\});',
4 re.DOTALL,
5)
6match = MOSAIC_RE.search(response.text)
7if match:
8 data = json.loads(match.group(1))
9 results = data["metaData"]["mosaicProviderJobCardsModel"]["results"]
10 print(f"Encontradas {len(results)} vagas no JSON oculto")
11else:
12 print("JSON oculto não encontrado — possível bloqueio ou mudança na página")
Passo 3: extraia os campos das vagas no JSON
Cada item em results contém mais dados do que o que aparece visualmente na página:
1jobs = []
2for job in results:
3 jobs.append({
4 "jobkey": job["jobkey"],
5 "title": job["title"],
6 "company": job.get("company"),
7 "location": job.get("formattedLocation"),
8 "remote": job.get("remoteLocation"),
9 "salary": (job.get("salarySnippet") or {}).get("text"),
10 "posted": job.get("formattedRelativeTime"),
11 "job_type": job.get("jobTypes"),
12 "easy_apply": job.get("indeedApplyEnabled"),
13 "url": f"https://www.indeed.com/viewjob?jk={job['jobkey']}",
14 })
O JSON frequentemente inclui estimativas salariais, atributos de taxonomia (tags de habilidades) e avaliações da empresa que nem sempre aparecem no HTML renderizado.
Passo 4: faça scrape de várias páginas
Use tierSummaries no JSON para entender a contagem total de resultados e depois faça o loop:
1import time, random
2all_jobs = []
3for start in range(0, 50, 10): # Primeiras 5 páginas
4 url = f"https://www.indeed.com/jobs?q=python+developer&l=Remote&start={start}&sort=date"
5 response = cffi_requests.get(
6 url,
7 impersonate="chrome124",
8 headers={"Accept-Language": "en-US,en;q=0.9", "Referer": "https://www.indeed.com/"},
9 timeout=30,
10 )
11 match = MOSAIC_RE.search(response.text)
12 if match:
13 data = json.loads(match.group(1))
14 results = data["metaData"]["mosaicProviderJobCardsModel"]["results"]
15 all_jobs.extend([{
16 "jobkey": j["jobkey"],
17 "title": j["title"],
18 "company": j.get("company"),
19 "location": j.get("formattedLocation"),
20 "salary": (j.get("salarySnippet") or {}).get("text"),
21 "url": f"https://www.indeed.com/viewjob?jk={j['jobkey']}",
22 } for j in results])
23 time.sleep(random.uniform(3, 7))
24print(f"Total: {len(all_jobs)} vagas extraídas")
Por que o JSON oculto é mais resiliente
A estrutura window.mosaic.providerData muda com menos frequência do que os nomes de classes CSS. Você obtém dados limpos e estruturados sem precisar analisar HTML bagunçado. Ainda assim, você continua precisando de mitigação anti-bot (headers, atrasos, proxies) — que cobriremos a seguir.
Como fazer scrape do Indeed com Python usando Selenium
Selenium é a abordagem de automação de navegador. É útil quando você precisa interagir com a página — clicar em painéis de detalhes da vaga, lidar com conteúdo protegido por login ou extrair descrições carregadas dinamicamente que não estão no HTML inicial.
Quando usar Selenium em vez de clientes HTTP
- O Indeed carrega parte do conteúdo dinamicamente (descrições completas no painel lateral direito)
- Você precisa fazer scrape de páginas que exigem estado de sessão ou login
- Você está fazendo scraping em pequena escala, em que velocidade não é crítica
Visão rápida
1from selenium import webdriver
2from selenium.webdriver.common.by import By
3from selenium.webdriver.chrome.options import Options
4import time
5options = Options()
6options.add_argument("--disable-blink-features=AutomationControlled")
7# options.add_argument("--headless=new") # Headless é mais detectável — use com cautela
8driver = webdriver.Chrome(options=options)
9driver.get("https://www.indeed.com/jobs?q=data+engineer&l=New+York")
10time.sleep(3)
11cards = driver.find_elements(By.CSS_SELECTOR, "[data-testid='slider_item']")
12for card in cards:
13 try:
14 title = card.find_element(By.CSS_SELECTOR, "h2.jobTitle").text
15 company = card.find_element(By.CSS_SELECTOR, "[data-testid='company-name']").text
16 location = card.find_element(By.CSS_SELECTOR, "[data-testid='text-location']").text
17 print(f"{title} | {company} | {location}")
18 except Exception:
19 continue
20driver.quit()
Limitações
Selenium é lento — cada página exige renderização completa no navegador. O Chrome em modo headless é (o Cloudflare verifica navigator.webdriver, strings de fornecedor WebGL, contagem de plugins e muito mais). Até mesmo o undetected-chromedriver só adia a detecção; ele não a impede para sempre. E, assim como no BS4, seus seletores quebram quando o Indeed atualiza a interface.
Na maioria dos casos, a abordagem de JSON oculto entrega os mesmos dados mais rápido e com menos manutenção. Reserve o Selenium para casos extremos em que você realmente precise de um navegador.
Como evitar erros 403 ao fazer scrape do Indeed com Python
Esta é a seção mais importante. Se você chegou aqui depois de uma busca frustrante no Google, está no lugar certo.

Por que o Indeed bloqueia seu scraper
O Indeed usa — não DataDome, não PerimeterX. Os headers da resposta confirmam isso: server: cloudflare, cf-ray e o cookie de bot management __cf_bm. O Cloudflare inspeciona sua impressão TLS (JA3/JA4), a ordem dos headers HTTP/2, padrões de requisição e sinais de comportamento de navegador. Se qualquer um desses elementos parecer não humano, você recebe 403, 429, 503 ou — o caso mais traiçoeiro — um 200 OK com uma página de desafio do Turnstile em vez dos dados reais da vaga.
Alterne o User-Agent e os headers da requisição
Um único User-Agent estático é o caminho mais rápido para ser bloqueado. Alterne entre uma lista de strings atuais e realistas. Importante: os campos de versão menor do Chrome estão desde a redução do User-Agent — não invente versões menores diferentes de zero, ou os anti-bots vão sinalizar.
1import random
2USER_AGENTS = [
3 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
4 "(KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36",
5 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 "
6 "(KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36",
7 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
8 "(KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36 Edg/145.0.3800.97",
9 "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0",
10 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 "
11 "(KHTML, like Gecko) Version/17.4 Safari/605.1.15",
12]
13headers = {
14 "User-Agent": random.choice(USER_AGENTS),
15 "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
16 "Accept-Language": "en-US,en;q=0.9",
17 "Accept-Encoding": "gzip, deflate, br, zstd",
18 "Referer": "https://www.indeed.com/",
19 "Sec-Fetch-Dest": "document",
20 "Sec-Fetch-Mode": "navigate",
21 "Sec-Fetch-Site": "same-origin",
22}
Também garanta que seus Client Hints sec-ch-ua correspondam à versão do UA. Um sec-ch-ua: "Chrome";v="131" ao lado de um User-Agent dizendo Chrome 145 é um sinal de alerta imediato.
Adicione atrasos aleatórios entre as requisições
Intervalos fixos chamam atenção de sistemas de detecção de padrões. Use jitter aleatório:
1import time, random
2# Entre cada requisição
3time.sleep(random.uniform(3, 6))
4# Em caso de retry após bloqueio
5def backoff_sleep(attempt):
6 base = 4
7 sleep_time = base * (2 ** attempt) + random.uniform(0, 2)
8 time.sleep(min(sleep_time, 60))
O consenso prático de e é de 3–6 segundos entre requisições por IP, com um teto rígido de cerca de 100 requisições por IP por sessão antes de rotacionar.
Use rotação de proxies
Este é o fator mais importante para o sucesso. Proxies de datacenter de faixas AWS/GCP conseguem algo em torno de 5–15% de sucesso em alvos Cloudflare Enterprise — praticamente inúteis no Indeed. Proxies residenciais, combinados com o fingerprint TLS correto, sobem para 80–95% de sucesso.
1PROXIES = [
2 "http://user:pass@us.residential.example:7777",
3 "http://user:pass@us.residential.example:7778",
4 "http://user:pass@us.residential.example:7779",
5]
6proxy = random.choice(PROXIES)
7response = cffi_requests.get(
8 url,
9 impersonate="chrome124",
10 headers=headers,
11 proxies={"https": proxy},
12 timeout=30,
13)
Em 2026, o preço de proxies residenciais fica em torno de , dependendo do fornecedor e do nível de compromisso. Para o Indeed especificamente, comece com um pool pequeno e aumente conforme necessário.
Trate com elegância os códigos 403, 429 e 503
Não tente simplesmente repetir de forma cega. Códigos diferentes significam coisas diferentes:
1def fetch_with_retry(url, proxy_pool, max_retries=5):
2 for attempt in range(max_retries):
3 proxy = random.choice(proxy_pool)
4 headers["User-Agent"] = random.choice(USER_AGENTS)
5 try:
6 r = cffi_requests.get(
7 url,
8 impersonate=random.choice(["chrome124", "chrome120", "edge101"]),
9 headers=headers,
10 proxies={"https": proxy},
11 timeout=30,
12 )
13 # Verifique o caso traiçoeiro de "200 com desafio"
14 if r.status_code == 200 and "cf-turnstile" not in r.text and "Just a moment" not in r.text:
15 return r
16 if r.status_code == 403:
17 print(f"403 — bloqueado. Trocando proxy, tentativa {attempt + 1}")
18 elif r.status_code == 429:
19 print(f"429 — limite de taxa atingido. Reduzindo a velocidade.")
20 elif r.status_code == 503:
21 print(f"503 — servidor sobrecarregado ou desafio JS.")
22 backoff_sleep(attempt)
23 except Exception as e:
24 print(f"Erro na requisição: {e}")
25 backoff_sleep(attempt)
26 raise RuntimeError(f"Falha após {max_retries} tentativas: {url}")
O caso de 200 com desafio é o mais traiçoeiro. Sempre procure no corpo da resposta por cf-turnstile ou Just a moment antes de tratar um 200 como sucesso.
A alternativa mais fácil: deixe o Thunderbit cuidar do anti-bot para você
Para usuários que não querem montar e manter pools de proxies, rotação de headers e impersonação de fingerprint TLS, o scraping em nuvem do lida automaticamente com CAPTCHAs, rotação de proxies e proteções anti-bot. Sem configuração de proxy, sem curl_cffi, sem bibliotecas para resolver CAPTCHA. É o caminho de menor resistência quando você só precisa dos dados.
Por que seu scraper do Indeed continua quebrando (e como consertar)
A barreira 403 é a dor aguda. A dor crônica é a manutenção — scrapers que funcionam hoje quebram na semana seguinte, retornando silenciosamente dados vazios ou resultados desatualizados.
Como o Indeed quebra seus seletores
O Indeed alterna nomes de classes CSS de forma agressiva. O guia da Bright Data que classes como css-1m4cuuf e css-1rqpxry "parecem ser geradas aleatoriamente — provavelmente no momento da compilação." Testes A/B fazem com que sessões diferentes vejam layouts diferentes. E reestruturações de DOM acontecem sem aviso.
A história do JobFunnel é instrutiva. Um colaborador relatou: "CaptchaBuster conseguiu mitigar o captcha com sucesso, e o motivo de ainda não conseguir fazer scrape da página [é] o uso de seletores desatualizados do BeautifulSoup." O scraper não estava bloqueado — estava analisando os elementos errados.
Estratégia: prefira JSON oculto em vez de análise do DOM
O bloco window.mosaic.providerData está estável em termos de esquema desde pelo menos 2023. O caminho metaData.mosaicProviderJobCardsModel.results[] em 2026. Seletores DOM quebram mensalmente. Extração de JSON quebra anualmente, se quebrar.
Estratégia: use atributos de dados em vez de nomes de classe
Quando precisar tocar no DOM, mire atributos funcionais:
| Seletor | Finalidade |
|---|---|
[data-testid="slider_item"] | Contêiner de cada card de vaga |
[data-testid="job-title"] ou h2.jobTitle > a | Link do título da vaga |
[data-testid="company-name"] | Nome do empregador |
[data-testid="text-location"] | Texto da localização |
data-jk="<jobkey>" em cada card | O gancho mais estável — inalterado desde 2019 |
Adicione verificações de asserção para detectar seletores obsoletos
Nunca deixe seu scraper rodar silenciosamente com zero resultados. Adicione uma verificação após cada busca:
1results = parse_hidden_json(html)
2assert len(results) > 0, (
3 f"O Indeed retornou um conjunto vazio em start={start} — "
4 "possível bloqueio, CAPTCHA ou mudança de seletor. "
5 f"Primeiros 500 caracteres da resposta: {html[:500]}"
6)
Registre os primeiros 500–2000 caracteres da resposta bruta em caso de falha. Assim você consegue ver imediatamente se recebeu um desafio do Turnstile, uma tela de login ou uma mudança de esquema. Execute um teste de fumaça diário em CI com uma consulta fixa (por exemplo, q=python&l=remote) que valide a obtenção de resultados não nulos.
A alternativa com IA: scrapers que nunca quebram
A IA do Thunderbit lê a estrutura da página do zero a cada execução — ela não depende de seletores codificados nem de padrões regex. Quando o Indeed muda o HTML, o Thunderbit se adapta automaticamente. Isso resolve diretamente a carga de manutenção que os usuários em fóruns citam constantemente como sua maior frustração. Se você já acordou com uma mensagem no Slack dizendo "o scraper está devolvendo linhas vazias de novo", sabe o valor de não precisar consertar isso.
Faça scrape do Indeed sem escrever Python: a alternativa sem código
Todo guia concorrente presume que você vai escrever código Python. Mas os dados dos fóruns contam outra história. Os usuários dizem coisas como "é simplesmente muito difícil, com bugs e erros constantes" e alguns sugerem contratar alguém no Fiverr só para conseguir os dados. Se isso parece com você, esta seção é sua rota de fuga.
Como fazer scrape do Indeed com Thunderbit (passo a passo)
Passo 1: Instale a na Chrome Web Store. Você pode começar grátis.
Passo 2: Acesse uma página de resultados de busca do Indeed no navegador — por exemplo, https://www.indeed.com/jobs?q=data+analyst&l=Austin%2C+TX.
Passo 3: Clique no ícone do Thunderbit na barra de ferramentas do navegador e depois clique em "Sugerir Campos com IA." A IA do Thunderbit varre a página e detecta automaticamente colunas como Título da Vaga, Empresa, Localização, Salário, URL da Vaga e Data da Publicação. Você pode revisar e ajustar os campos sugeridos — remover colunas de que não precisa ou adicionar campos personalizados descrevendo o que quer em linguagem natural.
Passo 4: Clique em "Scrape." O Thunderbit extrai os dados da página e os exibe em uma tabela estruturada. Você verá linhas de vagas com os campos configurados.
Enriqueça com scraping de subpáginas
Depois de fazer scrape da página de listagem, clique em "Scrape Subpages" para que o Thunderbit visite cada página individual de detalhes da vaga. Ele extrai descrições completas, qualificações, benefícios e links de candidatura — sem configuração adicional. É o equivalente a escrever um segundo scraper em Python para visitar cada URL /viewjob?jk=<jobkey>, só que com um clique.
Trate a paginação automaticamente
O Thunderbit lida automaticamente com a paginação baseada em cliques do Indeed. Não é necessário construir manualmente URLs de offset nem escrever loops de paginação. Ele navega pelas páginas e agrega os resultados.
Exporte para suas ferramentas favoritas
Exporte os dados extraídos para CSV, Excel, Google Sheets, Airtable ou Notion — . Sem precisar escrever código com csv.writer() ou pandas.to_csv().
Quando usar Python vs. Thunderbit
| Cenário | Melhor ferramenta |
|---|---|
| Pipelines de dados personalizados, automação agendada via cron/Airflow | Python |
| Integração em uma base de código maior | Python |
| Lógica de parsing altamente personalizada | Python |
| Pesquisa pontual ou análise de mercado | Thunderbit |
| Membros da equipe sem perfil técnico precisam dos dados | Thunderbit |
| Obter os dados agora sem depurar erros 403 | Thunderbit |
| Enriquecimento de subpáginas sem configuração | Thunderbit |
Comparação de tempo: configuração em Python + depuração anti-bot = horas a dias (especialmente na primeira vez). Thunderbit = menos de 2 minutos para os mesmos dados. Não estou dizendo que Python está errado — estou dizendo que depende do que você precisa.
Fazer scraping do Indeed é legal? O que você precisa saber
Nenhum dos guias mais bem ranqueados sobre scraping do Indeed aborda a legalidade, o que é surpreendente dado o quanto a pergunta "scraping do Indeed é legal?" aparece em fóruns. Isto não é aconselhamento jurídico, mas aqui está o cenário.
Os termos de serviço do Indeed
Os Termos de Uso do Indeed () não contêm uma cláusula geral de "proibição de scraping". A única proibição explícita de automação está na Seção A.3.5, que veda "o uso de qualquer automação, script ou bot para automatizar o processo Indeed Apply." Isso é algo bem específico do fluxo de candidatura, não da leitura passiva de vagas públicas. O principal mecanismo de aplicação do Indeed é técnico — desafios do Cloudflare, bloqueio de IP, fingerprint de dispositivo — e não judicial.
Precedentes jurídicos relevantes
O caso americano mais citado é hiQ Labs v. LinkedIn. O 9º Circuito que fazer scraping de dados publicamente acessíveis "provavelmente não viola o CFAA" (Computer Fraud and Abuse Act). No entanto, a hiQ acabou depois porque seus funcionários criaram perfis falsos no LinkedIn e aceitaram os Termos de Uso.
Mais recentemente, Meta v. Bright Data (N.D. Califórnia, jan. 2024) gerou uma decisão ainda mais clara. O juiz Chen que os Termos do Facebook e Instagram "não proíbem o scraping de dados públicos quando o usuário está desconectado". A Meta desistiu voluntariamente das demais alegações no mês seguinte.
robots.txt do Indeed
O do Indeed desautoriza amplamente /jobs/ e /job/ para o User-agent: * padrão, mas permite explicitamente que Googlebot e Bingbot acessem /viewjob? — as páginas individuais de detalhes da vaga. Os crawlers de treinamento de IA (GPTBot, CCBot, anthropic-ai) são fortemente restringidos. robots.txt não é juridicamente vinculativo nos EUA, mas respeitá-lo é uma boa prática e uma evidência de boa-fé.
Diretrizes práticas para um scraping responsável
- Faça scrape apenas de dados publicamente disponíveis — nunca faça login, nunca crie contas falsas
- Respeite limites de taxa: 1 requisição a cada 3–6 segundos por IP, concorrência de um dígito
- Não republicar dados extraídos como se fosse seu próprio quadro de vagas
- Use os dados para pesquisa pessoal ou interna, não para revenda comercial sem permissão
- Descarte ou faça hash de PII de que você não precise; defina um limite de retenção para dados que tangenciam informações pessoais
- Se você operar em escala ou na UE/Reino Unido, consulte um advogado — as obrigações de transparência do Artigo 14 do GDPR se aplicam a dados pessoais extraídos
O espectro de risco: automação de busca de emprego pessoal fica na faixa mais baixa. Revenda comercial em larga escala dos dados do Indeed fica na faixa mais alta.
Conclusão e principais pontos
Fazer scrape do Indeed com Python é possível, mas não é um projeto de fim de semana que você configura e esquece. A proteção Cloudflare do Indeed, os seletores que mudam e as medidas agressivas anti-bot significam que você precisa abordar isso com as ferramentas certas e as expectativas certas.
O que eu destacaria de tudo isso:
- O Indeed é a fonte mais rica de dados do mercado de trabalho na web — 350 milhões de visitantes mensais, 130 milhões de vagas — mas reage com força contra scrapers.
- A extração de JSON oculto (
window.mosaic.providerData) é a abordagem Python mais resiliente. O esquema está estável há anos, enquanto seletores CSS quebram todo mês. curl_cfficom impersonação de navegador é o cliente HTTP padrão de 2026 para sites protegidos por Cloudflare.requestsehttpxpuros são bloqueados só pela impressão TLS.- Use sempre headers rotativos, atrasos aleatórios e proxies residenciais para evitar erros 403. Proxies de datacenter são quase inúteis contra o Cloudflare Enterprise.
- Adicione verificações de asserção para saber imediatamente quando os seletores quebram ou quando você está recebendo uma página de desafio em vez dos dados da vaga.
- Para usuários não técnicos ou para quem só quer resultado rápido, oferece um caminho sem código e com IA que se adapta automaticamente às mudanças do site — sem proxies, sem depuração, sem manutenção.
Se quiser experimentar o caminho sem código, o para você testar no Indeed sem compromisso. E, se for seguir pela rota Python, os exemplos de código acima são um ótimo ponto de partida — só lembre que a resistência anti-bot precisa ser tratada como preocupação de primeira classe, não como detalhe secundário.
Para saber mais sobre abordagens e ferramentas de web scraping, confira nossos guias sobre , e . Você também pode assistir a tutoriais no .
Perguntas frequentes
Quais bibliotecas Python são melhores para fazer scrape do Indeed?
Para requisições HTTP, curl_cffi é a melhor escolha em 2026 — ele imita impressões TLS reais de navegador, o que é essencial para contornar o Cloudflare. httpx com HTTP/2 é uma alternativa razoável para alvos menos protegidos. Para parsing de HTML, BeautifulSoup4 com lxml continua sendo o padrão. Para automação de navegador, Playwright (com playwright-stealth) ou undetected-chromedriver funcionam, embora ambos estejam cada vez mais detectáveis. A abordagem de regex para JSON oculto (window.mosaic.providerData) evita a necessidade de parsing pesado por completo.
Por que continuo recebendo erros 403 ao fazer scrape do Indeed?
O Indeed usa Cloudflare Bot Management, que inspeciona sua impressão TLS (JA3/JA4), a ordem dos headers HTTP/2, padrões de requisição e comportamento do navegador. Se você estiver usando requests puro, sua impressão TLS já identifica você como um script Python — o 403 vem antes mesmo de os headers serem lidos. Corrija isso migrando para curl_cffi com impersonação de navegador, rotacionando strings realistas de User-Agent, adicionando atrasos aleatórios (3–6 segundos) e usando proxies residenciais. Verifique também o caso de "200 com desafio do Turnstile" — procure marcadores cf-turnstile no corpo da resposta.
Posso fazer scrape do Indeed sem programar?
Sim. Ferramentas como permitem extrair vagas do Indeed em poucos cliques — instale a extensão do Chrome, acesse uma página de busca do Indeed, clique em "Sugerir Campos com IA" e depois em "Scrape." A IA do Thunderbit detecta automaticamente campos como título da vaga, empresa, localização e salário. Ele trata paginação, enriquecimento de subpáginas (descrições completas) e proteções anti-bot automaticamente. Exporte para CSV, Google Sheets, Airtable ou Notion gratuitamente.
Com que frequência o Indeed muda a estrutura HTML?
O Indeed troca regularmente nomes de classes CSS (por exemplo, css-1m4cuuf, strings aleatórias com hash) e reestrutura elementos DOM sem aviso. Testes A/B fazem com que diferentes usuários vejam layouts diferentes ao mesmo tempo. A abordagem de JSON oculto (window.mosaic.providerData) é significativamente mais estável — o esquema permanece consistente desde pelo menos 2023. Quando for necessário usar seletores DOM, prefira atributos data-testid e data-jk (chave da vaga) em vez de classes CSS.
É legal fazer scrape do Indeed?
O scraping de URLs públicas do Indeed quando o usuário está desconectado provavelmente não gera responsabilidade sob o CFAA nos EUA, com base na decisão do 9º Circuito em hiQ v. LinkedIn (2022) e na decisão Meta v. Bright Data (2024). Os Termos de Uso do Indeed proíbem especificamente automatizar o processo de candidatura, não a leitura passiva de vagas públicas. Ainda assim, faça scraping com responsabilidade: não faça login, não crie contas falsas, respeite limites de taxa, não republicue os dados como se fossem seu próprio quadro de vagas e trate com cuidado quaisquer dados pessoais (nomes de recrutadores, e-mails) sob GDPR/CCPA. Para operações em escala comercial, consulte um advogado.
Saiba mais