Ağ İsteklerini Yakalamadan Selenium ile ALTCHA Nasıl Çözülür

solve altcha in selenium - How to Solve ALTCHA in Selenium Without Network Interception

Selenium ile ALTCHA çözmek için widget’ın işini bitirmesini asla beklemezsiniz. ALTCHA bir tanıma yöntemi değil, iş kanıtı yöntemidir: site bir hash problemi verir ve istemci, geçmesine izin verilmeden önce onu sağlayan sayıyı bulmak zorundadır. Bakılacak da tıklanacak da bir şey yoktur, dolayısıyla tarayıcı akışın geri kalanı için oradadır, CAPTCHA için değil. Selenium’da Playwright’in response bekleme çağrısının tek satırlık bir karşılığı yoktur ve bu, o eksikliğin size hiçbir şeye mal olmadığı ender durumlardan biridir: widget kendi challenge uç noktasını bir attribute içinde adlandırır ve çözücünün ihtiyaç duyduğu tek şey de budur.

Neye ihtiyacınız var

  • Bir Windows makinesinde çalışan CapSkip 1.2.6 veya üzeri. ALTCHA desteği o sürümle geldi, dolayısıyla daha eski bir yapıda çağrılacak bir metot yok.
  • Python 3.10 veya daha yenisi; Selenium ve CapSkip paketleri kurulu olmalı, ayrıca uyumlu bir tarayıcı gerekir.
  • Widget’ın üzerinde bulunduğu sayfanın URL’si. Sitekey yoktur, çünkü ALTCHA’da sitekey diye bir şey bulunmaz.
  • Çözücü için bir adres. Local modu yalnızca o cihaz için 127.0.0.1 üzerinden yanıt verir; Server modu ise ağ adresinizi veya genel IP’nizi dinler, böylece bir Grid node’u, bir CI runner’ı ya da başka bir makine aynı API üzerinden ona ulaşabilir. Hangisinin geçerli olduğunu 5. adım anlatıyor; ikisi de şurada ayarlanır: bağlantı ayarları.
# pip install selenium capskip
pip install selenium capskip

1. Adım: challenge uç noktasını widget’tan okuyun

Widget elementi, challenge’ını isteyeceği uç noktayı kendi üzerinde adlandırır. Bunu hangi attribute’un tuttuğu widget kuşağına göre değişir, o yüzden varsaymak yerine sayfa kaynağını okuyun.

Widget nesliChallenge’ı adlandıran öznitelik
v1 ve v2Bir endpoint için challengeurl, challenge satır içi olduğunda ayrı bir challengejson özniteliği
v3 ve sonrasıchallenge; bu tek attribute ya bir URL ya da challenge verisinin kendisini alır

Burada iki Selenium alışkanlığı önemli ve ikisi de aşikâr değil. Selenium bir elementi otomatik olarak beklemez; sayfa widget’ı DOM’a koymadan önce onu sorgularsanız yeniden deneme değil NoSuchElementException alırsınız. Sıradan attribute çağrısı ise melez bir çağrıdır: önce o addaki JavaScript property’sini dener ve ancak sonra markup’a düşer. Bir web component kendi attribute’ları için property tanımlar, dolayısıyla geri gelen değer okuyabildiğiniz HTML’e değil widget sürümüne bağlıdır. DOM attribute çağrısında böyle bir geri düşme yoktur ve markup ne diyorsa onu döndürür; istediğiniz de budur. Python API referansı aradaki farkı şurada açıklıyor: WebElement sayfası.

# pip install selenium capskip
from urllib.parse import urljoin
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

PAGE_URL = "https://example.com/signup"

driver = webdriver.Chrome()
driver.get(PAGE_URL)

# Nothing auto-waits in Selenium, so wait for the element.
widget = WebDriverWait(driver, 15).until(
    EC.presence_of_element_located((By.CSS_SELECTOR, "altcha-widget"))
)

# v1 and v2 use challengeurl or challengejson; v3 uses challenge.
value = (widget.get_dom_attribute("challengeurl")
         or widget.get_dom_attribute("challengejson")
         or widget.get_dom_attribute("challenge"))
if value is None:
    raise SystemExit("no challenge attribute on the widget")

