PHP ile DataDome CAPTCHA Nasıl Çözülür (Guzzle CookieJar)

datadome captcha in php - How to Solve DataDome CAPTCHA in PHP (Guzzle CookieJar)

PHP’de bir DataDome CAPTCHA’sı token ile değil, çerezle biter. Bu rehber 403 engelleme sayfasını okur, challenge adresini oluşturur ve onu capskip/capskip paketinin dataDome() metoduna verir; paket de CapSkip DataDome çözücüsü ile konuşur. Ardından dönen çerezi, bir sonraki isteği gönderen Guzzle istemcisinin cookie jar’ında tutar. CapSkip belgeleri, çerezin yalnızca onu kazanan User-Agent ve IP adresiyle çalıştığını söyler; bir Guzzle istemcisi ikisini de sabitler.

Neye ihtiyacınız var

  • Bir Windows bilgisayarında CapSkip 3.2.0 veya üzeri. DataDome’u (kaydırıcı ve cihaz kontrolü) çözen ilk sürüm budur.
  • curl ve json eklentileriyle PHP 8.0.2 veya üzeri ve Composer; ikisi de PATH içinde olmalı. Composer’ın platform denetimi, 8.0.30 kurulumunda 8.0.2, 8.1.33 ve 8.4.15 kurulumlarında ise 8.1.0 gerektiğini söyledi.
  • Boş bir çalışma klasörü. İçinde composer require capskip/capskip guzzlehttp/guzzle komutunu çalıştırın ve script’i oraya datadome-demo.php olarak kaydedin. capskip’in 1.5.0 öncesi sürümlerinde dataDome metodu yoktur.
  • Bir CA bundle. PHP for Windows bir tane ile gelmez; bu yüzden curl.se adresinden cacert.pem dosyasını indirip php.ini içindeki curl.cainfo ayarını ona yöneltene kadar ilk https isteği cURL error 60 ile başarısız olur.
  • Otomatikleştirmenize izin verilen, DataDome korumalı bir sayfa.
  • CapSkip için bir host ve port. Local modunda yalnızca 127.0.0.1 üzerinden, aynı bilgisayardaki script’ler için yanıt verir. Server modu, başka bir makinedeki PHP’nin ona ağınız ya da genel bir IP üzerinden ulaşmasını sağlar. Modu şuradan seçersiniz: bağlantı ayarları.
  • Bir testin ötesindeki her şey için görev başına bir sticky proxy.

Test yöntemi: yalnızca Windows’ta çalışan, benim yazdığım iki mock sunucu; canlı bir DataDome sitesi yok. Birincisi, CapSkip API belgeleri içindeki DataDome bölümünde yer alan denetimleri kopyalar. İkincisi, 403 ile bir engelleme sayfası veren, yalnızca birinci mock’un verdiği bir çerezi kabul eden ve aynı zamanda istekleri proxy olarak ileten korumalı bir siteyi taklit eder. https için geçici bir CA kullandı. Script’ler CapSkip uygulamasına hiç dokunmadı; bu yüzden gerçek bir çözüm için süre ölçümü ve canlı bir siteden gelen bir sonuç yoktur.

Bloklar, PHP 8.0.30, 8.1.33, 8.2.29, 8.3.28 ve 8.4.15 üzerinde capskip 1.6.0 ve Guzzle 8.3.1 ile, ayrıca 8.0.30 ve 8.4.15 üzerinde capskip 1.6.0 ve Guzzle 7.16.1 ile çalıştırıldı. PHP 8.5.0 üzerinde çalıştırma sonuna kadar gider, ancak SDK’nın Http.php dosyası, CapSkip’e gönderdiği her istek için curl_close() ile ilgili bir Deprecated uyarısı yazdırır; benim çalıştırmamda üç tane.

1. Adım: kimlik başına bir Guzzle istemcisi, ardından engelleme sayfası

Beş kod bloğunu gösterilen sırayla datadome-demo.php dosyasına koyun. İlki her use satırını içerir, bu yüzden diğerleri hiçbirini eklemez.

