Em algum momento lá pela minha quinquagésima cópia de um título de 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, já sabe 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 raspar. O scraper open source JobFunnel (com milhares de estrelas no GitHub) foi literalmente em dezembro de 2025, depois de anos perdendo a corrida armamentista contra bots. Nas palavras do próprio mantenedor: "All users can scrape some jobs, but are quickly hit by captcha, and the scraping fails, yielding no jobs." Outro colaborador relatou ter recebido um CAPTCHA . Então, sim — não é um alvo simples para scraping. Neste guia, vou mostrar todos os métodos práticos para fazer scraping do Indeed com Python, ensinar como sobreviver ao funil dos 403 e, para quem prefere pular o debug por completo, demonstrar uma alternativa sem código usando .
O que significa fazer scraping do Indeed com Python?
Web scraping, em essência, é a extração automatizada de dados estruturados de páginas web. Quando falamos em fazer scraping do Indeed com Python, estamos nos referindo a escrever um script que acessa páginas de resultados e detalhes de vagas do Indeed, lê o HTML subjacente (ou dados embutidos) e extrai campos como título da vaga, empresa, localização, salário e descrição para um formato utilizável — CSV, banco de dados ou Google Sheets.
As bibliotecas Python mais comuns nesse processo são Requests (para chamadas HTTP), BeautifulSoup (para parsing de HTML) e Selenium ou Playwright (para automação de navegador). Mas o Indeed não é um site estático simples. Ele é híbrido: HTML renderizado no servidor com um bloco JSON embutido, protegido pelo Cloudflare Bot Management. Isso significa que seu scraper precisa lidar com conteúdo renderizado por JavaScript, nomes de classes CSS que mudam constantemente e proteções anti-bot agressivas — tudo isso antes mesmo 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 sobrou é apenas para empregadores (Job Sync, Sponsored Jobs). Então, na prática, as opções reais são fazer scraping ou pagar por um provedor terceirizado de dados.
Por que fazer scraping de dados de vagas do Indeed?
O caso de negócio é direto: navegar manualmente por milhares de anúncios é inviável, e os dados dentro desses anúncios têm alto valor.