Bu üç attribute’tan ikisi size bir işaretçi değil belgenin kendisini verir, o yüzden elinize geçeni URL gibi kullanmadan önce test edin. URL’nin kendine ait bir adımı daha vardır: markup genellikle baştaki bir eğik çizgi ve bir klasör adı gibi aynı kaynaktan bir yol taşır ve bir HTTP istemcisi bunu tek başına çekemez. Önce sayfa URL’sine göre çözümleyin.

import json

challenge, endpoint = None, None
if value.lstrip().startswith("{"):
    # challengejson, or a v3 challenge holding the data inline.
    challenge = json.loads(value)
else:
    # A path in the markup becomes an absolute URL here.
    endpoint = urljoin(PAGE_URL, value)

Widget’ın type attribute’undaki üç etkileşim stili, yani native, checkbox ve switch, tamamen görseldir ve asla çözücüye ulaşmaz; ayrı bir attribute olan display de aynı şekilde görseldir. ALTCHA hepsini şurada belgeliyor: kendi widget rehberi.

2. Adım: challenge’ı tarayıcının oturumuyla çekin

Uç nokta herkese açıksa bu adımı atlayabilirsiniz ve pek çoğu açıktır. URL’yi CapSkip’e verin, challenge’ı kendisi çeksin. Bu adımın var olma nedeni, bazı sitelerin challenge’ı onu isteyen oturuma bağlamasıdır; çözücü ise sizin makinenizden çerezsiz istek yapar, dolayısıyla geri aldığı şey kimseye ait değildir. Sonuçta cevap doğru olur ve site yine de kabul etmez.

Çözüm, challenge’ı tarayıcının oturumu içinde çekmek ve işaretçi yerine belgenin kendisini geçirmektir. Selenium’un halihazırda tuttuğu çerezleri bir HTTP istemcisine kopyalayın, aynı user agent’ı taşıyın ve isteği kendiniz yapın.

import requests

if endpoint:
    session = requests.Session()
    for cookie in driver.get_cookies():
        session.cookies.set(cookie["name"], cookie["value"],
                            domain=cookie["domain"])
    session.headers["User-Agent"] = driver.execute_script(
        "return navigator.userAgent")

    # Referer matters on sites that check where the ask came from.
    challenge = session.get(endpoint,
                            headers={"Referer": PAGE_URL}).json()

O satırdaki bir JSON çözümleme hatası, işinize yarayan hatadır. Uç noktanın HTML ile, genellikle bir giriş sayfası veya bir onay duvarıyla yanıt verdiği anlamına gelir; bu da isteğin yabancı olarak gittiğini ve bu adımın doğru karar olduğunu söyler. Çözümü, çekme işleminin hemen ardından başlatın. Pencere kısadır ve saat, siz onu kullanmaya sıra geldiğinde değil site challenge’ı yayınladığında başlar.

3. Adım: challenge’ı tek ALTCHA metoduna verin

Tek bir metot ve tam anlatımı şu sayfada: ALTCHA çözücü sayfası. Bu metot challenge’ı iki biçimde de kabul eder. Belgeyi geçirin, hiç istek gitmez. Uç noktayı geçirin, çözücü onu kendisi çeker; iş kuyrukta ilkinin süresi dolacak kadar beklediyse yenisini çeker.

from capskip import CapSkip

solver = CapSkip(host="127.0.0.1", port=8080)

# The document from step 2, so nothing is fetched twice.
result = solver.altcha(url=PAGE_URL, challenge_json=challenge)

# Or hand over the endpoint and let CapSkip fetch it.
# result = solver.altcha(url=PAGE_URL, challenge_url=endpoint)

print(result["token"])    # base64 payload for the form field
print(result["number"])   # the counter that satisfied it

O sonuçtaki iki anahtar yalnızca ALTCHA için var. token anahtarı formun istediği base64 veriyi, number anahtarı ise challenge’ı çözen sayacı tutar. code anahtarı token ile aynı dizeyi taşır, yani ikisi de iş görür; ama token anahtarı, gideceği alanın adını taşıyor. GeeTest anahtarları ve Turnstile kullanıcı ajanı burada yok.

Çözücünün kapsadığı algoritmalar