Üç Guzzle varsayılanı bu döngüye ters düşer. Bir istemci, ona bir jar vermediğiniz sürece çerez tutmaz. Kendini GuzzleHttp artı ana sürüm numarasıyla tanıtır; mock bunu Guzzle 7 ve 8 üzerindeki yalın bir istemciden gördü. Ve herhangi bir 4xx yanıtında ClientException fırlatır; en çok burada önemlidir, çünkü 403 engelleme sayfası, okumanız gereken challenge’ın kendisidir. Bu varsayılan açık bırakıldığında benim çalıştırmam ilk istekte öldü ve mock CapSkip hiç görev almadı. Bu yüzden session() http_errors seçeneğini kapatır. Ayrıca jar’ı, User-Agent’ı ve proxy’yi birlikte ayarlar, böylece birbirlerinden kopamazlar. Yeni bir kimlik, yeni bir istemci demektir.

<?php
// composer require capskip/capskip guzzlehttp/guzzle
require __DIR__ . '/vendor/autoload.php';

use CapSkip\CapSkip;
use CapSkip\Exceptions\ApiException;
use CapSkip\Exceptions\CapSkipError;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Cookie\SetCookie;
use Psr\Http\Message\ResponseInterface;

const PAGE_URL = 'https://www.example.com/'; // a page behind DataDome
// For the first request only. The cookie comes back with its own User-Agent.
const BROWSER_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
    . '(KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36';

// Guzzle fixes its options when the Client is built, so one Client is one
// identity: a cookie jar, a User-Agent and a proxy.
function session(CookieJar $jar, string $userAgent, ?string $proxyUrl = null): Client
{
    return new Client([
        'cookies' => $jar,
        'headers' => ['User-Agent' => $userAgent],
        'proxy' => $proxyUrl,
        'http_errors' => false, // a 403 block page is an answer, not an exception
        'timeout' => 30,
    ]);
}

DataDome’un güvenmediği bir istek 403 alır (bazen 401) ve kısa bir HTML sayfası. Belgelerin captcha_url dediği challenge adresi, ya geo.captcha-delivery.com üzerindeki bir iframe’in src değeridir ya da bir script’in dd nesnesinden derlediği bir şeydir. Aşağıdaki fonksiyon ikisini de karşılar. dd nesnesindeki rt girdisi challenge sayfasını seçer: c harfi /captcha/ altındaki kaydırıcıyı, i harfi /interstitial/ altındaki cihaz kontrolünü seçer. Belgeler, bv değerindeki bir t değerini yasaklanmış bir IP adresi olarak tanımlar ve sıradan challenge’larda fe gösterir; fe değerinin bu şekilde okunması onlara aittir.

İlk istek, CapSkip’e hiç gönderilmeyen sade bir Chrome User-Agent ile gider; bu yüzden sürümünün hiçbir şeyle eşleşmesi gerekmez. Her engelleme sayfası yeni bir challenge getirir, bu yüzden adresini hemen kullanın.

// Build the challenge address from a DataDome block page, or null.
function captchaUrlFrom(ResponseInterface $resp): ?string
{
    if (!in_array($resp->getStatusCode(), [401, 403], true)) {
        return null;
    }
    $html = (string) $resp->getBody();
    // Some block pages carry the challenge iframe: its src is the address.
    if (preg_match('/<iframe[^>]+src="(https:\/\/[^"]*captcha-delivery\.com\/[^"]+)"/', $html, $found)) {
        return $found[1]; // an HTML-escaped &amp; is fine, CapSkip decodes it
    }
    // Most build it from the dd object in a script instead.
    if (!preg_match('/var\s+dd\s*=\s*(\{[^}]*\})/', $html, $found)) {
        return null;
    }
    $dd = json_decode(str_replace("'", '"', $found[1]), true);
    if (!is_array($dd)) {
        return null;
    }
    $query = ['initialCid' => $dd['cid'] ?? '', 'hash' => $dd['hsh'] ?? '', 'cid' => $dd['cookie'] ?? ''];
    if (!empty($dd['t'])) {
        $query['t'] = $dd['t']; // fe is a challenge, bv means the address is banned
    }
    $query['referer'] = PAGE_URL;
    $query['s'] = $dd['s'] ?? '';
    foreach (['e', 'b'] as $key) {
        if (!empty($dd[$key])) {
            $query[$key] = $dd[$key];
        }
    }
    $query['dm'] = 'cd';
    $path = ($dd['rt'] ?? '') === 'i' ? 'interstitial' : 'captcha';

    return 'https://' . ($dd['host'] ?? 'geo.captcha-delivery.com') . "/{$path}/?"
        . http_build_query($query, '', '&', PHP_QUERY_RFC3986);
}

