Sayfa kaynağını çektiğinizde bir sitekey göremiyorsanız, bu, sitede CAPTCHA olmadığı anlamına gelmez. Modern formların çoğu doğrulamayı ilk HTML ile değil; bir butona tıklandığında, alan odaklandığında, sayfa aşağı kaydırıldığında veya kısa bir gecikmenin ardından JavaScript ile ekler. Çözüm tek cümlede özetlenebilir: statik bir anlık görüntü almak yerine CAPTCHA'nın DOM'a eklenmesini bekleyin ya da izleyin. Bu rehberde geç yüklenen reCAPTCHA ve Turnstile doğrulamalarını güvenilir biçimde yakalamanın üç yöntemini, Selenium bekleme kalıplarını ve uçtan uca bir çözüm akışını bulacaksınız.
Neden CAPTCHA'lar sonradan yüklenir?
Siteler doğrulamayı geciktirerek iki şey elde eder: ilk sayfa daha hızlı açılır ve otomatik trafik, gerçek bir etkileşim olmadan sitekey değerine ulaşamaz. Bir e-ticaret kayıt formunu düşünün — Türkiye'deki birçok fintech ve pazaryeri uygulaması, reCAPTCHA'yı yalnızca kullanıcı e-posta ya da +90 ile başlayan telefon alanına odaklandığında enjekte eder. QA otomasyonunuz sayfayı açar açmaz kaynağı okursa, doğrulama henüz orada değildir ve akış sessizce başarısız olur.
Bu davranış, müşteri otomasyonu geliştiren freelance ekipler için sık rastlanan bir tuzaktır: yerelde sorunsuz çalışan bir betik, Europe/Istanbul saat diliminde canlı ortama alındığında formun geç yüklenen doğrulaması yüzünden durur. Doğru yaklaşım, öğe görünene kadar beklemek ve ancak ondan sonra sitekey ile pageurl değerlerini çıkarmaktır.
KVKK notu: Kazıdığınız veriler kişisel veri içeriyorsa KVKK kapsamına girer. Bu teknikleri yalnızca yetkili olduğunuz QA ve veri toplama akışlarında kullanın.
Geç yükleme tetikleyicilerini tanıyın
Bir doğrulamayı beklemeden önce onu neyin tetiklediğini bilmeniz gerekir. En sık karşılaşılan tetikleyiciler şunlardır:
| Tetikleyici | Örnek | Nasıl tetiklenir |
|---|---|---|
| Buton tıklaması | "Gönder" butonu forma reCAPTCHA ekler | Önce butona tıklayın |
| Alan odağı | CAPTCHA, giriş alanı odaklanınca belirir | E-posta/parola alanına odaklanın |
| Kaydırma konumu | CAPTCHA, ilgili bölüm görününce yüklenir | Forma kadar kaydırın |
| Zamanlayıcı | CAPTCHA 3 saniye sonra yüklenir | Gecikmeyi bekleyin |
| JavaScript koşulu | CAPTCHA, bir AJAX yanıtından sonra yüklenir | Ön koşul isteğini tetikleyin |
Yöntem 1: MutationObserver ile DOM'u izleme
En güvenilir yaklaşım, sayfayı tek bir anlık görüntü gibi değil canlı bir akış gibi ele almaktır. MutationObserver, DOM'a yeni bir düğüm eklendiğinde sizi haberdar eder; böylece CAPTCHA container'ı tam eklendiği anda sitekey değerini okuyabilirsiniz. Gözlemciyi kurmadan önce öğenin zaten var olup olmadığını denetlemek kritiktir — aksi hâlde erken yüklenen bir CAPTCHA'yı tümüyle kaçırırsınız.
Puppeteer
const puppeteer = require('puppeteer');
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.goto('https://staging.example.com/qa-login');
// Set up MutationObserver before triggering the CAPTCHA
const captchaInfo = await page.evaluate(() => {
return new Promise((resolve) => {
// Check if already present
const existing = document.querySelector('.g-recaptcha, .cf-turnstile, .h-captcha');
if (existing) {
resolve({
type: existing.className,
sitekey: existing.getAttribute('data-sitekey'),
});
return;
}
// Watch for new elements
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.nodeType !== 1) continue;
const captcha = node.matches?.('.g-recaptcha, .cf-turnstile, .h-captcha')
? node
: node.querySelector?.('.g-recaptcha, .cf-turnstile, .h-captcha');
if (captcha) {
observer.disconnect();
resolve({
type: captcha.className,
sitekey: captcha.getAttribute('data-sitekey'),
});
return;
}
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
// Timeout after 30 seconds
setTimeout(() => {
observer.disconnect();
resolve(null);
}, 30000);
});
});
console.log('Detected CAPTCHA:', captchaInfo);
Yüklemeyi tetikleyin
Gözlemci hazır olduğunda, doğrulamayı asıl başlatan eylemi çalıştırın — bir tıklama, odak ya da kaydırma:
// Click the submit button to trigger CAPTCHA
await page.click('#submit-btn');
// Or focus the input
await page.focus('#email');
// Or scroll to the form
await page.evaluate(() => {
document.querySelector('#signup-form').scrollIntoView();
});
Yöntem 2: CAPTCHA betiğinin yüklenmesini bekleme
Bazen container div'i sayfada baştan bulunur ama CAPTCHA'yı yöneten JavaScript kütüphanesi henüz yüklenmemiştir; bu durumda data-sitekey boş döner. Container'ı beklemek yerine kütüphanenin global nesnesini (grecaptcha, turnstile) bekleyerek bu yarış koşulunu ortadan kaldırırsınız:
// Wait for reCAPTCHA script to load
await page.waitForFunction(() => {
return typeof window.grecaptcha !== 'undefined'
&& typeof window.grecaptcha.render === 'function';
}, { timeout: 30000 });
// Now extract parameters
const sitekey = await page.evaluate(() => {
const el = document.querySelector('.g-recaptcha');
return el?.getAttribute('data-sitekey');
});
Turnstile için
Cloudflare Turnstile'da aynı kalıp geçerlidir; yalnızca beklenen global nesne ve container sınıfı değişir:
await page.waitForFunction(() => {
return typeof window.turnstile !== 'undefined';
}, { timeout: 30000 });
const sitekey = await page.evaluate(() => {
const el = document.querySelector('.cf-turnstile');
return el?.getAttribute('data-sitekey');
});
Yöntem 3: render çağrılarını yakalama
En kesin yöntem, CAPTCHA kütüphanesinin render fonksiyonunu daha çağrılmadan sarmalamaktır. evaluateOnNewDocument ile sayfa betikleri çalışmadan önce grecaptcha nesnesine bir tuzak yerleştirirsiniz; render tetiklendiğinde sitekey, callback ve container bilgisini doğrudan parametrelerden okursunuz. Bu yöntem DOM sınıflarına ya da CSS seçicilerine hiç bağlı kalmadan çalışır:
// Inject before page scripts run
await page.evaluateOnNewDocument(() => {
window.__captchaDetected = null;
// Hook grecaptcha.render
let _grecaptcha;
Object.defineProperty(window, 'grecaptcha', {
set(val) {
_grecaptcha = val;
const origRender = val.render;
val.render = function(container, params) {
window.__captchaDetected = {
type: 'recaptcha',
sitekey: params.sitekey,
callback: params.callback?.name,
container: typeof container === 'string' ? container : container.id,
};
return origRender.apply(this, arguments);
};
},
get() { return _grecaptcha; },
});
});
await page.goto('https://example.com/signup');
// Trigger the CAPTCHA (click, scroll, etc.)
await page.click('#show-form');
// Wait for detection
await page.waitForFunction(() => window.__captchaDetected !== null, {
timeout: 30000,
});
const detected = await page.evaluate(() => window.__captchaDetected);
console.log('Detected:', detected);
// { type: 'recaptcha', sitekey: '6Le-wvkS...', callback: 'onCaptcha', container: 'recaptcha-box' }
Python (Selenium) ile tembel CAPTCHA'ları bekleme
Node.js yerine Python yığınında çalışıyorsanız aynı mantık WebDriverWait ile kurulur. Tetikleyici eylemi çalıştırın, ardından presence_of_element_located ile CAPTCHA öğesinin görünmesini en fazla 30 saniye bekleyin ve sınıf adından türü belirleyin:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome()
driver.get("https://staging.example.com/qa-login")
# Trigger the CAPTCHA loading
submit = driver.find_element(By.ID, "submit-btn")
submit.click()
# Wait for CAPTCHA to appear
try:
captcha_el = WebDriverWait(driver, 30).until(
EC.presence_of_element_located((
By.CSS_SELECTOR,
".g-recaptcha, .cf-turnstile, .h-captcha"
))
)
sitekey = captcha_el.get_attribute("data-sitekey")
captcha_class = captcha_el.get_attribute("class")
if "g-recaptcha" in captcha_class:
captcha_type = "recaptcha"
elif "cf-turnstile" in captcha_class:
captcha_type = "turnstile"
else:
captcha_type = "hcaptcha"
print(f"Type: {captcha_type}, Sitekey: {sitekey}")
except Exception:
print("No CAPTCHA appeared within 30 seconds")
iframe'i bekleyin (reCAPTCHA)
reCAPTCHA'da küçük bir incelik vardır: div sayfada bulunsa bile betik hâlâ yüklenirken sitekey boş kalabilir. Bu yüzden yalnızca div'i değil, asıl doğrulama iframe'ini bekleyin:
# reCAPTCHA loads an iframe even when the div exists but the script is still loading
WebDriverWait(driver, 30).until(
EC.presence_of_element_located((
By.CSS_SELECTOR,
"iframe[src*='recaptcha'], iframe[src*='challenges.cloudflare.com']"
))
)
print("CAPTCHA iframe loaded")
Uçtan uca: algıla, çöz ve token'ı enjekte edin
Parçaları birleştirdiğinizde akış beş adıma iner:
- Tetikleyici eylemi çalıştırın.
- Öğenin DOM'a eklenmesini bekleyin.
- Sınıf adından CAPTCHA türünü belirleyin.
sitekeyilepageurldeğerlerini CaptchaAI'ye gönderin.- Sonucu sorgulayıp dönen token'ı forma enjekte edin.
Aşağıdaki fonksiyon reCAPTCHA ile Turnstile'ı otomatik ayırt eder, doğru method ve token alanını seçer ve dönen token'ı sayfaya yazar:
import requests
import time
def detect_and_solve(driver, api_key, trigger_action=None):
"""Detect a lazy-loaded CAPTCHA, solve it, and inject the token."""
# 1. Trigger the CAPTCHA
if trigger_action:
trigger_action(driver)
# 2. Wait for it to appear
captcha_el = WebDriverWait(driver, 30).until(
EC.presence_of_element_located((
By.CSS_SELECTOR,
".g-recaptcha, .cf-turnstile, .h-captcha"
))
)
sitekey = captcha_el.get_attribute("data-sitekey")
page_url = driver.current_url
captcha_class = captcha_el.get_attribute("class")
# 3. Determine type and method
if "g-recaptcha" in captcha_class:
method, key_param, token_field = "userrecaptcha", "googlekey", "g-recaptcha-response"
elif "cf-turnstile" in captcha_class:
method, key_param, token_field = "turnstile", "sitekey", "cf-turnstile-response"
else:
method, key_param, token_field = "hcaptcha", "sitekey", "h-captcha-response"
# 4. Solve with CaptchaAI
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": api_key, "method": method,
key_param: sitekey, "pageurl": page_url, "json": "1",
}).json()
task_id = resp["request"]
for _ in range(24):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": api_key, "action": "get", "id": task_id, "json": "1"
}).json()
if result["status"] == 1:
token = result["request"]
break
# 5. Inject
driver.execute_script(f"""
const el = document.querySelector('textarea[name="{token_field}"], input[name="{token_field}"]');
if (el) el.value = arguments[0];
""", token)
return token
Bu akış, çok sayıda geç yüklenen formu tarayan QA çalışmalarında iyi ölçeklenir. CaptchaAI thread tabanlı fiyatlandırır: eş zamanlı istek sayınız kadar thread ödersiniz, çözüm başına ücret alınmaz. İki yaygın başlangıç noktası:
- BASIC — $15/ay, 5 thread; küçük otomasyon işleri ve tekil formlar için.
- ADVANCE — $90/ay, 50 thread; çok sayıda formu paralel tarayan yüksek hacimli işler için.
TL'nin oynaklığı düşünüldüğünde öngörülebilir aylık USD maliyeti planlama açısından gerçek bir avantajdır. CaptchaAI; reCAPTCHA v2/v3, Cloudflare Turnstile ve Challenge, GeeTest v3 ile görüntü/OCR türlerini çözer. hCaptcha ve FunCaptcha desteklenmez, dolayısıyla bu türleri akışınızda beklemeyin.
Hangi yöntemi ne zaman kullanmalısınız?
Üç yöntem birbirini dışlamaz; senaryoya göre seçin:
- MutationObserver — tetikleyicinin ne olduğunu bildiğiniz standart tarayıcı otomasyonunda en pratik seçenektir.
- Global nesneyi bekleme — container erken gelip
sitekey'in geç dolduğu, yanidata-sitekey'in null döndüğü durumlar için idealdir. - render çağrısını yakalama — DOM seçicilerinin kırılgan olduğu, sınıf adlarının rastgele üretildiği tek sayfa uygulamalarında (SPA) en dayanıklı yöntemdir.
Pratikte çoğu ekip MutationObserver'ı ana yöntem olarak kullanır, sitekey boş geldiğinde ise iframe ya da global-nesne beklemesine geçer.
Sorun giderme
| Sorun | Olası neden | Çözüm |
|---|---|---|
| CAPTCHA hiç görünmüyor | Yanlış tetikleyici eylem | Sayfayı inceleyip doğrulamayı neyin tetiklediğini belirleyin |
sitekey boş (null) |
Öğe var ama betik henüz çalışmadı | Yalnızca div'i değil, CAPTCHA iframe'ini bekleyin |
| Gözlemci CAPTCHA'yı kaçırıyor | CAPTCHA zaten sayfadaydı | Gözlemciyi kurmadan önce mevcut öğeleri denetleyin |
| Sürekli zaman aşımı | CAPTCHA yalnızca gerçek kullanıcılara yükleniyor olabilir | Tam işlevli, gerçek bir tarayıcı profiliyle test edin |
Sık sorulan sorular
Sayfa kaynağında sitekey yoksa CAPTCHA gerçekten yok mudur?
Hayır. Ctrl+U ile açtığınız statik kaynakta CAPTCHA div'i veya betiği görünmüyor ama sayfayla etkileşime geçince beliriyorsa, doğrulama geç yüklenmiştir. Öğeyi görmek için tetikleyici eylemi (tıklama, odak, kaydırma) uygulayıp DOM'u yeniden inceleyin.
MutationObserver mı yoksa waitForFunction mı kullanmalıyım?
Container'ın DOM'a ne zaman ekleneceğini bilmiyorsanız MutationObserver daha uygundur; öğe sonradan eklenir. Container zaten sayfadaysa ama kütüphane geç yükleniyorsa (grecaptcha henüz tanımsız), waitForFunction ile global nesneyi beklemek daha güvenilirdir.
reCAPTCHA'nın data-sitekey değeri neden boş geliyor?
Genellikle div DOM'a girmiştir ama reCAPTCHA betiği henüz sitekey'i yerleştirmemiştir. Yalnızca div'in varlığını değil, iframe[src*='recaptcha'] öğesinin yüklenmesini bekleyin; iframe geldikten sonra sitekey güvenilir biçimde okunur.
Bu yöntemler headless tarayıcılarda çalışır mı?
Genellikle evet, ancak dikkatli olun. Bazı siteler doğrulamayı yalnızca headless olmayan tarayıcılara yükler. Puppeteer'da headless: 'new' modunu kullanın ve gerçek bir kullanıcı gibi davranan tam bir tarayıcı profili yapılandırın.
CaptchaAI hangi geç yüklenen CAPTCHA türlerini çözebilir?
Öğeyi algıladıktan sonra CaptchaAI; reCAPTCHA v2/v3, Cloudflare Turnstile ve Challenge, GeeTest v3 ile görüntü tabanlı CAPTCHA'ları API üzerinden çözer. CaptchaFox, Friendly Captcha ve Lemin beta aşamasındadır. hCaptcha ve FunCaptcha desteklenmez.
CaptchaAI ile herhangi bir CAPTCHA'yı çözün
Geç yüklenen doğrulamayı algıladıktan sonra çözüm tek bir API çağrısı kadar basittir. Ücretsiz API anahtarınızı captchaai.com üzerinden alın ve yukarıdaki akışı iş akışınıza ekleyin.