Cómo extraer ofertas de empleo de Indeed: Estructura, Bloques y la Ruta SERP
Indeed es el mayor portal de empleo en la web y un indicador en tiempo real del mercado laboral. También está detrás de Cloudflare con nombres de clases que rotan semanalmente. Aquí te mostramos cómo extraerlo de manera duradera — y cuándo evitar el sitio por completo.
Indeed atrae alrededor de 542 millones de visitas al mes, lo que convierte sus listados en una de las señales en vivo más limpias para el mercado laboral — demanda de contratación por rol, bandas salariales, qué empresas están aumentando su personal. También es un sitio React protegido por Cloudflare cuyos nombres de clases CSS visibles rotan con suficiente frecuencia como para romper un scraper ingenuo en semanas. Esta guía muestra cómo extraer ofertas de empleo de Indeed de manera duradera, cómo paginarlas y deduplicarlas, la realidad de Cloudflare y el vertical de SERP de empleos que te permite evitar la lucha por completo.
Lee la página de búsqueda, ancla en atributos estables
La unidad de datos de Indeed es la página de resultados de búsqueda: la consulta más la ubicación te da una página de tarjetas de empleo. La trampa es seleccionar los nombres de clases con hash (cosas como eu4oa1w0) que Indeed regenera. Ancla en su lugar en los atributos data-testid, que cambian con mucha menos frecuencia:
import requests
from bs4 import BeautifulSoup
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
"Accept-Language": "en-US,en;q=0.9",
}
proxies = {"http": "http://USER:PASS@gate.quantumproxies.io:8000",
"https": "http://USER:PASS@gate.quantumproxies.io:8000"}
url = "https://www.indeed.com/jobs?q=python&l=New+York%2C+NY"
html = requests.get(url, headers=headers, proxies=proxies, timeout=30).text
soup = BeautifulSoup(html, "html.parser")
jobs = []
for card in soup.select("div.job_seen_beacon"):
title = card.find("a", class_="jcs-JobTitle")
company = card.find("span", attrs={"data-testid": "company-name"})
location = card.find("div", attrs={"data-testid": "text-location"})
jobs.append({
"title": title.get_text(strip=True) if title else None,
"company": company.get_text(strip=True) if company else None,
"location": location.get_text(strip=True) if location else None,
"jk": title["href"].split("jk=")[-1][:16] if title else None,
})
print(len(jobs), "jobs on page 1")
Cada tarjeta lleva una clave de empleo (jk) — un ID estable que usas para deduplicar entre páginas y ejecuciones. Almacena en jk, no en URL, porque la misma publicación aparece con diferentes parámetros de seguimiento.
Paginación y sitios por país
Indeed pagina con un parámetro start que incrementa de 10 en 10. Y no es un solo sitio — cada país es un subdominio: www.indeed.com para EE.UU., uk.indeed.com para el Reino Unido, y así sucesivamente. Haz coincidir la salida de tu proxy con el país que estás consultando para que la solicitud sea geográficamente coherente:
def crawl(query, location, country="www", pages=5):
base = f"https://{country}.indeed.com/jobs"
for start in range(0, pages * 10, 10):
params = {"q": query, "l": location, "start": start}
html = requests.get(base, params=params, headers=headers,
proxies=proxies, timeout=30).text
# parse as above, yield rows
yield html
# freshness filter: fromage accepts only 1, 3, 7 or 14 days
# .../jobs?q=python&l=NYC&fromage=7
Una peculiaridad que vale la pena conocer: el filtro "publicado dentro de" de Indeed (fromage) solo acepta 1, 3, 7 o 14 días — otros valores son ignorados, así que construye tu lógica de frescura alrededor de esos intervalos.