2. Adım: çözün ve çerezi jar’a koyun

Metot dataDome(string $captchaUrl, string $url, array $options = []) biçimindedir; küçük harfli datadome() benim çalıştırmamda aynı sonucu döndürdü. options dizisi userAgent ve proxy alır ve içindeki null değerler gönderilmez. Proxy, aşağıdaki gibi type ve uri içeren bir dizi olarak ya da SDK’nın HTTP proxy olarak gönderdiği düz bir dize olarak verilir.

userAgent vermeyin. CapSkip o zaman kendi tarayıcısının User-Agent değerini kullanır, sonuçta bildirir ve siz bundan sonra o dizeyi gönderirsiniz. Sizin vereceğiniz bir dize, o tarayıcıyla aynı ana sürüme sahip, Windows için standart Chrome olmalıdır: SDK yerel olarak bir Firefox dizesini reddetti ve mock, bir Chrome 120 dizesine ERROR_WRONG_USERAGENT yanıtı verdi. Metot en fazla recaptchaTimeout kadar, varsayılan olarak 300 saniye bekler.

// Local mode. In Server mode, use your server's address instead.
$solver = new CapSkip(['host' => '127.0.0.1', 'port' => 8080]);

function solve(CapSkip $solver, string $captchaUrl, ?string $proxyUrl): array
{
    $options = [];
    if ($proxyUrl !== null) {
        [$scheme, $rest] = explode('://', $proxyUrl, 2);
        $options['proxy'] = ['type' => strtoupper($scheme), 'uri' => $rest];
    }
    // No userAgent option: CapSkip uses its own browser's and reports it.
    return $solver->dataDome($captchaUrl, PAGE_URL, $options);
}

Sonuç; captchaId, code, userAgent ve cookie anahtarlarını içeren bir dizidir. code değeri, çerezin DataDome tarafından ayarlandığı hâliyle, öznitelikler dahil olarak kendisidir. cookie değeri yalnızca datadome ad ve değer çiftidir. proxy anahtarı yalnızca siz hiç proxy vermediyseniz ve CapSkip kendi DataDome listesinden bir proxy kullandıysa görünür. Şema ve giriş bilgisi olmadan host ile portu taşır; bu yüzden o liste girdisi bir giriş gerektiriyorsa girişi kendiniz ekleyin. Çerez o zaman yalnızca o proxy üzerinden çalışır.

SDK’nın istisnaları CapSkip\Exceptions altında yer alır ve RuntimeException değil, Exception sınıfından türeyen CapSkipError sınıfını genişletir. Yalnızca ApiException, yani reddedilen bir görev, bir kez daha denemeye değer başarısız bir çözümdür.

Bir PHP script’ini Cookie başlığı oluşturan bir script’ten ayıran nokta budur. code değeri doğrudan bir Guzzle SetCookie nesnesine ayrıştırılır ve jar sonra bir tarayıcının kurallarını uygular: çerezi yalnızca Domain özniteliğiyle eşleşen bir host’a, eşleşen bir Path özniteliği için, Max-Age dolana kadar ve https üzerinden ise yalnızca Secure özniteliği ayarlıysa gönderir. Yerine geçen çerezi de saklar. Mock, 200 sayfasıyla birlikte yeni bir çerez gönderdi ve aynı istemci üzerinden giden sonraki istek onu taşıdı.

Jar dört şekilde sessizce başarısız olur ve her birine ben de çarptım. Domain özniteliği olmayan bir çerez saklanır ama hiç gönderilmez. Domain ya da Secure uyumsuzluğu Cookie başlığı göndermez, bu yüzden jar çerezi tutsa da site 403 yanıtı verir. setCookie(), bir çerezi reddettiğinde, örneğin süresi dolmuş bir çerezi, istisna fırlatmak yerine false döndürür. Ve engelleme sayfasının kendisinin ayarladığı bir datadome çerezi, çözülmüş olanın yanında jar’da kalır; ikisi de gider ve bir site yanlış olanı okuyabilir. İlk mock’um bu çerezi hiç ayarlamadı; ayarladığında, aşağıdaki clear() çağrısı onu kaldırana kadar üç deneme de başarısız oldu. Blok dördüne karşı da korur.

