Сценарии использования

Парсинг с доски объявлений с обработкой CAPTCHA с использованием CaptchaAI

Такие доски объявлений, как Indeed, LinkedIn и Glassdoor, применяют CAPTCHA, когда обнаруживают шаблоны автоматического доступа. Рекрутинговые платформы, исследователи рынка и инструменты HR-аналитики нуждаются в надежном решении CAPTCHA для сбора данных о списках вакансий в больших масштабах.


CAPTCHA на основных досках объявлений

Платформа Тип капчи Курок Доступные данные
Действительно reCAPTCHA v2 Большой объем запросов Списки вакансий, зарплаты
LinkedIn страница Cloudflare-защиты в staging Обнаружение ботов Вакансии, данные компании
Стеклянная дверь reCAPTCHA v2 Обнаружение парсинга Отзывы, зарплаты, вакансии
ZipRecruiter Cloudflare Turnstile Автоматизированный доступ Списки вакансий
Монстр reCAPTCHA v2 Страницы поиска Списки вакансий
КарьераСтроитель reCAPTCHA v3 Вход, поиск Объявления о работе, поиск резюме

Скребок доски объявлений с обработкой 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)}")

Сбор данных о зарплате

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",
)

Советы по скрытной настройке досок объявлений

Техника Почему это помогает
Ротация авторизованный сетевой выход Распределяет запросы по реальным IP
Задержка между страницами 3-5 секунд. Имитирует человеческую скорость просмотра
Согласованный пользовательский агент для каждого сеанса Избегает несовпадения сигналы браузера
Принять файлы cookie Доски объявлений отслеживают сеансы с помощью файлов cookie
Рандомизировать порядок поиска Избегайте последовательных шаблонов страниц
Ограничение до 200 страниц/day на домен Оставайтесь ниже порогов обнаружения

Поиск неисправностей

Проблема Причина Исправить
CAPTCHA при каждом поиске IP помечен или скорость превышена Переключите IP, добавьте более длительные задержки
Пустая страница результатов Вместо этого вернулся блок CAPTCHA Обнаружение CAPTCHA перед анализом
«Пожалуйста, подтвердите, что вы человек» Сработало обнаружение ботов Используйте авторизованный сетевой выход + реалистичный UA
Требуется логин для данных о зарплате Содержание шлюза платформы Реализация сеанса с аутентификацией
Результаты отличаются от браузера Местоположение/cookie различия Сопоставьте Accept-Language и географический прокси

Часто задаваемые вопросы

Сколько списков вакансий я могу просмотреть в день?

При ротации авторизованный сетевой выход и правильных задержках можно получить 500-2000 страниц на домен без использования постоянных CAPTCHA.

Блокируют ли доски вакансий парсинг?

На большинстве досок по трудоустройству есть условия, препятствующие автоматическому доступу, но их соблюдение различается. CAPTCHA — это их основная защита, с которой справляется CaptchaAI.

Какой тип прокси подходящий всего подходит для досок объявлений?

Ротация авторизованный сетевой выход — подходящий баланс затрат и успеха. IP-адреса центров обработки данных часто блокируются LinkedIn и Glassdoor.


Связанные руководства

  • Ротация авторизованный сетевой выход
  • Качество прокси влияет на скорость решения
  • Прикрепленные и чередующиеся сеансы

  • Собирайте масштабные данные о рынке труда —получите ключ CaptchaAIдля автоматического решения CAPTCHA.*
Комментарии для этой статьи отключены.