Eski şema SHA-1, SHA-256, SHA-384 ve SHA-512 ile, iş kanıtı v2 ise PBKDF2 ve yinelemeli SHA ile karşılanır. PBKDF2, ALTCHA’nın kendi önerdiği varsayılandır, dolayısıyla canlı sitelerin büyük çoğunluğu bunu kullanır. Argon2id ve scrypt istisnadır ve denenmek yerine reddedilir: bunlardan birini isteyen bir challenge yaklaşık saniyenin üçte biri içinde ERROR_CAPTCHA_UNSOLVABLE olarak geri döner ve asla yeniden denenmez, çünkü bellek yoğun bir fonksiyon yeniden denemeyle düzelecek bir şey değildir. Bu türde o sonuç, okunamayan bir görüntüyü değil algoritmayı işaret eder ve bu hata kodu şurada anlatılıyor: kendine ait bir rehber.

4. Adım: token’ı gizli alana yazın

Widget, payload’ını adı kendi name attribute’undan gelen bir gizli input içinde gönderir; bu adın varsayılanı altcha’dır. Challenge attribute’unu okuduğunuz gibi bunu da varsaymak yerine okuyun. Bu çalıştırmada widget’ı hiçbir şey doğrulamadı, dolayısıyla o alanı da hiçbir şey doldurmadı; onu siz dolduruyorsunuz.

Selenium bir WebElement’i doğrudan bir script’e geçirebilir; bu da doğru formu bulmanın en temiz yolunu düğmenin kendisi yapar. Gönder düğmesini verin ve onun form property’sini okuyun: bir kayıt sayfası çoğu zaman birden fazla form taşır ve düğme başka bir forma aitken alanı sayfadaki ilk forma eklemeniz, sunucunun hiçbir zaman bir değer görmemesi demektir.

SET_FIELD = """
const form = arguments[0].form || document.querySelector('form');
let field = form.querySelector('[name="' + arguments[1] + '"]');
if (!field) {
    field = document.createElement('input');
    field.type = 'hidden';
    field.name = arguments[1];
    form.appendChild(field);
}
field.value = arguments[2];
"""

button = driver.find_element(By.CSS_SELECTOR, "button[type=submit]")
field_name = widget.get_dom_attribute("name") or "altcha"

driver.execute_script(SET_FIELD, button, field_name, result["token"])
button.click()

Dizeyi olduğu gibi geçirin. Token, alanları sunucunun HMAC imzası kapsamında olan bir JSON belgesinin base64 hâlidir; bu yüzden düzeltme gibi görünen her şey onu bozar: boşluk kırpmak, çözüp yeniden kodlamak ya da JSON’u anahtarlar başka sırada olacak şekilde yeniden kurmak. Bazı entegrasyonlar veriyi bir form alanı yerine bir JSON gövde alanından okur ve widget bunu bir çerezle teslim edecek şekilde de yapılandırılabilir; o yüzden sayfanın kendi gönderiminin ne yolladığına bakın ve aynısını yapın.

Gönder düğmesi hiçbir zaman tıklanabilir hale gelmiyorsa sayfa, düğmeyi alanın bir değer tutmasına değil kendi script’inin widget’ın başarılı olduğunu duymasına bağlıyordur. Burada dürüst iki cevap var. Sayfanın neyi dinlediğini bulup onu karşılayın ya da düğmeyi atlayıp formun alanlarını tarayıcının zaten sahip olduğu çerezlerle doğrudan gönderin; bu genelde daha kısa ve her zaman daha kararlıdır.

5. Adım: script’iniz yer değiştirdiğinde çözücü nerede çalışır

Örnekler 127.0.0.1 kullanıyor, çünkü script’iniz ile CapSkip aynı makineyi paylaşırken doğru olan budur. Çözücüyü tarayıcı ya da sayfa değil, sizin Python kodunuz çağırır; dolayısıyla adres, Chrome’un değil test sürecinin nerede çalıştığına bağlıdır. Selenium bunu ters anlamayı kolaylaştırır, çünkü tarayıcı zaten çok sık başka bir yerdedir.

Python sürecinin çalıştığı yerHangi bağlantı modu
CapSkip makinesinde, yerel bir tarayıcı sürerekLocal modu. 127.0.0.1 gerçekten doğru
Kendi makinenizde, uzak bir WebDriver veya bir Grid node’u sürerkenYerel mod. Tarayıcı çözücüyle hiç konuşmaz
Aynı ağdaki başka bir makinedeServer modu, çözücü makinenin özel adresinde
Bir container’da, bir CI runner’da veya bir VPS’teStatik bir genel IP ve bir güvenlik duvarı kuralı ile Server modu

