Use Cases

İş İlanı Sitelerini Kazıma: CAPTCHA'ları CaptchaAI ile Çözme

Bir işgücü piyasası veri seti oluştururken veya İK analitiği için ilan verisi toplarken kazıyıcınız genellikle birkaç yüz sayfadan sonra aynı duvara toslar: "İnsan olduğunuzu doğrulayın" ekranı. Bu noktada gereken şey daha fazla proxy değil, CAPTCHA doğrulamasını akışın içinde otomatik çözecek bir katmandır. CaptchaAI, iş ilanı sitelerinde en sık çıkan reCAPTCHA v2/v3 ve Cloudflare Turnstile doğrulamalarını API üzerinden çözer; böylece toplama işi kaldığı yerden kesintisiz devam eder.

Bu kılavuz, çözüm katmanını doğrudan requests tabanlı bir kazıyıcıya nasıl bağlayacağınızı, maaş verilerini nasıl topluca toplayacağınızı ve üretimde istikrarlı bir akış için hangi ayarların işe yaradığını gösterir.


İş ilanı sitelerinde CAPTCHA neden tetiklenir?

İş ilanı siteleri, ilan ve maaş verisini rakip toplayıcılara karşı korur. Kısa sürede gelen çok sayıda istek, tek bir IP üzerinden sıralı sayfa gezintisi ya da tarayıcıya benzemeyen istek başlıkları bot tespit sistemlerini tetikler. Tetiklendiğinde sunucu, gerçek içerik yerine bir CAPTCHA doğrulama sayfası döndürür.

Kritik nokta şu: kazıyıcınız bu sayfayı normal HTML sanıp ayrıştırmaya çalışırsa boş sonuç alırsınız ama hata da almazsınız — sessizce eksik veri toplarsınız. Doğru yaklaşım, her yanıtı önce CAPTCHA açısından denetlemek, varsa çözüp isteği yeniden göndermektir.


Büyük iş ilanı sitelerinde CAPTCHA türleri

Site CAPTCHA Türü Tetikleyici Erişilebilen Veri
Indeed reCAPTCHA v2 Yüksek istek hacmi İlanlar, maaşlar
LinkedIn Cloudflare doğrulama akışı Bot tespiti İlanlar, şirket verileri
Glassdoor reCAPTCHA v2 Kazıma tespiti Yorumlar, maaşlar, ilanlar
ZipRecruiter Cloudflare Turnstile Otomatik erişim İlan listeleri
Monster reCAPTCHA v2 Arama sayfaları İlan listeleri
CareerBuilder reCAPTCHA v3 Giriş, arama İlanlar, özgeçmiş arama

Marka adlarını her zaman İngilizce bırakın; kod tarafında da bu türlerin token alanları sabittir: reCAPTCHA için g-recaptcha-response, Turnstile için cf-turnstile-response.


CAPTCHA çözümünü kazıyıcıya bağlama

Aşağıdaki sınıf, her sayfa isteğinden sonra yanıtı CAPTCHA açısından denetler. reCAPTCHA veya Turnstile bulursa sitekey değerini çıkarır, CaptchaAI'ye gönderir ve dönen token'ı ilgili alana yerleştirerek isteği yeniden gönderir. requests ve BeautifulSoup dışında bağımlılık gerektirmez.

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

solve_captcha fonksiyonu görevi in.php uç noktasına gönderir, ardından res.php üzerinden sonucu CAPCHA_NOT_READY dönmeyene kadar sorgular. _has_captcha yöntemi sayfada g-recaptcha ya da cf-turnstile işaretlerini arar; bu sayede yalnızca gerçekten gerektiğinde çözüm çağrısı yaparsınız — her istekte değil.


Maaş verilerini topluca toplama

Piyasa analizi için birden çok unvan ve konumu birlikte taramak istediğinizde her kombinasyonu tek tek çağırmak yerine döngüye alın. Aşağıdaki fonksiyon unvan–konum çiftlerini gezip sonuçları bir CSV dosyasına yazar; hata veren kombinasyonları da ayrı bir alanda kaydeder, böylece toplama yarıda kalmaz.

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

Sonuç dosyası her unvan–konum için toplam ilan sayısını ve maaş bilgisi içeren ilan oranını verir. Bu format, işgücü piyasası raporları veya ücret kıyaslaması için doğrudan bir tabloya dönüştürülebilir.


İş ilanı siteleri için standart yapılandırma

Çözüm katmanı tek başına yeterli değildir; isteklerinizin tutarlı ve öngörülebilir olması gereksiz CAPTCHA tetiklenmesini azaltır. Aşağıdaki ayarlar üretimde istikrarlı toplama için iyi bir başlangıçtır.