function sendWithCookie(array $result, CookieJar $jar, ?string $proxyUrl): array
{
    // code is the cookie as DataDome set it, with Domain, Path and Max-Age.
    $cookie = SetCookie::fromString($result['code']);
    if (!$cookie->getDomain()) {
        // A jar keeps a cookie with no Domain but never sends it.
        $cookie->setDomain(parse_url(PAGE_URL, PHP_URL_HOST));
    }
    // The block page may have set a datadome cookie of its own. Drop it.
    $jar->clear(parse_url(PAGE_URL, PHP_URL_HOST), '/', 'datadome');
    if (!$jar->setCookie($cookie)) { // false, not an exception, for a cookie it rejects
        throw new RuntimeException('The cookie jar refused: ' . $result['code']);
    }
    // The cookie works only from the address that earned it.
    $via = $proxyUrl;
    if ($via === null && !empty($result['proxy'])) {
        // CapSkip solved through its own list. Add user:pass@ if that entry needs a login.
        $via = 'http://' . $result['proxy'];
    }
    $http = session($jar, $result['userAgent'], $via);

    return [$http, $http->get(PAGE_URL)];
}

3. Adım: 403 yeniden deneme döngüsüyle tam örnek

Son blok adımları birbirine bağlar. Onu datadome-demo.php dosyasına ekleyin ve çalışma klasöründe php datadome-demo.php komutunu çalıştırın. Her deneme yeni bir jar ve istemciyle başlar, bu yüzden her deneme yeni bir challenge taşır ve başarısız bir denemeden kalma çerez taşımaz. Bu seçim benimdir ve DataDome’a karşı denenmemiştir.

Döngü, ERROR_CAPTCHA_UNSOLVABLE gibi bir ApiException’ı ve hâlâ 403 alan bir çerezi yeniden dener. ERROR_PROXY_ adını taşıyan bir ApiException hemen yeniden fırlatılır, çünkü aynı adres aynı şekilde başarısız olurdu. dd nesnesi ya da iframe içermeyen bir 403 döngüyü durdurur. Her yeniden deneme siteye bir engelleme sayfası isteği daha yükler, bu yüzden üç deneme makul bir sınırdır.

function fetchProtected(CapSkip $solver, ?string $proxyUrl = null): array
{
    for ($attempt = 1; $attempt <= 3; $attempt++) {
        $jar = new CookieJar(); // a clean jar for every attempt
        $http = session($jar, BROWSER_UA, $proxyUrl);
        $block = $http->get(PAGE_URL); // a fresh block page, a fresh challenge
        $captchaUrl = captchaUrlFrom($block);
        if ($captchaUrl === null) {
            if ($block->getStatusCode() >= 400) {
                throw new RuntimeException('HTTP ' . $block->getStatusCode() . ' without a DataDome block page');
            }
            return [$http, $block]; // nothing to solve here
        }
        try {
            $result = solve($solver, $captchaUrl, $proxyUrl);
        } catch (ApiException $e) {
            if (str_contains($e->getMessage(), 'ERROR_PROXY_')) {
                throw $e; // the address is the problem, so a retry fails the same way
            }
            echo "attempt {$attempt}: {$e->getMessage()}\n";
            continue;
        }
        [$http, $page] = sendWithCookie($result, $jar, $proxyUrl);
        if ($page->getStatusCode() === 200) {
            return [$http, $page];
        }
        echo "attempt {$attempt}: HTTP {$page->getStatusCode()} with the cookie\n";
    }
    throw new RuntimeException('DataDome not passed in 3 attempts');
}

try {
    [$http, $page] = fetchProtected($solver); // or ($solver, 'http://user:[email protected]:8080')
    echo $page->getStatusCode(), ' ', substr((string) $page->getBody(), 0, 40), PHP_EOL;
    // Same Client, so the same jar, User-Agent and proxy.
    echo $http->get(PAGE_URL)->getStatusCode(), PHP_EOL;
} catch (CapSkipError | RuntimeException $e) {
    fwrite(STDERR, get_class($e) . ': ' . $e->getMessage() . PHP_EOL);
    exit(1);
}