CapSkip’i Server moduna geçirin; loopback yerine ağ adresinizi veya genel IP’nizi dinler ve runner, yerelde kullanacağı aynı HTTP API üzerinden bağlanır. Rota internetten geçiyorsa statik bir genel IP önerilir ve yalnızca beklediğiniz adreslere izin veren bir güvenlik duvarı kuralı eklenmelidir. Server modu çözücünün nerede dinlediğinden başka hiçbir şeyi değiştirmez: donanım yine sizin donanımınızdır ve çözüm başına ücretlendirme yine yoktur. Testlerin kendisini bir Grid üzerinde çalıştırmanın kendine ait tuzakları var; onlar ayrıca şurada anlatılıyor: Selenium Grid rehberi.

Tek bir script’in her iki yerde de çalışması için host ve port değerlerini ortamdan okuyun. İstemci CAPSKIP_HOST ve CAPSKIP_PORT değerlerini kendi başına okumaz; bu yüzden, aşağıdaki tam örnekte olduğu gibi, bunları yapıcıya geçirin.

Proxy’lerle ilgili ALTCHA’ya özgü bir not. Burada proxy destekleniyor, ama yalnızca challenge’ın indirilmesi için kullanılıyor. Üzerinden yönlendirilecek bir tarayıcı oturumu yok, yani iş kanıtının kendisine hiçbir etkisi olmuyor; challenge belgesini satır içi geçtiğinizde ise hiçbir işe yaramıyor.

Tam çalışan örnek

import json, os, requests
from urllib.parse import urljoin
from capskip import CapSkip, ApiException, NetworkException, TimeoutException
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

PAGE_URL = "https://example.com/signup"

SET_FIELD = """
const form = arguments[0].form || document.querySelector('form');
let field = form.querySelector('[name="' + arguments[1] + '"]');
if (!field) {
    field = document.createElement('input');
    field.type = 'hidden';
    field.name = arguments[1];
    form.appendChild(field);
}
field.value = arguments[2];
"""

solver = CapSkip(
    host=os.environ.get("CAPSKIP_HOST", "127.0.0.1"),
    port=int(os.environ.get("CAPSKIP_PORT", 8080)),
)

driver = webdriver.Chrome()
try:
    driver.get(PAGE_URL)
    widget = WebDriverWait(driver, 15).until(
        EC.presence_of_element_located((By.CSS_SELECTOR, "altcha-widget"))
    )
    value = (widget.get_dom_attribute("challengeurl")
             or widget.get_dom_attribute("challengejson")
             or widget.get_dom_attribute("challenge"))
    if value is None:
        raise SystemExit("no challenge attribute on the widget")

    if value.lstrip().startswith("{"):
        challenge = json.loads(value)
    else:
        endpoint = urljoin(PAGE_URL, value)
        session = requests.Session()
        for cookie in driver.get_cookies():
            session.cookies.set(cookie["name"], cookie["value"],
                                domain=cookie["domain"])
        session.headers["User-Agent"] = driver.execute_script(
            "return navigator.userAgent")
        challenge = session.get(endpoint,
                                headers={"Referer": PAGE_URL}).json()

    try:
        result = solver.altcha(url=PAGE_URL, challenge_json=challenge)
    except ApiException:
        raise SystemExit("refused: Argon2id, scrypt, or an expired challenge")
    except NetworkException:
        raise SystemExit("solver unreachable: check host and connection mode")
    except TimeoutException:
        raise SystemExit("no answer inside defaultTimeout")

    button = driver.find_element(By.CSS_SELECTOR, "button[type=submit]")
    field_name = widget.get_dom_attribute("name") or "altcha"
    driver.find_element(By.NAME, "email").send_keys("[email protected]")
    driver.execute_script(SET_FIELD, button, field_name, result["token"])
    button.click()
finally:
    driver.quit()

Dört istisnanın hepsi CapSkipError’dan türer, dolayısıyla onu yakalamak SDK’nın verebileceği her hatayı tek bir blokta karşılar. Yanıt farklılaştığında yukarıdaki gibi özel olanları, farklılaşmadığında CapSkipError’ı yakalayın. Hangi TimeoutException’ı yakaladığınıza dikkat edin: CapSkip’inki ile Selenium’un kendi sınıfı aynı adı taşır, ikisi de kapsamdaysa birini takma adla import edin.