| Caso de uso | Quem se beneficia | Exemplo |
|---|---|---|
| Geração de leads | Times de vendas e recrutamento | Criar listas de empresas contratando com informações de contato |
| Pesquisa de mercado de trabalho | Analistas, 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 de busca de emprego | Candidatos | Reunir vagas que correspondem aos seus critérios em diferentes localidades |
| Dados de treinamento para modelos de ML | Cientistas de dados | Criar modelos de previsão salarial com base em histórico de vagas |
A própria pesquisa do Indeed Hiring Lab que os dados de anúncios acompanham de perto o BLS JOLTS e podem servir como um proxy quase em tempo real das condições do mercado de trabalho nos EUA. Fundos de investimento usam a velocidade de publicação de vagas como sinal de dados alternativos. Times de RH comparam remuneração usando faixas salariais extraídas. E recrutadores montam 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 melhores, mas ainda são incompletos. Em meados de 2025, cerca de incluíam salário, mas apenas cerca de traziam um valor exato — o restante mostrava faixas. Qualquer análise salarial baseada em dados do Indeed precisa considerar essa lacuna.
Como escolher o melhor método para fazer scraping do Indeed com Python
Não existe um único jeito “certo” de raspar o Indeed. A melhor abordagem depende do seu nível técnico, da quantidade de dados de que você precisa e do quanto de manutenção você aceita. Testei as quatro abordagens principais, e a comparação é esta:
| Critério | BS4 + Requests | Selenium | JSON oculto (window.mosaic) | Sem código (Thunderbit) |
|---|---|---|---|---|
| Dificuldade | Iniciante | Intermediário | Intermediário a avançado | Nenhuma (2 cliques) |
| Velocidade | Rápido | Lento (renderização no navegador) | Rápido | Rápido (scraping em nuvem) |
| Conteúdo renderizado por JS | Não | Sim | Sim (dados embutidos) | Sim |
| Resistência anti-bot | Baixa | Média (detectável) | Média a alta | Alta (tratado 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 massa | Não desenvolvedores, resultados rápidos |
Este guia percorre cada método. Se você é desenvolvedor Python, vale ler as seções de BS4, JSON oculto e Selenium. Se você não codifica — ou já cansou de depurar 403 — pule direto para a parte do Thunderbit.
Antes de começar
- Dificuldade: Iniciante a intermediário (seções Python); nenhuma (seção Thunderbit)
- Tempo necessário: cerca de 20 a 60 minutos para configurar Python e fazer o primeiro scrape; cerca de 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 scraping do Indeed
Antes de escrever qualquer código de scraping, deixe o 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 com JSON oculto (recomendada)
6pip install curl_cffi parsel tenacity
7# Para a abordagem com automação de navegador
8pip install selenium
Algumas observações:
curl_cffivirou o padrão de 2026 para raspar sites protegidos por Cloudflare. Ele imita as impressões TLS de navegadores reais, algo querequestsehttpxpuros não conseguem fazer. Explico melhor por que isso importa na seção anti-bot.- Selenium 4.6+ já vem com o Selenium Manager, então você não precisa mais baixar manualmente o ChromeDriver — o próprio Selenium gerencia o binário do navegador.
- Use
lxmlcomo backend de parsing do BeautifulSoup. Ele é cerca de que ohtml.parserda biblioteca padrão.
Crie a estrutura do projeto
Mantenha simples:
1indeed_scraper/
2├── scraper.py
3├── requirements.txt
4└── output/
Todos os exemplos de código abaixo partem de scraper.py.
Como fazer scraping do Indeed com Python usando BeautifulSoup
Esta é a abordagem mais amigável para iniciantes: use requests para buscar a página e BeautifulSoup para interpretar o HTML. É a forma mais rápida de começar, 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=<consulta>&l=<local>&start=<offset>
Por exemplo, procurando 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 um limite máximo de 1.000 resultados (start <= 990). Qualquer valor acima de 990 devolve silenciosamente a mesma página.
Passo 2: Envie uma requisição HTTP com headers adequados
O Indeed bloqueia imediatamente requisições com o user-agent padrão 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á funcionando. Se vier 403, o Cloudflare te identificou. (Mais adiante eu explico como sobreviver a isso.)
Passo 3: Parseie os anúncios de vagas do HTML
Use BeautifulSoup para selecionar os elementos dos cards de vaga. Prefira atributos data-testid — eles são mais estáveis do que as classes CSS aleatórias 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: Faç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 # ... parseie 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. requests puro usa a biblioteca TLS padrão do Python, que produz uma que o Cloudflare identifica instantaneamente como “não é um navegador”. Ele também não suporta HTTP/2, que é o que o Indeed serve. Você provavelmente será bloqueado depois de algumas páginas. E os seletores CSS? O Indeed troca nomes como css-1m4cuuf e jobsearch-JobComponent-embeddedBody-1n0gh5s , então qualquer seletor baseado neles é uma bomba-relógio.
Use este método apenas para protótipos rápidos em uma única página. Para qualquer coisa em escala, prefira a abordagem com JSON oculto.
Como fazer scraping 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 embutida no código-fonte da página do Indeed: window.mosaic.providerData["mosaic-provider-jobcards"].
Cada campo que interessa — título, empresa, localização, salário, chave da vaga, data de publicação, indicação de remoto — já está nesse bloco JSON. Não é necessário executar JavaScript. O esquema tem sido , 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 as impressões TLS de navegadores reais, 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 usar curl_cffi? Ele é uma integração 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 mantido ativamente que derrota em uma única chamada. Os alvos de impersonação incluem chrome120, chrome124, chrome131, além de variantes Safari e Edge.
Passo 2: Extraia o JSON com uma expressão regular
O bloco JSON fica embutido em uma tag <script>. Extraia 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 do JSON
Cada item em results traz mais dados do que o que aparece visivelmente 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 muitas vezes inclui estimativas salariais, atributos de taxonomia (como tags de habilidades) e avaliações da empresa que nem sempre aparecem no HTML renderizado.
Passo 4: Raspe várias páginas
Use tierSummaries no JSON para entender a contagem total de resultados e então 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 resistente
A estrutura window.mosaic.providerData muda com menos frequência do que os nomes das classes CSS. Você obtém dados limpos e estruturados sem precisar lidar com HTML bagunçado. Ainda assim, você continua precisando de mitigação anti-bot — headers, atrasos e proxies — que veremos a seguir.
Como fazer scraping do Indeed com Python usando Selenium
Selenium é a abordagem de automação de navegador. Ele é ú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 raspar 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 de vagas no painel lateral)
- Você precisa raspar páginas que exigem estado de sessão ou login
- Está fazendo scraping em pequena escala, em que velocidade não é prioridade
Passo a passo rápido
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 cuidado
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
O Selenium é lento — cada página precisa ser totalmente renderizada no navegador. O Chrome em modo headless é (o Cloudflare verifica navigator.webdriver, strings do fornecedor WebGL, contagem de plugins e muito mais). Até mesmo o undetected-chromedriver apenas adia a detecção; não a impede para sempre. E, assim como no BS4, seus seletores quebrarão quando o Indeed atualizar a interface.
Para a maioria dos casos, a abordagem com JSON oculto entrega os mesmos dados mais rápido e com menos manutenção. Use Selenium apenas para situações específicas em que você realmente precise de um navegador.
Como evitar erros 403 ao fazer scraping 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, nem PerimeterX. Os headers da resposta confirmam isso: server: cloudflare, cf-ray e o cookie de bot management __cf_bm. O Cloudflare analisa sua impressão digital TLS (JA3/JA4), a ordem dos headers HTTP/2, o padrão das requisições e sinais de comportamento do navegador. Se qualquer um desses fatores parecer não humano, você recebe um 403, 429, 503 ou — o caso mais traiçoeiro — um 200 OK com uma página de desafio do Turnstile no lugar dos dados reais da vaga.
Rotacione o User-Agent e os headers da requisição
Um User-Agent estático é o jeito mais rápido de ser bloqueado. Alterne entre uma lista de strings atuais e realistas. Importante: os campos de versão menor do Chrome ficam desde a redução do User-Agent — não invente versões menores diferentes de zero, ou os sistemas anti-bot vão sinalizar isso.
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}
Garanta também que os Client Hints sec-ch-ua batam com a versão do User-Agent. Um sec-ch-ua: "Chrome";v="131" junto de um User-Agent dizendo Chrome 145 é um sinal de alerta instantâneo.
Adicione atrasos aleatórios entre as requisições
Intervalos fixos são fáceis de detectar. Use jitter aleatório:
1import time, random
2# Entre cada requisição
3time.sleep(random.uniform(3, 6))
4# Na retentativa 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, com base em e , é de 3 a 6 segundos entre requisições por IP, com limite rígido de cerca de 100 requisições por IP por sessão antes de rotacionar.
Use rotação de proxies
Este é o maior fator de sucesso. Proxies de datacenter em faixas da AWS/GCP têm algo como 5% a 15% de sucesso em alvos com Cloudflare Enterprise — na prática, inutilizáveis no Indeed. Proxies residenciais, combinados com a impressão digital TLS correta, sobem para 80% a 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 provedor e do nível de compromisso. Para o Indeed, comece com um pool pequeno e escale conforme necessário.
Trate os status codes 403, 429 e 503 com elegância
Não fique apenas tentando de novo no escuro. Cada status code significa algo diferente:
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. 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"Falhou após {max_retries} tentativas: {url}")
O caso de 200 com desafio é o mais traiçoeiro. Sempre procure por cf-turnstile ou Just a moment no corpo da resposta antes de considerar que um 200 foi realmente sucesso.
A alternativa mais fácil: deixe o Thunderbit cuidar do anti-bot para você
Para quem não quer 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 configuração de curl_cffi, sem bibliotecas para resolver CAPTCHA. É o caminho de menor atrito quando você só quer os dados.
Por que seu scraper do Indeed quebra o tempo todo (e como consertar)
A barreira do 403 é a dor aguda. A dor crônica é a manutenção — scrapers que funcionam hoje quebram na semana seguinte, devolvendo silenciosamente dados vazios ou resultados desatualizados.
Como o Indeed quebra seus seletores
O Indeed troca 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 ao mesmo tempo. E reestruturações do DOM acontecem sem aviso.
A história do JobFunnel é um bom exemplo. Um colaborador relatou: "CaptchaBuster has successfully mitigated the captcha, and the reason for still unsuccessfully scraping the page [is] due to outdated beautiful soup selectors." O scraper não estava bloqueado — ele estava lendo os elementos errados.
Estratégia: prefira JSON oculto em vez de parsing do DOM
O bloco window.mosaic.providerData tem esquema estável desde pelo menos 2023. O caminho metaData.mosaicProviderJobCardsModel.results[] em 2026. Seletores de DOM quebram todo mês. Extração de JSON quebra, no pior caso, uma vez por ano.
Estratégia: use atributos de dados em vez de nomes de classe
Quando realmente 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 da empresa |
[data-testid="text-location"] | Texto da localização |
data-jk="<jobkey>" em cada card | O gancho mais estável — sem mudança desde 2019 |
Adicione verificações de asserção para detectar seletores obsoletos
Nunca deixe seu scraper rodar silenciosamente com zero resultados. Adicione uma checagem após cada busca:
1results = parse_hidden_json(html)
2assert len(results) > 0, (
3 f"Indeed retornou um conjunto vazio em start={start} — "
4 "possível bloqueio, CAPTCHA ou desvio no seletor. "
5 f"Primeiros 500 caracteres da resposta: {html[:500]}"
6)
Registre os primeiros 500 a 2000 caracteres da resposta bruta em caso de falha. Assim você identifica imediatamente se recebeu um desafio Turnstile, uma barreira de login ou uma mudança de esquema. Rode um teste de fumaça diário em nível de CI com uma consulta fixa (por exemplo, q=python&l=remote) que valide a presença de resultados.
A alternativa com IA: scrapers que não quebram
A IA do Thunderbit lê a estrutura da página do zero a cada execução — ela não depende de seletores codificados ou padrões de regex. Quando o Indeed muda o HTML, o Thunderbit se adapta automaticamente. Isso resolve diretamente o problema de manutenção que usuários de fóruns citam consistentemente 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 scraping do Indeed sem escrever Python: a alternativa sem código
Todo guia concorrente parte do pressuposto de que você vai escrever código em Python. Mas os dados de fóruns contam outra história. Usuários dizem coisas como "it's just so difficult with constant bugs and errors" e alguns sugerem contratar alguém no Fiverr só para obter os dados. Se isso soa familiar, esta seção é sua saída.
Como fazer scraping do Indeed com Thunderbit (passo a passo)
Passo 1: Instale a na Chrome Web Store. Você pode começar gratuitamente.
Passo 2: Abra 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 do navegador e depois em "AI Suggest Fields." A IA do Thunderbit analisa a página e detecta automaticamente colunas como Título da Vaga, Empresa, Localização, Salário, URL da Vaga e Data de Publicação. Você pode revisar e ajustar os campos sugeridos — remover colunas desnecessárias ou adicionar campos personalizados descrevendo em português simples o que você quer.
Passo 4: Clique em "Scrape." O Thunderbit extrai os dados da página e exibe tudo em uma tabela estruturada. Você verá linhas com as vagas e os campos que configurou.
Enriquecimento com scraping de subpáginas
Depois de raspar a página de listagem, clique em "Scrape Subpages" para que o Thunderbit visite cada página individual de detalhe da vaga. Ele coleta descrições completas, requisitos, 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.
Paginação automática
O Thunderbit lida automaticamente com a paginação por cliques do Indeed. Não há necessidade de montar URLs com offsets manualmente nem escrever loops de paginação. Ele percorre as 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 versus 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 403 | Thunderbit |
| Enriquecimento de subpáginas sem configuração | Thunderbit |
Comparação de tempo: configuração em Python + depuração anti-bot = horas ou 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.
É legal fazer scraping do Indeed? O que você precisa saber
Nenhum dos guias mais bem ranqueados sobre scraping do Indeed aborda legalidade, o que é surpreendente, já que a pergunta "Is scraping Indeed legal?" aparece o tempo todo em fóruns. Isto não é aconselhamento jurídico, mas aqui vai o panorama.
Termos de serviço do Indeed
Os Termos de Serviço do Indeed () não trazem uma cláusula genérica proibindo scraping. A única proibição explícita de automação é a seção A.3.5, que veda "use of any automation, scripting, or bots to automate the Indeed Apply process." Isso é restrito ao fluxo de candidatura, não à leitura passiva de vagas públicas. O principal mecanismo de enforcement do Indeed é técnico — desafios do Cloudflare, bloqueios de IP, fingerprint de dispositivo — e não judicial.
Precedentes jurídicos relevantes
O caso mais citado nos EUA é hiQ Labs v. LinkedIn. O 9º Circuito que raspar dados publicamente acessíveis “provavelmente não viola o CFAA” (Computer Fraud and Abuse Act). No entanto, a hiQ mais tarde foi considerada porque seus funcionários criaram perfis falsos no LinkedIn e aceitaram os termos de uso.
Mais recentemente, Meta v. Bright Data (N.D. Cal., jan. 2024) produziu 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á deslogado”. A Meta retirou voluntariamente as demais alegações no mês seguinte.
robots.txt do Indeed
O do Indeed bloqueia 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. Crawlers de treinamento de IA (GPTBot, CCBot, anthropic-ai) têm restrições severas. O robots.txt não é juridicamente vinculante nos EUA, mas respeitá-lo é uma boa prática e demonstra boa-fé.
Diretrizes práticas para scraping responsável
- Raspe apenas 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, com baixa concorrência
- Não republicar os dados extraídos como se fossem seu próprio site de vagas
- Use os dados para pesquisa pessoal ou interna, não para revenda comercial sem permissão
- Elimine ou anonimize PII desnecessária; 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 aprendizados
Fazer scraping do Indeed com Python é possível, mas não é aquele projeto de fim de semana que você “faz e esquece”. A proteção do Cloudflare, os seletores que mudam e as medidas anti-bot agressivas significam que você precisa encarar isso com as ferramentas certas e expectativas realistas.
O que eu destacaria de tudo isso:
- O Indeed é uma das fontes mais ricas de dados do mercado de trabalho na web — 350 milhões de visitantes mensais, 130 milhões de anúncios — mas combate scrapers com força.
- A extração do JSON oculto (
window.mosaic.providerData) é a abordagem Python mais resiliente. O esquema está estável há anos, enquanto seletores CSS quebram mensalmente. 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 digital TLS.- Sempre use headers rotativos, atrasos aleatórios e proxies residenciais para evitar erros 403. Proxies de datacenter são praticamente inúteis contra 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, com IA, que se adapta automaticamente às mudanças do site — sem proxies, sem debug, sem manutenção.
Se quiser experimentar o caminho sem código, para testar no Indeed sem compromisso. E, se você for seguir pela rota Python, os exemplos acima são um ótimo ponto de partida — só lembre de tratar a resistência anti-bot como prioridade, e não como detalhe.
Para saber mais sobre abordagens e ferramentas de web scraping, confira nossos guias sobre , e . Você também pode assistir aos tutoriais no .
FAQs
Quais bibliotecas Python são melhores para raspar o Indeed?
Para requisições HTTP, curl_cffi é a escolha mais forte em 2026 — ele imita impressões TLS de navegadores reais, o que é essencial para contornar o Cloudflare. httpx com HTTP/2 é um bom plano B 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 com regex no JSON oculto (window.mosaic.providerData) evita a necessidade de parsing pesado quase por completo.
Por que continuo recebendo erro 403 ao raspar o Indeed?
O Indeed usa Cloudflare Bot Management, que analisa sua impressão digital TLS (JA3/JA4), a ordem dos headers HTTP/2, o padrão de requisições e sinais de comportamento do navegador. Se você estiver usando requests puro, a impressão TLS já te identifica imediatamente como script Python — o 403 vem antes mesmo de os headers serem lidos. Corrija isso migrando para curl_cffi com impersonação de navegador, rotacionando User-Agents realistas, adicionando atrasos aleatórios (3 a 6 segundos) e usando proxies residenciais. Também verifique o caso de “200 com desafio Turnstile” — procure por marcadores cf-turnstile no corpo da resposta.
Posso raspar o Indeed sem programar?
Sim. Ferramentas como o permitem extrair vagas do Indeed em poucos cliques — instale a extensão do Chrome, abra uma página de busca do Indeed, clique em "AI Suggest Fields" e depois em "Scrape". A IA do Thunderbit detecta automaticamente campos como título da vaga, empresa, localização e salário. Ele lida automaticamente com paginação, enriquecimento de subpáginas (descrições completas) e proteções anti-bot. Você pode exportar para CSV, Google Sheets, Airtable ou Notion gratuitamente.
Com que frequência o Indeed muda a estrutura HTML?
O Indeed troca nomes de classes CSS com frequência (por exemplo, css-1m4cuuf, strings hash aleatórias) e reestrutura elementos do DOM sem aviso. Testes A/B fazem com que diferentes usuários vejam layouts distintos ao mesmo tempo. A abordagem com JSON oculto (window.mosaic.providerData) é muito mais estável — o esquema vem se mantendo consistente desde pelo menos 2023. Quando precisar usar seletores DOM, prefira atributos data-testid e data-jk (job key) em vez de classes CSS.
É legal raspar o Indeed?
O scraping deslogado de URLs públicas do Indeed provavelmente não cria responsabilidade pelo CFAA nos EUA, com base na decisão hiQ v. LinkedIn do 9º Circuito (2022) e na decisão Meta v. Bright Data (2024). Os Termos de Serviço do Indeed proíbem especificamente automatizar o processo de candidatura, não a leitura passiva de anúncios públicos. Ainda assim, faça scraping com responsabilidade: não faça login, não crie contas falsas, respeite limites de taxa, não republicar os dados como se fossem seu próprio site de vagas e trate qualquer dado pessoal (nomes de recrutadores, e-mails) com cuidado sob GDPR/CCPA. Para operações em escala comercial, consulte um advogado.
Saiba mais
