Integrations

Crawlee + CaptchaAI: Modern Kazıma Çerçevesi Entegrasyonu

Crawlee; oturum havuzunu, proxy'leri ve yeniden denemeleri kutudan çıktığı gibi yönetir, ama CAPTCHA çözmeyi yönetmez. Bir reCAPTCHA v2 sayfasına çarptığınızda çözümü siz eklemek zorundasınız — ve bunun temiz yolu, CaptchaAI'nin HTTP API'sini requestHandler içinden çağırmaktır. Bu rehber, Apify'ın Node.js çerçevesi Crawlee ile CaptchaAI'yi CheerioCrawler, PlaywrightCrawler ve oturum havuzu senaryolarında uçtan uca bağlar.


Crawlee neyi halleder, neyi halletmez

Crawlee'nin cazip yanı, bir kazıma botundaki tekrarlayan altyapı işlerini hazır sunmasıdır: oturum yönetimi, proxy desteği, otomatik yeniden deneme ve istek kuyruğu. Ancak CAPTCHA çözümü bu listede yoktur; onu bir servise devretmeniz gerekir. Aşağıdaki tablo, Crawlee'nin sağladığı yapı taşlarını ve bunların CaptchaAI entegrasyonuna nasıl katkı yaptığını özetler.

Crawlee özelliği Entegrasyona katkısı
Oturum havuzu (session pool) Çözülen CAPTCHA'lar boyunca tutarlı oturum durumu
Otomatik yeniden deneme Çözüm sonrası başarısız istekleri otomatik yeniden dener
Proxy desteği CaptchaAI ile aynı botta proxy yapılandırması
İstek kuyruğu CAPTCHA çözümünü kazıma akışıyla aynı kuyrukta sıraya koyar

CheerioCrawler ile temel entegrasyon

En hafif kurulum CheerioCrawler'dır: sunucu tarafında render edilen statik HTML'i çeker, headless tarayıcı başlatmaz. Aşağıdaki solveCaptcha yardımcı fonksiyonu görevi in.php uç noktasına gönderir, 15 saniye bekler ve res.php'yi 5 saniyelik aralıklarla sorgular. Çözüm henüz hazır değilse API CAPCHA_NOT_READY döndürür ve döngü sorgulamaya devam eder; 24 denemeden sonra hâlâ sonuç yoksa zaman aşımı fırlatılır. Sayfada data-sitekey görülürse token alınır ve g-recaptcha-response alanıyla form gönderilir. Bu yardımcı fonksiyon çerçeveden bağımsızdır — birazdan göreceğiniz PlaywrightCrawler ve oturum havuzu örnekleri de aynı solveCaptcha fonksiyonunu yeniden kullanır.

const { CheerioCrawler } = require('crawlee');
const https = require('https');

const API_KEY = process.env.CAPTCHAAI_API_KEY;

async function solveCaptcha(sitekey, pageurl) {
    // Submit task
    const submitData = new URLSearchParams({
        key: API_KEY,
        method: 'userrecaptcha',
        googlekey: sitekey,
        pageurl: pageurl,
        json: '1',
    });

    const submitResp = await fetch('https://ocr.captchaai.com/in.php', {
        method: 'POST',
        body: submitData,
    });
    const submitResult = await submitResp.json();

    if (submitResult.status !== 1) {
        throw new Error(`Submit error: ${submitResult.request}`);
    }

    const taskId = submitResult.request;

    // Poll for result
    await new Promise(r => setTimeout(r, 15000));

    for (let i = 0; i < 24; i++) {
        const pollResp = await fetch(
            `https://ocr.captchaai.com/res.php?key=${API_KEY}&action=get&id=${taskId}&json=1`
        );
        const pollResult = await pollResp.json();

        if (pollResult.status === 1) return pollResult.request;
        if (pollResult.request !== 'CAPCHA_NOT_READY') {
            throw new Error(`Solve error: ${pollResult.request}`);
        }

        await new Promise(r => setTimeout(r, 5000));
    }

    throw new Error('Solve timeout');
}