Diğer tipler aynı istemciden aynı şekilde çalışıyor. reCAPTCHA ve Turnstile bir sitekey ile bir sayfa URL’si alır, GeeTest bir gt değeri, bir challenge ve sayfa URL’sini alır, görsel çözme ise bir dosya yolu, bir URL veya base64 alır. Paketin sunduğu bütün metotlar şurada listeleniyor: Python CAPTCHA çözücü sayfası, tarayıcı tarafının tamamı ise şurada: Selenium CAPTCHA çözücü sayfası.

Sık görülen hatalar ve anlamları

GördüğünüzNedenDüzeltme
Widget’ta NoSuchElementExceptionElement henüz DOM’a eklenmemişti; markup’ı script oluşturduğunda böyle olur ve Selenium kendiliğinden beklemezAramayı, elementin sayfada bulunmasını bekleyen bir explicit wait içine alın
Üç attribute okumasının üçü de None dönüyorWidget tamamen JavaScript’ten yapılandırılmış, dolayısıyla üç addan hiçbiri markup’ta yokYapılandırmayı sayfanın kendi script’inden okuyun ya da ağ panelinde görebildiğiniz uç noktayı kullanın
Sayfa kaynağında açıkça bir değer varken boş dönen attributeSıradan attribute çağrısı, web component’in tanımladığı ve boş olan bir property döndürdüMarkup’ı okuyan ve property’ye düşmeyen DOM attribute çağrısını kullanın
HTTP istemcisinden, eğik çizgiyle başlayan bir değeri gösteren MissingSchemaMarkup aynı kaynaktan bir yol tutuyor ve DOM attribute çağrısı onu yazıldığı gibi döndürüyorÇekmeden ve çözücüye geçirmeden önce sayfa URL’sine göre çözümleyin
Attribute değeri süslü parantezle başlıyorBir challengejson attribute’u ya da URL yerine belgeyi satır içinde taşıyan bir v3 widget’ıJSON olarak ayrıştırın ve satır içi challenge olarak geçirin
Uç noktayı çekerken JSON çözümleme hatasıUç nokta HTML ile yanıt verdi, yani istek tarayıcının oturumu olmadan ulaştıÇekmeden önce çerezleri ve user agent’ı kopyalayın
Az önce yakaladığınız bir challenge’da ApiExceptionSatır içi challenge’ın süresi çoktan dolmuştu, çözücü de hashlemek yerine onu reddettiÇekme ve çözmeyi tek nefeste yapın ya da uç nokta oturuma bağlı değilse, çözücünün yeniden çekebilmesi için uç noktayı geçirin
Yaklaşık saniyenin üçte biri içinde, bir ApiException içinde ERROR_CAPTCHA_UNSOLVABLEChallenge, Argon2id veya scrypt kullanıyorYeniden denenecek bir şey yok. Bu ikisi tasarım gereği reddedilir
Günlüğünüzün çözüldüğünü gösterdiği bir token’la, çıplak bir doğrulama hatasıChallenge’ın süresi, çözüm ile gönderim arasında dolduArada yavaş hiçbir şey olmadan yakalayın, çözün ve gönderin
İlk çözümde bir NetworkExceptionCapSkip çalışmıyor ya da script bir konteynerde ve loopback’e yönlendirilmişCapSkip’i başlatın, sonra Yerel mod ile Sunucu modu arasında seçim yapın
120 saniyeyi işaret eden bir TimeoutExceptionÇözücü, ALTCHA’nın daha uzun reCAPTCHA değeri yerine kullandığı varsayılan yoklama zaman aşımı içinde yanıt vermediÇözücünün çalıştığını ve dolu olmadığını kontrol edin. Tavanı yükseltmek aynı cevabı yalnızca geciktirir
Form gönderiliyor ama sunucu eksik bir altcha değeri bildiriyorGizli alan, sayfadaki başka bir forma eklendiÖrnekteki gibi form property’sini gönder düğmesinden okuyun
Gönder düğmesi devre dışı kalıyorSayfa bunu, kendi script’inin widget’ın başarılı olduğunu görmesine bağlıyorForm alanlarını doğrudan gönderin ya da sayfanın dinlediği her neyse onu karşılayın
Çağrıda bir ValidationExceptionChallenge seçeneklerinden hiçbiri verilmedi ya da ALTCHA’nın almadığı bir seçenek geçildiUç noktayı ya da belgeyi geçin ve başka her şeyi bırakın

