Casos de Uso

Raspado de la bolsa de trabajo con manejo de CAPTCHA usando CaptchaAI

Bolsas de trabajo como Indeed, LinkedIn y Glassdoor implementan CAPTCHA cuando detectan patrones de acceso automatizados. Las plataformas de contratación, los investigadores de mercado y las herramientas de análisis de recursos humanos necesitan una solución CAPTCHA confiable para recopilar datos de ofertas de empleo a escala.


CAPTCHA en las principales bolsas de trabajo

Plataforma Tipo CAPTCHA gatillo Datos disponibles
De hecho reCAPTCHA v2 Alto volumen de solicitudes Ofertas de trabajo, salarios
LinkedIn Cloudflare Challenge Detección de robots Empleos, datos de la empresa.
Puerta de cristal reCAPTCHA v2 Detección de raspado Reseñas, salarios, puestos de trabajo.
ZipRecruiter Cloudflare Turnstile Acceso automatizado Listados de trabajo
monstruo reCAPTCHA v2 Buscar páginas Listados de trabajo
CareerBuilder reCAPTCHA v3 Iniciar sesión, buscar Ofertas de empleo, búsqueda de currículum

Raspador de bolsa de trabajo con manejo CAPTCHA

import requests
import time
import re
from bs4 import BeautifulSoup

CAPTCHAAI_KEY = "YOUR_API_KEY"
CAPTCHAAI_URL = "https://ocr.captchaai.com"


def solve_captcha(method, sitekey, pageurl, **kwargs):
    data = {
        "key": CAPTCHAAI_KEY,
        "method": method,
        "googlekey": sitekey,
        "pageurl": pageurl,
        "json": 1,
    }
    data.update(kwargs)
    resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data=data)
    task_id = resp.json()["request"]

    for _ in range(60):
        time.sleep(5)
        result = requests.get(f"{CAPTCHAAI_URL}/res.php", params={
            "key": CAPTCHAAI_KEY, "action": "get",
            "id": task_id, "json": 1,
        })
        r = result.json()
        if r["request"] != "CAPCHA_NOT_READY":
            return r["request"]
    raise TimeoutError("Solve timeout")


class JobBoardScraper:
    def __init__(self, proxy=None):
        self.session = requests.Session()
        if proxy:
            self.session.proxies = {"http": proxy, "https": proxy}
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
            "AppleWebKit/537.36 Chrome/126.0.0.0 Safari/537.36",
            "Accept-Language": "en-US,en;q=0.9",
        })

    def search_jobs(self, base_url, query, location, pages=5):
        """Search job listings across multiple pages."""
        all_jobs = []

        for page in range(pages):
            url = f"{base_url}/jobs?q={query}&l={location}&start={page * 10}"
            resp = self.session.get(url, timeout=30)

            # Check for CAPTCHA
            if self._has_captcha(resp.text):
                resp = self._solve_and_retry(resp.text, url)

            if resp.status_code == 200:
                jobs = self._parse_listings(resp.text)
                all_jobs.extend(jobs)
                print(f"Page {page + 1}: {len(jobs)} jobs found")
            else:
                print(f"Page {page + 1}: Request failed ({resp.status_code})")

            time.sleep(3)  # Rate limit

        return all_jobs

    def _has_captcha(self, html):
        indicators = [
            'data-sitekey=',
            'g-recaptcha',
            'cf-turnstile',
            'captcha-delivery',
        ]
        return any(ind in html.lower() for ind in indicators)

    def _solve_and_retry(self, html, url):
        # Try reCAPTCHA first
        match = re.search(r'data-sitekey="([^"]+)"', html)
        if match:
            sitekey = match.group(1)

            # Detect Turnstile vs reCAPTCHA
            if 'cf-turnstile' in html:
                token = solve_captcha("turnstile", sitekey, url)
                field = "cf-turnstile-response"
            else:
                token = solve_captcha("userrecaptcha", sitekey, url)
                field = "g-recaptcha-response"

            return self.session.post(url, data={field: token})

        return self.session.get(url)

    def _parse_listings(self, html):
        soup = BeautifulSoup(html, "html.parser")
        jobs = []

        for card in soup.select(".job_seen_beacon, .jobsearch-ResultsList > li"):
            title_el = card.select_one("h2 a, .jobTitle a")
            company_el = card.select_one(".companyName, [data-testid='company-name']")
            location_el = card.select_one(".companyLocation, [data-testid='text-location']")
            salary_el = card.select_one(".salary-snippet, .estimated-salary")

            if title_el:
                jobs.append({
                    "title": title_el.get_text(strip=True),
                    "company": company_el.get_text(strip=True) if company_el else "",
                    "location": location_el.get_text(strip=True) if location_el else "",
                    "salary": salary_el.get_text(strip=True) if salary_el else "",
                    "url": title_el.get("href", ""),
                })

        return jobs