Tam DataDome CAPTCHA PHP script’i mock’a karşı şunu yazdırdı:

200 <html><body><h1>Real content</h1></body>
200

Aşağıdaki her satır, tek bir ayrıntı dışında, tüm bu kombinasyonlarda aynı çıktı: Guzzle 8, cURL error 60 için ConnectException fırlatır; Guzzle 7 ise RequestException fırlatır.

Mock ayarıScript ne yaptı
Kaydırıcı, cihaz kontrolü ya da iframe engelleme sayfasıBir görev, ardından 200 ve yeniden kullanımda yine 200
İlk iki çözüm ya da üçü de reddedildiÜçüncü görevde 200 ya da RuntimeException ve çıkış kodu 1
t=bv içeren engelleme sayfasıValidationException; mock hiçbir şey almadı
Sonuç bir proxy adlandırıyor ya da kendi proxy’nizi veriyorsunuzÇerezi taşıyan her iki istek de o proxy’yi kullandı ve başarılı oldu
Çözüm ERROR_PROXY_BANNED yanıtı verdiBir görevden sonra ApiException
Domain özniteliği uyumsuz ya da düz http üzerinde SecureÜç deneme, her biri çerezle birlikte 403
Domain özniteliği olmayan çerez2. Adım bloğundaki yedekle 200, onsuz üç adet 403 denemesi
Kendi datadome çerezini ayarlayan engelleme sayfası2. Adım bloğundaki clear() ile 200, onsuz üç adet 403 denemesi
Geçici bir CA ile https sitesicurl.cainfo olmadan cURL error 60; onunla bir 200, Secure bir çerez için de

Çerezin bağlanma kuralı ve mock sitenin iki datadome çerezinden ilkini okuması, CapSkip belgelerini okuduktan sonra yazdığım kurallardır. Canlı hizmet hakkında hiçbir şey söylemezler.

Komut satırı mı, web isteği mi?

SDK bir çözüm için en fazla 300 saniye bekler. PHP kılavuzu max_execution_time varsayılanını web için 30 saniye, komut satırında ise 0 olarak verir. Mock görevi 8 saniye tuttum, bu yüzden script yaklaşık 13 saniye sürdü; her çalıştırmayı PHP 8.4.15 üzerinde ilk ifadesinden sonuncusuna kadar zamanladım; PHP 8.0.30 aynı sonuçları 0,1 saniye içinde verdi. Bu bilgisayardaki php.ini 120 diyor; 5 sınırı için onun bir kopyasını kullandım. php.exe, php.ini içindeki max_execution_time değerini yok sayar, bu yüzden bir konsol çalıştırmasını yalnızca -d bayrağı ya da set_time_limit() sınırlar; bir web sunucusunun PHP’yi çalıştırmak için kullanabileceği CGI sürümü olan php-cgi.exe ise dosyayı okur.

Nasıl çalıştıSonuç
php.exe, hiçbir şey ayarlanmadı (php.ini 120 diyor, php.exe 0 okuyor)Tamamlandı: iki kez 200 durumu, yaklaşık 13 saniye
php.exe, php.ini içinde 5 sınırıTamamlandı: sınır yok sayıldı, yaklaşık 13 saniye
php.exe -d max_execution_time=57,9 saniye sonra Fatal error
php-cgi.exe, php.ini içinde 5 sınırı7,9 saniye sonra Fatal error
php-cgi.exe -d max_execution_time=5, ilk ifade olarak set_time_limit(60) ileYaklaşık 13 saniye sonra tamamlandı
php-cgi.exe, hiçbir şey ayarlanmadı (php.ini içinden 120 okur)Yaklaşık 13 saniye sonra tamamlandı

Bunu yalnızca Windows’ta çalıştırdım; orada sınır beklemeyi de sayar, bu yüzden SDK’nın yoklama döngüsünde bloke olan bir script onu tüketir. Sınır geçtiğinde script yoklamalar arasında uyuyor olduğu için PHP, 5 değil yaklaşık 8 saniyede fatal error verdi. Script çerezi okumadan ölür ve CapSkip çözümü zaten harcamıştır. Bu döngüyü bir konsoldan, zamanlanmış bir görevden ya da bir kuyruk worker’ından çalıştırın; worker biçimi için bkz. Laravel kuyruk rehberi. Bir web isteğinin içinde, önce set_time_limit() fonksiyonunu bir çözümün alabileceğinden daha uzun bir süreyle çağırın.

