Kullanım Senaryoları

Maaş ve Tazminat Verilerinin Toplanması için CAPTCHA Kullanımı

Maaş veritabanları, iş panoları ve devlet işgücü portalları, ücret verilerini Cloudflare Turnstile ve reCAPTCHA ile korur. CAPTCHA'lar, maaş aralıkları rol, konum veya sektöre göre sorgulanırken, özellikle de birçok iş unvanında toplu veri toplama sırasında tetiklenir. Bunları nasıl ele alacağınız aşağıda açıklanmıştır.

Maaş Portallarındaki CAPTCHA Modelleri

Kaynak türü CAPTCHA Tetikleyici
Maaş karşılaştırma siteleri Cloudflare Turnstile Tekrarlanan arama sorguları
İş panosu maaş filtreleri reCAPTCHA v2 Çoklu maaş aramaları
Devlet işgücü istatistikleri Resim CAPTCHA'sı Veri indirme istekleri
Kurumsal maaş sayfaları Cloudflare doğrulama akışı Toplu sayfa görüntülemeleri
İK anket platformları reCAPTCHA v3 Form gönderimleri

Maaş Veri Toplayıcı

import requests
import time
import re
from dataclasses import dataclass

@dataclass
class SalaryRecord:
    title: str
    location: str
    min_salary: float
    max_salary: float
    median_salary: float
    sample_size: int
    source: str

class SalaryCollector:
    def __init__(self, api_key):
        self.api_key = api_key
        self.session = requests.Session()
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
        })

    def collect_salary_data(self, portal_url, job_title, location):
        """Search for salary data, solving CAPTCHAs as needed."""
        response = self.session.get(portal_url, params={
            "title": job_title,
            "location": location
        })

        if self._is_turnstile_challenge(response):
            response = self._solve_turnstile_and_retry(response, portal_url)

        return self._parse_salary_data(response.text, portal_url)

    def collect_bulk(self, portal_url, job_titles, locations):
        """Collect salary data for multiple job title + location combos."""
        results = []

        for title in job_titles:
            for location in locations:
                try:
                    data = self.collect_salary_data(
                        portal_url, title, location
                    )
                    results.extend(data)
                    # Respectful delay between requests
                    time.sleep(2)
                except Exception as e:
                    print(f"Failed for {title} in {location}: {e}")

        return results

    def _is_turnstile_challenge(self, response):
        return (
            response.status_code == 403 or
            "cf-turnstile" in response.text or
            "challenges.cloudflare.com" in response.text
        )

    def _solve_turnstile_and_retry(self, response, url):
        match = re.search(r'data-sitekey="(0x[^"]+)"', response.text)
        if not match:
            raise ValueError("Turnstile sitekey not found")

        resp = requests.post("https://ocr.captchaai.com/in.php", data={
            "key": self.api_key,
            "method": "turnstile",
            "sitekey": match.group(1),
            "pageurl": url,
            "json": 1
        })
        task_id = resp.json()["request"]

        for _ in range(60):
            time.sleep(3)
            result = requests.get("https://ocr.captchaai.com/res.php", params={
                "key": self.api_key,
                "action": "get",
                "id": task_id,
                "json": 1
            })
            data = result.json()
            if data["status"] == 1:
                return self.session.post(url, data={
                    "cf-turnstile-response": data["request"]
                })

        raise TimeoutError("Turnstile solve timed out")

    def _parse_salary_data(self, html, source):
        from bs4 import BeautifulSoup
        soup = BeautifulSoup(html, "html.parser")
        records = []

        def text_or_empty(node):
            return node.text.strip() if node and node.text else ""

        for row in soup.select(".salary-row, .compensation-entry, tr[data-salary]"):
            try:
                records.append(SalaryRecord(
                    title=text_or_empty(row.select_one(".job-title, .title")),
                    location=text_or_empty(row.select_one(".location")),
                    min_salary=self._parse_amount(
                        text_or_empty(row.select_one(".min-salary, .low"))
                    ),
                    max_salary=self._parse_amount(
                        text_or_empty(row.select_one(".max-salary, .high"))
                    ),
                    median_salary=self._parse_amount(
                        text_or_empty(row.select_one(".median, .mid"))
                    ),
                    sample_size=int(
                        text_or_empty(row.select_one(".count, .sample")).replace(",", "") or 0
                    ),
                    source=source
                ))
            except (AttributeError, ValueError):
                continue

        return records

    def _parse_amount(self, text):
        if not text:
            return 0.0
        cleaned = re.sub(r'[^\d.]', '', text)
        return float(cleaned) if cleaned else 0.0


# Usage
collector = SalaryCollector("YOUR_API_KEY")
data = collector.collect_bulk(
    "https://salary.example.com/search",
    job_titles=["Software Engineer", "Data Analyst", "Product Manager"],
    locations=["San Francisco", "New York", "Austin"]
)

for record in data:
    print(f"{record.title} in {record.location}: "
          f"${record.min_salary:,.0f}–${record.max_salary:,.0f} "
          f"(median: ${record.median_salary:,.0f})")

Çok Kaynaklı Toplama (JavaScript)