Teknik Neden İşe Yarar
Proxy havuzunu düzenli değiştirin İstekleri farklı IP'ler arasında dağıtır
Sayfalar arası 3–5 saniye gecikme İnsan tarama hızını taklit eder
Oturum başına tutarlı User-Agent test profil yapılandırması uyumsuzluklarını önler
Çerezleri kabul edin İş ilanı siteleri oturumu çerezle izler
Arama sırasını rastgele yapın Sıralı sayfa kalıplarından kaçınır
Alan adı başına günlük sayfa sınırı koyun Tespit eşiklerinin altında kalır

Sık karşılaşılan sorunlar ve çözümleri

Sorun Neden Çözüm
Her aramada CAPTCHA IP işaretlendi veya hız sınırı aşıldı IP'yi değiştirin, gecikmeleri artırın
Boş sonuç sayfası Ayrıştırmadan önce CAPTCHA bloğu döndü Ayrıştırmadan önce CAPTCHA'yı algılayın
"İnsan olduğunuzu doğrulayın" uyarısı Bot tespiti tetiklendi Kendi sunucu altyapınızı ve gerçekçi User-Agent kullanın
Maaş verisi için giriş isteniyor Platform içeriği kapıyor Kimliği doğrulanmış oturum kullanın
Tarayıcıdan farklı sonuç Konum/çerez farkları Accept-Language ve coğrafi proxy'yi eşleştirin

KVKK ve yetkili veri toplama

İş ilanı verisi çoğu zaman kişisel veri içerir: aday adları, iletişim bilgileri, kimi zaman maaş beklentileri. Türkiye'de bu veriler Kişisel Verilerin Korunması Kanunu (KVKK) kapsamındadır. Toplama işini yalnızca erişim yetkiniz olan ve kullanım koşulları elveren kaynaklarla sınırlayın; kişisel veri topluyorsanız işleme amacını, saklama süresini ve hukuki dayanağı önceden netleştirin.

CaptchaAI'yi bu tür akışlarda yetkili QA ve veri toplama süreçlerinde CAPTCHA doğrulamasını çözmek için kullanın — bir erişim iznini veya platform koşullarını aşmak için değil. Ayrımı net tutmak hem uyumluluk hem de kalıcı bir toplama altyapısı açısından önemlidir.


Hacme göre thread ve fiyatlandırma

CaptchaAI, çözüm başına değil eşzamanlı thread başına ücretlendirir ve her plan ay boyunca sınırsız çözüm içerir. Tek işçili basit bir kazıyıcı için BASIC ($15/ay, 5 thread) genelde yeterlidir. Onlarca sayfayı paralel tararken darboğaza girerseniz ADVANCE ($90/ay, 50 thread) daha yüksek eşzamanlılık sağlar. Güncel fiyatlar captchaai.com/pricing sayfasındadır.

TL kuru dalgalanırken aylık maliyetin USD ve sabit olması, bütçe planlamasını kolaylaştıran gerçek bir avantajdır; toplama hacminiz artsa bile thread sayısını plan yükselterek öngörülebilir biçimde ayarlarsınız.


Sık sorulan sorular

CaptchaAI iş ilanı sitelerindeki hangi CAPTCHA türlerini çözer?

reCAPTCHA v2 (görünmez sürüm dahil), reCAPTCHA v3, Cloudflare Turnstile ve Cloudflare doğrulama akışını çözer. Bunlar iş ilanı sitelerinde en sık karşılaşılan türlerdir. hCaptcha desteklenmez.

Yüksek hacimli toplama için kaç thread gerekir?

Thread sayısı eşzamanlı çözüm kapasitenizi belirler. Tek işçili bir kazıyıcı için BASIC ($15/ay, 5 thread) genelde yeterlidir; onlarca sayfayı paralel tarıyorsanız ADVANCE ($90/ay, 50 thread) daha rahattır.

Boş sonuç alıyorum ama hata da yok; sebebi ne olabilir?

Büyük olasılıkla sunucu içerik yerine CAPTCHA sayfası döndürüyor ve kazıyıcı bunu ayrıştırıyor. Ayrıştırmadan önce yanıtta CAPTCHA işareti olup olmadığını kontrol edin; kod örneğindeki _has_captcha tam olarak bunun için vardır.

Topladığım aday ve maaş verileri KVKK kapsamında mı?

İçinde kişisel veri (isim, iletişim bilgisi) varsa evet. Yalnızca yetkiniz olan kaynaklardan toplayın ve işleme amacı ile saklama süresini belgeleyin. CaptchaAI'yi yalnızca CAPTCHA doğrulamasını çözmek için kullanın.

Giriş isteyen maaş verilerine nasıl erişilir?

Bazı platformlar maaş verisini oturum arkasında tutar. Kimliği doğrulanmış bir oturum açın ve giriş yaptıktan sonra aynı oturumla istek gönderin; sayfada CAPTCHA çıkarsa aynı çözüm akışı geçerlidir.


İlgili kılavuzlar


İş piyasası verilerini geniş ölçekte toplayın: CaptchaAI anahtarınızı alın ve CAPTCHA doğrulamalarını otomatik çözün.

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