// Crawlee spider with CAPTCHA handling
const crawler = new CheerioCrawler({
    maxConcurrency: 5,
    requestHandlerTimeoutSecs: 180,

    async requestHandler({ request, $, log }) {
        // Check if page has CAPTCHA
        const captchaDiv = $('[data-sitekey]');

        if (captchaDiv.length > 0) {
            const sitekey = captchaDiv.attr('data-sitekey');
            log.info(`CAPTCHA found on ${request.url}, solving...`);

            const token = await solveCaptcha(sitekey, request.url);
            log.info('CAPTCHA solved, submitting form');

            // Submit form with token
            const formData = new URLSearchParams({
                'g-recaptcha-response': token,
            });

            const resp = await fetch(request.url, {
                method: 'POST',
                body: formData,
            });
            const html = await resp.text();
            // Parse the result page...
        }

        // Extract data
        const title = $('title').text();
        const data = $('table tr').map((i, row) => ({
            col1: $(row).find('td:eq(0)').text().trim(),
            col2: $(row).find('td:eq(1)').text().trim(),
        })).get();

        log.info(`Scraped ${data.length} rows from ${request.url}`);
    },

    failedRequestHandler({ request, log }) {
        log.error(`Failed: ${request.url}`);
    },
});

// Run
(async () => {
    await crawler.run([
        'https://example.com/page1',
        'https://example.com/page2',
    ]);
})();

PlaywrightCrawler ile JavaScript'li sayfalar

reCAPTCHA widget'ı JavaScript ile geç yükleniyorsa CheerioCrawler onu göremez, çünkü tarayıcı çalıştırmaz. PlaywrightCrawler gerçek bir headless tarayıcı açar: sitekey'i DOM'dan okur, token'ı g-recaptcha-response textarea'sına enjekte eder ve varsa data-callback fonksiyonunu tetikleyerek formu gönderir.

const { PlaywrightCrawler } = require('crawlee');

const crawler = new PlaywrightCrawler({
    maxConcurrency: 3,
    requestHandlerTimeoutSecs: 180,
    launchContext: {
        launchOptions: {
            headless: true,
            args: ['--no-sandbox'],
        },
    },

    async requestHandler({ request, page, log }) {
        await page.goto(request.url, { waitUntil: 'networkidle' });

        // Check for reCAPTCHA
        const sitekey = await page.evaluate(() => {
            const el = document.querySelector('[data-sitekey]');
            return el ? el.getAttribute('data-sitekey') : null;
        });

        if (sitekey) {
            log.info(`CAPTCHA detected, solving for ${request.url}`);

            const token = await solveCaptcha(sitekey, request.url);

            // Inject token
            await page.evaluate((t) => {
                const ta = document.querySelector('[name="g-recaptcha-response"]');
                if (ta) {
                    ta.style.display = 'block';
                    ta.value = t;
                }
                // Trigger callback
                const widget = document.querySelector('.g-recaptcha');
                if (widget) {
                    const cb = widget.getAttribute('data-callback');
                    if (cb && typeof window[cb] === 'function') {
                        window[cb](t);
                    }
                }
            }, token);

            await page.click('button[type="submit"]');
            await page.waitForNavigation({ waitUntil: 'networkidle' });
        }

        // Extract data
        const title = await page.title();
        const content = await page.textContent('body');
        log.info(`Page: ${title}, length: ${content.length}`);
    },
});

Oturum havuzuyla token'ı yeniden kullanma

Aynı site birden çok istekte CAPTCHA çıkarıyorsa her istek için baştan çözmek hem yavaş hem de gereksiz thread tüketir. Crawlee'nin oturum havuzu, çözülen token'ı session.userData içinde saklamanıza ve aynı oturumu sonraki isteklerde kullanmanıza olanak tanır — böylece bir kez çözersiniz, aynı oturum boyunca yeniden kullanırsınız.

const { CheerioCrawler, Session } = require('crawlee');

const crawler = new CheerioCrawler({
    useSessionPool: true,
    sessionPoolOptions: {
        maxPoolSize: 10,
        sessionOptions: {
            maxUsageCount: 50,
        },
    },

    async requestHandler({ request, $, session, log }) {
        // If blocked, solve CAPTCHA and mark session as usable
        if ($('.captcha-container').length > 0) {
            const sitekey = $('[data-sitekey]').attr('data-sitekey');
            const token = await solveCaptcha(sitekey, request.url);

            // Store token in session for subsequent requests
            session.userData = session.userData || {};
            session.userData.captchaToken = token;
            session.userData.tokenTime = Date.now();

            log.info('CAPTCHA solved, session updated');
        }

        // Normal scraping
        const items = $('div.item').map((i, el) => ({
            name: $(el).find('.name').text().trim(),
            price: $(el).find('.price').text().trim(),
        })).get();

        log.info(`Found ${items.length} items`);
    },
});

maxConcurrency'yi thread planınıza göre ölçekleyin

Crawlee'nin maxConcurrency değeri kaç isteğin paralel çalışacağını belirler; her eşzamanlı CAPTCHA çözümü ise bir CaptchaAI thread'i kullanır. Bu yüzden iki sayıyı hizalamak önemlidir: maxConcurrency: 5 ile çalışıyorsanız aynı anda beşe kadar çözüm isteği açık olabilir, dolayısıyla planınızın en az beş thread'i olmalıdır.