class SalaryAggregator {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.sources = [];
  }

  addSource(name, searchUrl) {
    this.sources.push({ name, searchUrl });
  }

  async collectForRole(jobTitle, location) {
    const results = [];

    for (const source of this.sources) {
      try {
        const data = await this.querySource(source, jobTitle, location);
        results.push({ source: source.name, ...data });
      } catch (error) {
        results.push({ source: source.name, error: error.message });
      }
    }

    return this.aggregateResults(results, jobTitle, location);
  }

  async querySource(source, jobTitle, location) {
    const url = `${source.searchUrl}?title=${encodeURIComponent(jobTitle)}&location=${encodeURIComponent(location)}`;
    const response = await fetch(url);
    const html = await response.text();

    if (html.includes('cf-turnstile') || response.status === 403) {
      return this.solveAndRetry(source.searchUrl, html, jobTitle, location);
    }

    return this.parseSalaryData(html);
  }

  async solveAndRetry(baseUrl, html, jobTitle, location) {
    const match = html.match(/data-sitekey="(0x[^"]+)"/);
    if (!match) throw new Error('Turnstile sitekey not found');

    const submitResp = await fetch('https://ocr.captchaai.com/in.php', {
      method: 'POST',
      body: new URLSearchParams({
        key: this.apiKey,
        method: 'turnstile',
        sitekey: match[1],
        pageurl: baseUrl,
        json: '1'
      })
    });
    const { request: taskId } = await submitResp.json();

    for (let i = 0; i < 60; i++) {
      await new Promise(r => setTimeout(r, 3000));
      const result = await fetch(
        `https://ocr.captchaai.com/res.php?key=${this.apiKey}&action=get&id=${taskId}&json=1`
      );
      const data = await result.json();
      if (data.status === 1) {
        const response = await fetch(baseUrl, {
          method: 'POST',
          body: new URLSearchParams({
            'cf-turnstile-response': data.request,
            title: jobTitle,
            location: location
          })
        });
        return this.parseSalaryData(await response.text());
      }
    }
    throw new Error('Turnstile solve timed out');
  }

  aggregateResults(results, jobTitle, location) {
    const valid = results.filter(r => !r.error && r.median);
    if (valid.length === 0) return null;

    const medians = valid.map(r => r.median);
    return {
      jobTitle,
      location,
      avgMedian: medians.reduce((a, b) => a + b, 0) / medians.length,
      sources: valid.length,
      range: { min: Math.min(...medians), max: Math.max(...medians) }
    };
  }
}

// Usage
const aggregator = new SalaryAggregator('YOUR_API_KEY');
aggregator.addSource('SalaryDB', 'https://salarydb.example.com/search');
aggregator.addSource('PayScale', 'https://payscale.example.com/lookup');

const result = await aggregator.collectForRole('Software Engineer', 'San Francisco');
console.log(`Median salary: $${result.avgMedian.toLocaleString()} (${result.sources} sources)`);

Veri Toplama Stratejisi

Yaklaşım Günlük hacim CAPTCHA sıklığı Şunun için en iyisi:
Gecikmeli sıralı 100-500 sorgu Düşük Küçük anketler
Vekil rotasyonu 500-2.000 sorgu Orta Bölgesel analiz
Çoklu oturum paralel 2.000-10.000 sorgu Yüksek Kapsamlı veri kümeleri

Sorun giderme

Sorun Sebep Düzeltme
Her aramada Turnstile Oturumun süresi doldu Persist qa_session_cookie çerezi
Maaş verileri "Giriş yapılması gerekiyor" ifadesini gösteriyor Portal kimlik doğrulaması gerektiriyor Aramadan önce kimlik doğrulaması yapın
CAPTCHA çözümünden sonra boş sonuçlar POST parametreleri eksik Tüm gizli form alanlarını dahil et
Çalıştırmalar arasında veriler tutarsız Portal farklı aralıkları gösteriyor Tutarlı sorgu parametreleri kullanın

SSS

Günde kaç maaş sorgulaması yapabilirim?

CaptchaAI'ye değil, portalın oran sınırlarına bağlıdır. CaptchaAI Turnstileyi %100 başarı oranıyla çözüyor. Yüksek hacimli toplama için 2-5 saniye arayla alan talep edilir ve proxy'ler dönüşümlü olarak kullanılır.

Maaş verilerinin toplanması için proxy kullanmalı mıyım?

Evet, özellikle binlerce iş unvanının toplu olarak toplanması için. Yerleşik proxy'ler, veri merkezi IP'lerine kıyasla CAPTCHA sıklığını önemli ölçüde azaltır.

Gerçek zamanlı maaş verilerini toplayabilir miyim?

Çoğu maaş portalı verileri aylık veya üç ayda bir günceller, dolayısıyla gerçek zamanlı tahsilat gereksizdir. Kapsamlı veri kümeleri için haftalık veya aylık toplama çalıştırmaları planlayın.

İlgili Makaleler

Sonraki Adımlar

Tazminat verilerini güvenilir bir şekilde toplayın —CaptchaAI API anahtarınızı alınve maaş portalı CAPTCHA'larını otomatik olarak yönetin.

Bu makale için yorumlar devre dışı bırakılmıştır.