# Usage
scraper = JobBoardScraper(
    proxy="http://user:pass@residential.proxy.com:5000"
)
jobs = scraper.search_jobs(
    base_url="https://jobs.example.com",
    query="python developer",
    location="New York",
    pages=10,
)
print(f"Total jobs collected: {len(jobs)}")

Recopilación de datos salariales

import csv


def collect_salary_data(titles, locations, output_file):
    """Collect salary data across job titles and locations."""
    scraper = JobBoardScraper(
        proxy="http://user:pass@residential.proxy.com:5000"
    )

    results = []
    for title in titles:
        for location in locations:
            try:
                jobs = scraper.search_jobs(
                    "https://jobs.example.com",
                    title, location, pages=3,
                )
                salaries = [j["salary"] for j in jobs if j["salary"]]
                results.append({
                    "title": title,
                    "location": location,
                    "listings": len(jobs),
                    "with_salary": len(salaries),
                    "salary_samples": "; ".join(salaries[:5]),
                })
                time.sleep(5)
            except Exception as e:
                results.append({
                    "title": title,
                    "location": location,
                    "error": str(e),
                })

    with open(output_file, "w", newline="") as f:
        writer = csv.DictWriter(
            f, fieldnames=["title", "location", "listings",
                           "with_salary", "salary_samples", "error"],
        )
        writer.writeheader()
        writer.writerows(results)

    return results


# Collect salary data for market analysis
collect_salary_data(
    titles=["Data Engineer", "ML Engineer", "DevOps Engineer"],
    locations=["San Francisco", "New York", "Austin", "Remote"],
    output_file="salary_data.csv",
)

Consejos de configuración diagnóstico controladosa para bolsas de trabajo

Técnica Por qué ayuda
Representantes residenciales rotativos Distribuye solicitudes a través de IP reales
Retrasos de 3 a 5 segundos entre páginas Imita la velocidad de navegación humana
Usuario-Agente consistente por sesión Evita discrepancias en las huellas dactilares.
Aceptar cookies Las bolsas de trabajo rastrean las sesiones a través de cookies
Aleatorizar el orden de búsqueda Evite patrones de páginas secuenciales
Límite de 200 páginas/day por dominio Manténgase por debajo de los umbrales de detección

Solución de problemas

Problema causa Solución
CAPTCHA en cada búsqueda IP marcada o velocidad excedida Cambie de IP, agregue retrasos más prolongados
Página de resultados vacía Bloque CAPTCHA devuelto en su lugar Detectar CAPTCHA antes de analizar
"Por favor verifica que eres humano" Detección de bot activada Utilice red residencial autorizada + UA realista
Es necesario iniciar sesión para obtener datos salariales Contenido de acceso a la plataforma Implementar sesión autenticada
Resultados diferentes a los del navegador Ubicación/cookie diferencias Coincidir con Aceptar-Idioma y proxy geográfico

Preguntas frecuentes

¿Cuántas ofertas de trabajo puedo eliminar por día?

Con proxies residenciales rotativos y retrasos adecuados, se pueden lograr entre 500 y 2000 páginas por dominio sin necesidad de utilizar CAPTCHA persistentes.

¿Las bolsas de trabajo bloquean el scraping?

La mayoría de las bolsas de trabajo tienen términos que desaconsejan el acceso automatizado, pero su aplicación varía. Los CAPTCHA son su principal defensa, de la que se encarga CaptchaAI.

¿Qué tipo de proxy funciona mejor para las bolsas de trabajo?

Los representantes residenciales rotativos son una opción sólida equilibrio entre costo y tasa de éxito. LinkedIn y Glassdoor bloquean con frecuencia las direcciones IP de los centros de datos.


Guías relacionadas


Recopilar datos del mercado laboral a escala.obtenga su clave CaptchaAIpara la resolución automática de CAPTCHA.

Los comentarios están deshabilitados para este artículo.