La realidad de Cloudflare
Indeed está detrás de Cloudflare, por lo que una IP de centro de datos con una solicitud simple a menudo recibe un desafío en lugar de resultados. Las soluciones son el habitual stack anti-bot: un User-Agent de navegador coherente y un conjunto de encabezados, y — la mayor palanca — una salida residencial que no esté pre-calificada como automatización. Un proxy residencial se presenta como la conexión de un usuario real, lo que mantiene a Cloudflare sirviendo la página. Cuando aparece un desafío, hacer coincidir la huella digital del navegador importa tanto como la IP; nuestra guía sobre cómo superar los desafíos de Cloudflare en 2026 cubre dónde está la línea.
El atajo: el vertical de SERP de empleos
Si lo que realmente quieres son datos del mercado laboral — no el HTML de Indeed específicamente — hay un camino más limpio. El vertical de empleos de Google agrega publicaciones de Indeed y muchos otros portales, y una SERP API devuelve ese vertical de empleos como JSON analizado: título, empresa, ubicación, fecha de publicación y fuente, sin selectores CSS que mantener y sin Cloudflare que enfrentar. Cambias algo de control a nivel de campo por un feed estructurado estable a través de muchos portales a la vez, que para análisis de contratación agregada suele ser la mejor opción. Es el mismo razonamiento detrás de construir un agregador de portales de empleo en un feed SERP en lugar de un scraper por sitio.
Extrae el vertical de empleos como JSON limpio
Para qué son buenos los datos
Más allá de una búsqueda de empleo, estos datos impulsan un análisis real: comparación salarial por rol y ciudad, seguimiento de la demanda de habilidades específicas a lo largo del tiempo, señales de contratación de competidores (un rival formando un nuevo equipo es una pista estratégica), y enriquecimiento — emparejando los roles abiertos de una empresa con datos firmográficos para el outbound. Empareja esto con datos salariales de Glassdoor y obtendrás una imagen de compensación más completa que cualquiera de las fuentes por sí sola.

Preguntas frecuentes
¿Puedes extraer legalmente ofertas de empleo de Indeed?
Las ofertas de empleo son datos públicos, y recopilar listados públicamente visibles es en general defendible, pero los términos de Indeed restringen el acceso automatizado y debes evitar datos personales y respetar los límites de velocidad. Muchos equipos evitan la cuestión de los términos utilizando el vertical de SERP de empleos agregado en lugar de extraer directamente de Indeed. Esta es información general, no asesoramiento legal — revisa los términos actuales antes de un proyecto grande.
¿Por qué mi scraper de Indeed sigue fallando?
Dos razones. Primero, Indeed rota sus nombres de clases CSS con hash, por lo que los selectores anclados a ellos fallan en semanas — ancla en atributos data-testid y la clave de empleo en su lugar. Segundo, Cloudflare comienza a desafiar tu IP una vez que está marcada; una salida residencial y una huella digital de navegador coherente mantienen cargando la página real. Arregla ambos y el scraper se estabiliza.
¿Cómo extraigo empleos de múltiples países?
Indeed sirve a cada país desde su propio subdominio (www.indeed.com, uk.indeed.com, de.indeed.com, etc.). Consulta el subdominio correcto y enruta a través de una salida de proxy en ese país para que la solicitud sea geográficamente consistente — de lo contrario, podrías obtener resultados desajustados o redirigidos. El vertical de SERP de empleos también acepta un parámetro de país si prefieres no gestionar subdominios.
¿Es mejor el SERP de empleos de Google que extraer de Indeed?
Para análisis del mercado laboral agregado, generalmente sí. El vertical de empleos devuelve publicaciones de Indeed y muchos otros portales como JSON estructurado, sin selectores rotativos y sin desafío de Cloudflare que manejar. Pierdes algunos campos específicos de Indeed, pero ganas un feed estable de múltiples fuentes y mucho menos mantenimiento. Extrae directamente de Indeed solo cuando necesites campos que se encuentran en las propias páginas de Indeed.
Indeed recompensa el enfoque duradero: ancla en atributos estables, deduplica por clave de empleo, pagina de diez en diez, y llama con una IP residencial para que Cloudflare te sirva. Y cuando solo necesitas la señal del mercado laboral en lugar del HTML exacto de Indeed, el vertical de SERP de empleos te lo entrega como JSON. Elige el camino que coincida con el control de campo que realmente necesitas.