CaptchaAI çözüm başına değil, eşzamanlı thread başına faturalandırır — bir thread'deki çözüm bitince sıradaki CAPTCHA'yı alır ve aylık çözüm sayısında üst sınır yoktur. Küçük botlar için BASIC ($15/ay, 5 thread), yukarıdaki maxConcurrency: 5 ile birebir eşleşir; PlaywrightCrawler örneğindeki maxConcurrency: 3 de bu plana rahat sığar. Daha yoğun kazıma için STANDARD ($30/ay, 15 thread) veya ADVANCE ($90/ay, 50 thread), eşzamanlılığı artırmanıza alan açar. TL kurundaki oynaklık düşünüldüğünde, aylık sabit USD fiyatlandırması bütçe planlamasını öngörülebilir kılar.

thread sayınız maxConcurrency'den düşük kalırsa fazladan çözüm istekleri kuyruğa girer ve requestHandlerTimeoutSecs (örneklerde 180 saniye) dolmadan tamamlanmaları gerekir. Zaman aşımı hatalarını görüyorsanız ilk kontrol, bu iki değerin oranıdır.


Üretimde güvenilirlik için ipuçları

Kazımayı üretime taşırken birkaç ayrıntı botunuzu kararlı tutar. Geçici hataları Solve error ile hemen çökertmek yerine yeniden denemeye açık bırakın; Crawlee'nin otomatik yeniden denemesi başarısız istekleri tekrar kuyruğa alır ve arka arkaya denemelerde üstel geri çekilme (exponential backoff) API'ye gereksiz yük binmesini önler. Token'ın kısa ömürlü olduğunu unutmayın: çözümü aldıktan sonra hedef forma dakikalar içinde göndermek en güvenli yaklaşımdır, çünkü bekleyen token'lar geçerliliğini yitirebilir. Son olarak CAPTCHAAI_API_KEY gibi gizli değerleri koda gömmeyin; ortam değişkeni olarak tanımlayın ve bakiyenizi panelden takip ederek thread doygunluğunu erkenden fark edin.


Yetkili kazıma ve KVKK

Otomasyonu üretime almadan önce hedef sitenin kullanım koşullarını ve robots.txt dosyasını kontrol edin. Türkiye'de kişisel veri içeren sayfaları kazıyorsanız toplanan veriler KVKK kapsamına girer; CaptchaAI'yi yalnızca yetkiniz olan QA ve veri toplama iş akışlarında kullanın. Örnek kodlardaki https://example.com/... adresleri yer tutucudur — bunları kendi test ortamınızın adresleriyle değiştirin.


SSS

maxConcurrency değerini CaptchaAI thread sayısıyla nasıl eşleştiririm?

maxConcurrency, aynı anda kaç çözüm isteğinin açık olacağını belirler ve her biri bir thread tüketir. Bu değeri planınızın thread sayısına eşit veya altında tutun — örneğin BASIC ($15/ay, 5 thread) planıyla maxConcurrency: 5.

CheerioCrawler mı PlaywrightCrawler mı kullanmalıyım?

Statik, sunucu tarafında render edilen sayfalar için CheerioCrawler daha hafif ve hızlıdır. reCAPTCHA'yı JavaScript ile yükleyen sayfalarda PlaywrightCrawler gerekir; PuppeteerCrawler da bir alternatiftir.

CaptchaAI, Crawlee ile hangi CAPTCHA türlerini çözer?

reCAPTCHA v2 ve v3, reCAPTCHA Enterprise ve Cloudflare Turnstile gibi türleri çözer. hCaptcha ve FunCaptcha desteklenmez; böyle bir sayfayla karşılaşırsanız akışınızı buna göre tasarlayın.

Token'ı forma eklediğim halde neden hâlâ engelleniyorum?

Çoğu durumda token geçerlidir ama istek şüpheli görünmektedir: gerçekçi HTTP başlıkları ekleyin, token'ı 60–120 saniye içinde gönderin ve token'ı aldığınız oturumla aynı proxy'yi kullanın.

Crawlee'yi Apify üzerinde CaptchaAI ile çalıştırabilir miyim?

Evet. Crawlee aktörünüzü Apify'a dağıtın ve CaptchaAI'yi HTTP API çağrılarıyla kullanın; API anahtarını Apify ortam değişkeni olarak tanımlayın.


İlgili kılavuzlar


Crawlee botunuza otomatik CAPTCHA çözümü ekleyin — CaptchaAI API anahtarınızı alın.

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