FAQ

ALTCHA’yı çözmek için tarayıcıya ihtiyacım var mı?

Hayır. ALTCHA bir hash problemidir, dolayısıyla cevabı CPU verir ve cevaba hiçbir tarayıcı karışmaz. Selenium’u açmanızın tek nedeni CAPTCHA idiyse kapatın: challenge’ı bir HTTP istemcisiyle çekip token’ı geri gönderin. Bunu baştan sona düz Python rehberi anlatıyor. Selenium, akışın geri kalanı gerçek bir sayfaya ihtiyaç duyduğunda hak ettiği yeri bulur: çerez kuran bir giriş, çok adımlı bir form ya da yalnızca script çalıştıktan sonra var olan markup gibi.

Widget attribute’u neden boş dönüyor?

Çünkü sıradan attribute çağrısı melez bir çağrıdır. Element bir property tanımlıyorsa onu döndürür ve ancak sonra markup’a düşer; bir web component de kendi attribute’ları için property tanımladığından, boş bir property sayfa kaynağında gördüğünüz değere üstün gelir. DOM attribute çağrısında böyle bir geri düşme yoktur ve markup ne diyorsa onu döndürür. Diğer neden de önünüzdeki kuşak için yanlış adı okuyor olmanızdır, o yüzden attribute yok demeden önce üçünü de deneyin.

Bir CI runner’ı veya bir container çözücüye ulaşabilir mi?

Evet, Server modu üzerinden. CapSkip’i bağlantı ayarlarından loopback yerine ağ adresinize veya genel IP’nize alın, sonra host ortam değişkenini oraya yöneltin. Runner ile çözücü o zaman tek makinedeymiş gibi aynı HTTP API’yi konuşur. Rota internetten geçiyorsa statik bir genel IP kullanın ve bir güvenlik duvarı kuralıyla kısıtlayın. Uzak bir Grid node’u ayrı bir konudur ve genellikle hiçbir şey gerektirmez, çünkü tarayıcıyı node çalıştırır; çözücüyü çağıran script’iniz ise başlattığınız yerde kalır.

Selenium’un kendi zaman aşımları çözümü yarıda keser mi?

Hayır, çünkü çözüm bir WebDriver komutu değildir. Implicit wait, sayfa yükleme zaman aşımı ve script zaman aşımı; hepsi Selenium’un tarayıcıya gönderdiği çağrıları kapsar, sizin çözücü çağrınız ise bunların ikisinin arasında duran sıradan Python kodudur. Geçerli olan tavan, istemcinin 120 saniyelik varsayılan yoklama zaman aşımıdır; ALTCHA bunu daha uzun reCAPTCHA değeri yerine kullanır, çünkü bu iş bir tarayıcı oturumu değil CPU işidir. Bunun yerine testin tamamını saran bir sınıra dikkat edin: test başına bir eklenti zaman aşımı veya bir CI iş sınırı gibi.

Kısa özet

Widget’ı bekleyin, challenge attribute’unu DOM attribute çağrısıyla okuyun, bir yolu sayfa URL’sine göre çözümleyin ve site challenge’ı bir oturuma bağlıyorsa o uç noktayı tarayıcının oturumu içinde çekin. Belgeyi ya da URL’yi sayfa URL’si ile birlikte tek ALTCHA metoduna geçirin, sonra token’ı gönder düğmesinin ait olduğu formda, adı widget’ın kendi name attribute’undan gelen gizli alana yazın. Token’a yolda dokunmayın. Çekme, çözme ve gönderme işlemlerini birbirine yakın tutun, çünkü pencere iki dakika içinde kapanabilir ve süresi dolmuş bir challenge tıpatıp yanlış cevap gibi görünür.

Yeniden denemeyi nasıl tasarlayacağınızı değiştiren son bir nokta daha var. Bu yolun sonundaki sınırsız captcha çözücü iş kanıtını zaten sahip olduğunuz donanımda hesapladığı için, çöpe giden bir challenge size yalnızca kendi CPU’nuzdan birkaç milisaniyeye mal olur, başka hiçbir şeye değil; bu yüzden yenisi için sayfayı yeniden yüklemek, bayat bir token’ı uzun bir test boyunca idare etmeye çalışmaktan her zaman daha ucuzdur.