Yaygın hatalar

HataNedenDüzeltme
cURL error 60: SSL certificate problemPHP for Windows bir CA bundle ile gelmezcurl.se adresinden cacert.pem dosyasını indirin ve php.ini içinde curl.cainfo ayarını ona yöneltin
ValidationExceptionSDK girdiyi reddetti: captcha-delivery.com dışında bir adres, t=bv içeren bir engelleme sayfası, http ya da https olmayan bir sayfa URL’si ya da Windows için standart Chrome olmayan bir userAgent. İleti hangisi olduğunu belirtirAdresi yeni bir engelleme sayfasından yeniden oluşturun, yasaklıysa proxy’yi değiştirin, userAgent değerini kaldırın
ApiException ile ERROR_PROXY_BANNED, ERROR_PROXY_CONNECT_REFUSED veya ERROR_WRONG_USERAGENTIP adresi yasaklı, CapSkip proxy üzerinden bağlanamıyor (yeniden üretilmedi) ya da userAgent değeriniz, CapSkip’in çözümde kullandığı tarayıcıdan farklı bir Chrome ana sürümüne sahipProxy’yi değiştirin veya test edin ya da userAgent göndermeyin
TimeoutException veya NetworkException300 saniyelik bekleme doldu ya da verdiğiniz host ve portta yanıt veren yokCapSkip’i başlatın ve host ile portunuzu bağlantı ayarlarıyla karşılaştırın
Jar çerezi tutsa da çerezle birlikte HTTP 403Domain özniteliği istediğiniz host ile eşleşmiyor ya da Secure özniteliği ayarlı ve URL httpJar’ı yazdırın ve PAGE_URL ile karşılaştırın
Fatal error: Maximum execution time of 30 seconds exceededBir web isteği, SDK’nın yoklaması sırasında PHP’nin zaman sınırına takıldı; sayı sizin sınırınızdırScript’i bir konsoldan ya da bir worker’dan çalıştırın

FAQ

Sunucudaki CapSkip’e karşı PHP’de bir DataDome CAPTCHA’sı çalıştırabilir miyim?

Evet, Server modu tam olarak bunun için var. CapSkip o zaman ağınızdan ya da genel bir IP’den (statik bir IP önerilir) gelen çağrıları kabul eder ve constructor içinde yalnızca host ile portu değiştirirsiniz. Windows makinesi sizin kalır ve çözüm başına ücretlendirme yoktur. Server modu başladığında portu her adrese açar; bu yüzden beklemediğiniz makineler için bir Windows Firewall engelleme kuralı ekleyerek onu özel tutun. Çözüm sunucuda çalışır, bu yüzden çerez onun IP adresine aittir: Guzzle isteklerinizi o makineden gönderin ya da çözüme ve istemciye aynı proxy’yi verin.

Neden Cookie başlığı değil de cookie jar?

Başlık da işe yarar. SDK’nın kendi datadome.php örneği çerez değerini düz bir cURL handle’ından bir başlıkta gönderir; onu çalıştırmadım. Jar; Domain ve Path özniteliklerine göre eşleştirmeyi, süre dolumunu ve yerine geçen çerezi ekler, bunun bedeli 2. Adım’daki sessiz hatalardır.

Proxy gerekli mi?

İlk test için hayır. CapSkip belgeleri çerezin yalnızca onu kazanan IP adresinden çalıştığını söyler; bu yüzden yüksek hacimde görev başına bir sticky proxy isteyeceksiniz. Onları döndürmek için bkz. Proxy döndürme kılavuzu.

Kısa özet

PHP ile DataDome CAPTCHA çözmek; kimlik başına bir Guzzle istemcisi, http_errors kapalıyken okunan bir engelleme sayfası, bir dataDome() çağrısı ve dönen çerezin istemcinin jar’ında durması demektir. Kendi çalıştırdığınız bir yerel captcha çözücü ile yeniden denemek hiçbir şeye mal olmaz, bu yüzden döngünün eski bir challenge’ı yeniden kullanmak yerine yenisini istemesine izin verin. Diğer CAPTCHA türlerinin SDK çağrıları için bkz. PHP CAPTCHA çözücü sayfası.