Bir Sayfadaki CAPTCHA Türü Nasıl Algılanır ve Çözülür (Python)

Bir sayfadaki CAPTCHA türünü algılamak için sayfanın HTML’inde widget’ın script’ini ve kapsayıcısını okuyun, sitekey’i bunlardan çıkarın ve aileyi onu çözen CapSkip metoduna eşleyin. Bir reCAPTCHA v2 kutusu, bir v3 script’i ve bir Turnstile widget’ı farklı seçeneklerle üç ayrı çağrı gerektirir; yanlış bir tahmin, bütün bir çözümü boşa harcar ve size kullanamayacağınız bir token verir. Bu rehber, türü, metodu ve argümanları döndüren ya da ayırt edemediğini açıkça söyleyen, yaklaşık 175 satırlık bir Python algılayıcısı oluşturur. CapSkip kendi Windows bilgisayarınızda çalışır ve çözüm başına ücret yoktur; bu yüzden yüz sayfayı denetleyen bir script ekstra hiçbir şeye mal olmaz.
Neye ihtiyacınız var
- Gerekli çözücüler açık hâlde bir Windows bilgisayarında çalışan CapSkip. İlk başlatmayı kurulum kılavuzu adım adım anlatır.
- Python 3.10 veya üzeri. Aşağıdaki kod Python 3.14.0 ve 3.11.7 üzerinde, requests 2.33.0 ve 2.34.2 ile capskip 1.3.0 kullanılarak çalıştırıldı.
- Bir PowerShell istemi. Komutlar PowerShell sözdizimini kullanır.
- Sahibi olduğunuz ya da otomatikleştirmenize izin verilen, üzerinde CAPTCHA bulunan bir sayfa.
- Çözücü için bir adres. Local modunda CapSkip 127.0.0.1 üzerinden yalnızca o cihaza yanıt verir. Server modunda ağ adresinizi veya genel IP’nizi dinler, böylece başka bir bilgisayardaki, bir VPS’teki veya barındırılan bir platformdaki script API üzerinden ona erişebilir. İkisi de şurada ayarlanır: bağlantı ayarları.
Nasıl test edildi: algılayıcı 66 kayıtlı sayfada çalıştırıldı ve her sayfa beklenen sonucu verdi. Bunların 59’u elle hazırlandı (yaklaşık 40’ı sağlayıcıların belgelerini izler, gerisi benim uydurduğum uç durumlardır) ve 7’si 29 Ekim 2026’da alınmış herkese açık sayfa anlık görüntüleridir. Çözümler, uygulamanın girdi denetimlerini kopyalayan bir mock CapSkip’e karşı gerçek capskip paketi üzerinden çalıştırıldı. Gerçek uygulama çalışmıyordu, bu yüzden aşağıdaki hiçbir sayı bir çözüm süresi değildir.
CAPTCHA türünü neden çözmeden önce algılarsınız
CapSkip’in her aile için farklı bir metodu vardır: SDK’da recaptcha, turnstile, geetest, altcha, capy, captchafox, friendly_captcha ve görseller için normal; ayrıca HTTP API’de, SDK’nın henüz sarmalamadığı hcaptcha. reCAPTCHA içinde seçenekler de önemlidir. Invisible, Enterprise ve v3 aynı çağrıdaki bayraklardır. Yanlış olanları gönderirseniz en iyi ihtimalle iş başarısız olur, en kötü ihtimalle site nedenini göstermeden bir token reddeder. Yanlış bir reCAPTCHA sürümünde gerçek uygulamanın bu ikisinden hangisini yaptığını kontrol edemedim. Her iki durumda da çözüm harcanmış olur.
Diğer hatalar hızlı ve gürültülü biçimde ortaya çıkar. reCAPTCHA’yı açıkça (explicit) render eden bir sayfadan sitekey okumak explicit kelimesini verir ve CapSkip bunu ERROR_GOOGLEKEY ile reddeder. ERROR_GOOGLEKEY ve ERROR_PAGEURL kılavuzu bu hatayı öbür taraftan ele alır. CAPTCHA türünü güvenilir biçimde algılamak için algılayıcının sinyalleri sabit bir sırayla denetlemesi gerekir ve bu sıra iki tür hatadan da kaçınır. Algılayıcı, birden fazla siteyle karşılaşan bir scraper’dan önce yazılacak ilk şeydir. Her türün ne olduğuna dair arka plan için CAPTCHA nedir yazısı aileleri kodsuz açıklar.
1. Adım: kurun ve sayfayı çekin
Boş bir klasörde sanal ortam oluşturun ve iki paketi kurun. Komutlar ortamın kendi Python’unu yolla çağırır, bu yüzden onu etkinleştirmeniz gerekmez.
py -m venv .venv .\.venv\Scripts\python.exe -m pip install capskip requests
Metinden CAPTCHA türünü algılamak için algılayıcının yalnızca iki girdiye ihtiyacı vardır: sayfanın HTML’i ve adresi. Sayfayı bir requests Session ile çekin ve o Session’ı saklayın, çünkü görsel CAPTCHA adımı resmi daha sonra aynı çerezlerle indirir. Düz bir requests çekimi, sunucunun JavaScript çalışmadan önce gönderdiğini gösterir. Google’ın reCAPTCHA demo sayfaları, Cloudflare’in Turnstile demosu ve hCaptcha demosu widget’larını bu HTML’de taşır. Widget’ı JavaScript ile ekleyen sayfalar taşımaz ve “HTML hiçbir şey söylemediğinde” bölümü bu durumu ele alır.
2. Adım: yardımcıları yazın
Buradan 9. Adım’a kadar her şeyi, blok blok art arda, detect.py adlı tek bir dosyaya kaydedin. İlk blok gerekenleri import eder ve dört küçük okuyucu yazar. Her biri hiçbir şey eşleşmediğinde None döndürür, bu yüzden eksik bir öznitelik hiçbir zaman hata fırlatmaz.
import base64, html as htmllib, re, time
from types import SimpleNamespace as NS
from urllib.parse import urljoin
import requests
SITEKEY_JS = r"sitekey['\"]?\s*:\s*['\"]([^'\"]+)" # sitekey: '...' inside a render() call
def grab(pattern, text): # first capture group that matched, or None
m = re.search(pattern, text or "", re.I | re.S)
return next(g for g in m.groups() if g is not None) if m else None
def tag(cls, h): # opening tag with CSS class cls (and not g-recaptcha-response)
pat = r'(<\w+[^>]*\sclass\s*=\s*["\'][^"\']*(?<![\w-])%s(?![\w-])[^"\']*["\'][^>]*>)'
return grab(pat % cls, h)
def attr(t, name): # attribute value from a tag string, either quote style, HTML-unescaped
v = grab(r'\s%s\s*=\s*(?:"([^"]*)"|\'([^\']*)\')' % name, t)
return None if v is None else htmllib.unescape(v)
def render_opts(call, js): # first 400 characters of the options in call(el, {...})
return grab(call + r"\([^,]+,\s*\{(.{0,400})", js)grab fonksiyonu eşleşen ilk yakalama grubunu döndürür. tag fonksiyonu bir açılış etiketini CSS sınıfına göre bulur ve adı yalnızca daha uzun bir sınıfın içinde geçen sınıfı reddeder; bu yüzden cf-turnstile-response adlı bir sınıf bir Turnstile widget’ı değildir. attr fonksiyonu her iki tırnak biçimiyle yazılmış bir özniteliği okur ve HTML varlıklarını çözer; bu, bir özniteliğin içinde tutulan challenge için önemlidir. Tırnaksız öznitelikler okunmaz.
İkinci blok bir sayfayı denetimlerin paylaşabileceği bir şeye dönüştürür. Yorumları ve style bloklarını ayıklar, böylece bir widget sınıfından söz eden bir CSS kuralı eşleşemez; script adreslerini ve satır içi JavaScript’i ayrı ayrı toplar. Son iki yardımcı olası iki yanıtı oluşturur: bir eşleşme (hit) ya da bir needs_browser yanıtı.
def parse(html, url):
h = re.sub(r"<!--.*?-->|<style\b.*?</style>", "", html, flags=re.S | re.I)
srcs = " ".join(re.findall(r'<script\b[^>]*?\ssrc\s*=\s*["\']([^"\']+)', h, re.I))
js = "\n".join(re.findall(r"<script\b[^>]*>(.*?)</script>", h, re.S | re.I))
return NS(h=h, srcs=htmllib.unescape(srcs), js=js, url=url)
def hit(kind, method, **args):
return {"type": kind, "method": method, "args": args}
def browser(kind, method, p, why): # the static HTML is not enough, so do not guess
return {"type": kind, "method": method, "args": {"url": p.url}, "needs_browser": why}3. Adım: hCaptcha ve CapSkip’in çözemedikleri
Doğru yanıtın hayır olduğu sayfalarla başlayın. CapSkip FunCaptcha veya Arkose’u çözmez; GeeTest v4 de v3 için olan GeeTest metodunun dışındadır. Algılayıcı bunları atlasaydı, Arkose kutusu olan bir sayfa sonraki denetime düşer ve olmadığı bir şey olarak dönebilirdi. Bu yüzden bu denetim ilk çalışır ve metodu olmayan bir sonuç döndürür.
Aynı blok, Cloudflare’in tam sayfa denetimini, yani “Just a moment” ara sayfasını da ele alır. Bu sayfa bir Turnstile widget’ı değildir. turnstile metodu ondan data ve pagedata olmak üzere iki değer ister ve düz bir fetch bunları içermez. Algılayıcı sayfanın satır içi challenge seçeneklerini arar ve needs_browser yanıtını verir. cf_clearance kılavuzu bir challenge sayfası geçildikten sonra olanları ele alır.
Sırada, reCAPTCHA’dan önce hCaptcha var. CapSkip, widget ister onay kutusu, ister invisible, ister Enterprise olsun hCaptcha’yı çözer; ancak reCAPTCHA’dan hCaptcha’ya geçen bir site widget’ında çoğu zaman g-recaptcha sınıfını korur ve ikisini yalnızca hCaptcha script’i ayırt eder. Denetim sitekey’i widget’tan ya da bir hcaptcha.render çağrısından okur ve widget’ta data-size invisible olarak ayarlıysa invisible ekler. Widget’a hcaptcha.setData üzerinden bir rqdata değeri veren bir Enterprise sayfası ise bunun yerine needs_browser alır, çünkü bu değer tek bir challenge için verilir ve kısa sürede geçerliliğini yitirir.
UNSUPPORTED = {"arkose": r"arkoselabs\.com/v2/|funcaptcha\.com/fc/", "geetest_v4": r"gt4\.js|initGeetest4"}
def unsupported(p): # CapSkip cannot solve these: say so instead of misleading the reader
for kind, pat in UNSUPPORTED.items():
if re.search(pat, p.srcs + " " + p.js):
return {"type": kind, "method": None, "args": {}, "unsupported": True}
def cf_challenge(p): # the full-page "Just a moment..." interstitial, not a widget
if re.search(r"_cf_chl_opt\s*=\s*\{", p.js):
why = "data and pagedata (Cloudflare calls them cData and chlPageData) only exist at run time"
return browser("turnstile_challenge", "turnstile", p, why)
def hcaptcha(p): # before reCAPTCHA: a site that moved to hCaptcha may keep the g-recaptcha class
if not re.search(r"hcaptcha\.com/1/api\.js", p.srcs + " " + p.js):
return None
if re.search(r"\.setData\s*\(|rqdata", p.js): # Enterprise rqdata is issued per challenge
return browser("hcaptcha_enterprise", "hcaptcha", p, "rqdata is issued for one challenge at run time")
w = tag("h-captcha", p.h) or tag("g-recaptcha", p.h)
key = attr(w, "data-sitekey") or grab(SITEKEY_JS, render_opts(r"hcaptcha\.render", p.js))
if not key:
return browser("hcaptcha", "hcaptcha", p, "no sitekey in the static HTML")
args = {"sitekey": key, "url": p.url}
if attr(w, "data-size") == "invisible":
args["invisible"] = 1
return hit("hcaptcha", "hcaptcha", **args)4. Adım: reCAPTCHA, doğru sırayla
reCAPTCHA en çok dikkat ister, çünkü birkaç CapSkip varyantı iki script adını paylaşır. Onları ayrı tutan şey soruların sırasıdır. Önce script adı: enterprise.js Enterprise bayrağı demektir, api.js değildir. Sonra script adresindeki render parametresi. render= sonrasındaki bir anahtar v3 demektir, ama render= sonrasındaki explicit kelimesi widget’ını koddan render eden bir v2 sayfası demektir ve asla anahtar olarak kullanılmamalıdır. Sonra işaretleme. g-recaptcha sınıfına ve data-action özniteliğine sahip bir düğme Google’ın v3 düğme bağlamasıdır. data-action olmayan bir düğme ya da data-size değeri invisible olan herhangi bir öğe, invisible v2 widget’ıdır. Sınıfa sahip diğer her şey onay kutusudur.
def recaptcha(p): # Enterprise flag first, then v3 (render=KEY), then v2
m = re.search(r"/recaptcha/(api|enterprise)\.js(\?\S*)?", p.srcs)
if not m:
return None
ent = {"enterprise": 1} if m.group(1) == "enterprise" else {}
render = grab(r"[?&]render=([^&\s]+)", m.group(2)) # a v3 sitekey, or "explicit" (a v2 page)
t, o = tag("g-recaptcha", p.h), render_opts(r"\.render", p.js)
button, action = (t or "").lower().startswith("<button"), attr(t, "data-action")
v3 = bool(render and render != "explicit") or bool(button and action)
key = render if render != "explicit" else None
key = key or attr(t, "data-sitekey") or grab(SITEKEY_JS, o)
if not key:
return browser("recaptcha", "recaptcha", p, "no sitekey in the static HTML (set by JavaScript)")
small = attr(t, "data-size") == "invisible" or grab(r"size['\"]?\s*:\s*['\"](invisible)", o)
invisible = not v3 and bool(button or small)
action = action or grab(r"execute\(\s*[^,)]+,\s*\{\s*['\"]?action['\"]?\s*:\s*['\"]([^'\"]+)", p.js)
args = {"sitekey": key, "url": p.url, **ent}
if v3:
args.update(version="v3", **({"action": action} if action else {}))
if invisible:
args["invisible"] = 1
kind = "recaptcha_" + ("v3" if v3 else "v2_invisible" if invisible else "v2")
return hit(kind + ("_enterprise" if ent else ""), "recaptcha", **args)Her sonuç, zaten bildiğiniz bir çağrıya eşlenir. Bir onay kutusu, bir sitekey ve sayfa URL’si ile solver.recaptcha çağrısıdır. Invisible, invisible=1 ekler, Enterprise enterprise=1 ekler, v3 ise version="v3" ile action değerini ekler. Action adı data-action özniteliğinden ya da sayfanın execute çağrısından gelir; ikisi de HTML’de yoksa argüman dışarıda bırakılır ve CapSkip varsayılan verify değerini kullanır. reCAPTCHA çözücü sayfası ele aldığı her varyantı listeler.
5. Adım: Turnstile
Bir Turnstile widget’ı sayfada iki şeye ihtiyaç duyar: Cloudflare’in api.js dosyası ve bir sitekey. Anahtar, cf-turnstile sınıfına sahip bir div üzerindeki data-sitekey özniteliğinde ya da bir turnstile.render çağrısında bulunur. Denetim script’i şart koşar, bu yüzden alakasız bir sayfadaki başıboş bir sınıf adı onu tetikleyemez. Ayrıca data-action ve data-cdata varsa onları da okur ve iletir. Turnstile çözücü sayfası seçenekleri listeler.
def turnstile(p): # widget only: implicit (div.cf-turnstile) or explicit (turnstile.render)
if "challenges.cloudflare.com/turnstile/v0/api.js" not in p.srcs:
return None
t, o = tag("cf-turnstile", p.h), render_opts(r"turnstile\.render", p.js)
key = attr(t, "data-sitekey") or grab(SITEKEY_JS, o)
if not key:
return browser("turnstile", "turnstile", p, "no sitekey in the static HTML")
action = attr(t, "data-action") or grab(r"\baction['\"]?\s*:\s*['\"]([^'\"]+)", o)
cdata = attr(t, "data-cdata") or grab(r"\bcData['\"]?\s*:\s*['\"]([^'\"]+)", o)
extra = {k: v for k, v in (("action", action), ("data", cdata)) if v}
return hit("turnstile", "turnstile", sitekey=key, url=p.url, **extra)6. Adım: GeeTest ve ALTCHA
GeeTest v3 size iki değer verir ve biri tuzaktır. gt değeri site için sabittir ve sayfada bulunabilir. challenge değeri tek kullanımlıktır ve siteler onu çalışma zamanında bir register endpoint’inden alır. Sayfa ikisini de taşıyorsa algılayıcı ikisini döndürür. Yalnızca initGeetest çağrısını taşıyorsa needs_browser yanıtını verir, çünkü eski HTML’de bulunan bir challenge zaten geçersiz olur.
ALTCHA öznitelik adlarını sürümler arasında değiştirdi. 1. ve 2. sürümler challengeurl, satır içi bir challenge için ise challengejson kullanır. 3. sürüm ve sonrası, bir URL’yi ya da challenge verisini alabilen challenge adlı tek bir öznitelik kullanır. Denetim üçünü de okur ve göreli bir URL’yi sayfa adresine ekler.
def geetest(p): # v3 only: gt is static, challenge comes from the register endpoint
if not (re.search(r"initGeetest\s*\(", p.js) or re.search(r"/gt(\.[\d.]+)?\.js", p.srcs)):
return None
gt = grab(r"\bgt['\"]?\s*:\s*['\"]([0-9a-f]{32})", p.js)
challenge = grab(r"\bchallenge['\"]?\s*:\s*['\"]([0-9a-f]{32,34})", p.js)
if not (gt and challenge):
return browser("geetest", "geetest", p, "gt and challenge come from the register endpoint")
return hit("geetest", "geetest", gt=gt, challenge=challenge, url=p.url)
def altcha(p): # challengeurl / challengejson (v1, v2) or challenge (v3+)
t = grab(r"(<altcha-widget\b[^>]*>)", p.h)
if not t:
return None
c = attr(t, "challenge")
as_json = attr(t, "challengejson") or (c if (c or "").lstrip().startswith("{") else None)
as_url = attr(t, "challengeurl") or (None if as_json else c)
if as_json:
return hit("altcha", "altcha", url=p.url, challenge_json=as_json)
if as_url:
return hit("altcha", "altcha", url=p.url, challenge_url=urljoin(p.url, as_url))
return browser("altcha", "altcha", p, "no challenge attribute")7. Adım: Capy, CaptchaFox ve Friendly Captcha
Bu üçünü bulmak kolaydır ve biraz yanlış anlamak da kolaydır. Capy bu denetim için bir widget div’i gerektirmez. Anahtarı PUZZLE_ ile başlar ve widget script adresinin k parametresinde bulunur; varsayılan dışındaki bir host api_server olarak iletilir. CaptchaFox iki derlemeyle gelir. Düz olan cdn.captchafox.com adresinden yüklenir. Diğeri, bir barındırma platformu tarafından ortak bir CDN yolunun altında paketlenir; bu yüzden algılayıcı yalnızca yola değil, bu yoldaki CaptchaFox paket adına bakar, çünkü yolu başka widget’lar da kullanır.
Friendly Captcha en sinsi olanıdır. 1. ve 2. sürüm aynı div’i ve aynı sitekey ad alanını kullanır ama protokolleri farklıdır; yanlış olanın token’ını CapSkip kabul eder ve site reddeder. Script’ler ikisini ayırır: 2. sürüm @friendlycaptcha/sdk paketini, 1. sürüm friendly-challenge yükler. Sayfa hiçbir işaret vermediğinde algılayıcı tahmin yürütmeyi reddeder ve needs_browser der.
def capy(p): # the widget script is .../puzzle/get_js/?k=PUZZLE_...
m = re.search(r"((?:https?:)?//[^\s\"']+?)/puzzle/get_js/\S*?(PUZZLE_[\w-]+)", p.srcs)
if m:
host = m.group(1) if m.group(1).startswith("http") else "https:" + m.group(1)
more = {} if host == "https://jp.api.capy.me" else {"api_server": host}
return hit("capy", "capy", sitekey=m.group(2), url=p.url, **more)
MAM = "mampkg/@mamdev/core.frontend.libs.captchafox"
def captchafox(p): # plain build from cdn.captchafox.com, or the bundled build from s.uicdn.com
t, key = tag("captchafox", p.h), grab(r"captchafox[^\"']*/api\.js\?key=([^\"'&]+)", p.srcs)
if t or key:
more = {"api_server": "https://s.uicdn.com/" + MAM + "/"} if MAM in p.srcs else {}
sitekey = attr(t, "data-sitekey") or key
if not sitekey:
return browser("captchafox", "captchafox", p, "no sitekey in the static HTML")
return hit("captchafox", "captchafox", sitekey=sitekey, url=p.url, **more)
def friendly(p): # v2 loads @friendlycaptcha/sdk (site.min.js), v1 loads friendly-challenge
t = tag("frc-captcha", p.h)
if not t:
return None
v2_end, v1_end = attr(t, "data-api-endpoint"), attr(t, "data-puzzle-endpoint")
server = v2_end or v1_end
v2 = v2_end is not None or re.search(r"friendlycaptcha/sdk|site(\.compat)?\.min\.js", p.srcs)
v1 = v1_end is not None or re.search(r"friendly-challenge|widget(\.module)?\.min\.js", p.srcs)
if bool(v2) == bool(v1):
return browser("friendly_captcha", "friendly_captcha", p, "cannot tell v1 from v2 in the HTML")
if not attr(t, "data-sitekey"):
return browser("friendly_captcha", "friendly_captcha", p, "no sitekey in the static HTML")
more = {"api_server": server} if server else {}
return hit("friendly_captcha_" + ("v2" if v2 else "v1"), "friendly_captcha",
sitekey=attr(t, "data-sitekey"), url=p.url, version="v2" if v2 else "v1", **more)Bunların her birinin, metodun aldığı seçenekleri içeren kendi çözücü sayfası vardır:
- Capy Puzzle çözücü sayfası: bir Capy yanıtının döndürdüğü üç değer.
- CaptchaFox çözücü sayfası: CaptchaFox metodunun aldığı seçenekler.
- Friendly Captcha çözücü sayfası: Friendly Captcha metodu.
8. Adım: görsel CAPTCHA, bir tahmin
Görsel bir CAPTCHA’nın bulunacak bir sağlayıcı script’i yoktur, bu yüzden bu denetim bir tahmindir ve yalnızca başka hiçbir şey eşleşmediğinde çalışır. Denetim, işaretlemesinde captcha geçen bir görsel ve sayfanın başka bir yerinde işaretlemesinde captcha ya da security code geçen bir metin kutusu arar. İşaretlemenin bu sözcükleri kullanmadığı her durumda kaçırdı ve çizilmiş bir canvas ona görünmezdir. Bir eşleşmeyi kesin değil, olası sayın.
def image(p): # a guess: an <img> that says captcha, next to a text box that says captcha
box = r"<input\b(?![^>]*type\s*=\s*[\"']?(?:hidden|checkbox|submit))[^>]*(?:captcha|security.?code)"
for img in re.findall(r"<img\b[^>]*>", p.h, re.I):
if re.search(box, p.h, re.I) and re.search(r"captcha", img, re.I) and attr(img, "src"):
return hit("image", "normal", image=urljoin(p.url, attr(img, "src")))9. Adım: denetimler, detect ve solve
Son blok denetimleri sırayla listeler ve detect ile solve işlevlerini sunar; detect_all bir yardımcıdır. Sıra mantığın kendisidir: önce desteklenmeyen sağlayıcılar, sonra Cloudflare sayfası, sonra hCaptcha, sonra diğer her aile ve görsel tahmini yalnızca liste hiçbir şey bulmadığında. detect işlevi ilk sonucu döndürür ve sayfa birden fazla aile taşıdığında, örneğin bir v3 script’i artı bir Turnstile kutusu, bir also listesi ekler. Liste aileleri adlandırır, başka anahtarlı ikinci bir reCAPTCHA widget’ını değil. solve işlevi sonucu doğru CapSkip metoduna yönlendirir. unsupported olan ya da needs_browser işaretli her şeyi bir RuntimeError ile reddeder ve görseli kendi Session’ınız üzerinden indirir, böylece çerezler eşleşir.
SDK’da henüz hCaptcha çağrısı yok, bu yüzden bir hCaptcha sonucu bunun yerine solve_hcaptcha’ya gider. Bu fonksiyon sitekey’i ve sayfa adresini method=hcaptcha ile in.php’ye gönderir, ardından token hazır olana kadar res.php’yi 5 saniyede bir yoklar. Bu akışı API belgelerinin hCaptcha bölümü anlatır. Çözücünün adresini api olarak, API key doğrulaması açıksa API key’inizi de key olarak verin. Sonuç token’ı code içinde, token’ın alındığı User-Agent’ı ise userAgent içinde taşır.
CHECKS = [unsupported, cf_challenge, hcaptcha, recaptcha, turnstile, geetest, altcha, capy, captchafox, friendly]
def detect_all(html, page_url):
p = parse(html, page_url)
return [r for r in (check(p) for check in CHECKS) if r] or [r for r in [image(p)] if r]
def detect(html, page_url):
found = detect_all(html, page_url)
if len(found) > 1:
found[0]["also"] = [r["type"] for r in found[1:]]
return found[0] if found else None
def solve_hcaptcha(args, api, key=None): # no SDK call yet: post to in.php, poll res.php
form = {"method": "hcaptcha", "sitekey": args["sitekey"], "pageurl": args["url"],
"invisible": args.get("invisible", 0), "data": args.get("data"), "key": key, "json": 1}
job = requests.post(api + "/in.php", data=form, timeout=30).json()
if job["status"] != 1:
raise RuntimeError(job["request"])
for _ in range(36): # a solve takes 10 to 30 seconds, so give up after 3 minutes
time.sleep(5)
q = {"key": key, "action": "get", "id": job["request"], "json": 1}
r = requests.get(api + "/res.php", params=q, timeout=30).json()
if r["request"] != "CAPCHA_NOT_READY":
if r["status"] != 1:
raise RuntimeError(r["request"])
return {"code": r["request"], "userAgent": r.get("useragent")}
raise TimeoutError("hCaptcha was not solved within 3 minutes")
def solve(info, solver, session=None, api="http://127.0.0.1:8080", key=None):
if info.get("unsupported") or info.get("needs_browser"):
raise RuntimeError(info.get("needs_browser") or info["type"] + " is not supported by CapSkip")
args = dict(info["args"])
if info["method"] == "hcaptcha":
return solve_hcaptcha(args, api, key)
if info["method"] == "normal": # fetch the image with YOUR session so the cookie matches it
src = args.pop("image")
if not src.startswith("data:"):
r = (session or requests).get(src, timeout=30)
r.raise_for_status()
src = "data:image/png;base64," + base64.b64encode(r.content).decode()
args["file"] = src
return getattr(solver, info["method"])(**args)Çalıştırın
Bunu detect.py dosyasının yanına use.py olarak kaydedin ve ortamın kendi Python’uyla, .\.venv\Scripts\python.exe use.py örneğindeki gibi çalıştırın. 8080 portu varsayılandır; sizinki farklıysa CapSkip’in bağlantı ayarlarında gösterilen portu kullanın. Script sayfanızı çeker, algılayıcının bulduğunu yazdırır ve CapSkip’i yalnızca çağrılacak bir metot varsa ve hiçbir şey eksik değilse çağırır.
import requests
from capskip import CapSkip
from detect import detect, solve
URL = "https://example.com/login" # a page of yours with a CAPTCHA
HOST, PORT = "127.0.0.1", 8080 # from CapSkip's connection settings
session = requests.Session()
html = session.get(URL, timeout=30).text
info = detect(html, URL)
print(info)
if info and info["method"] and not info.get("needs_browser"):
solver = CapSkip(host=HOST, port=PORT)
result = solve(info, solver, session, api=f"http://{HOST}:{PORT}")
print(result)reCAPTCHA v2 onay kutusu olan bir sayfada ilk çıktı türü, metodu ve argümanları gösterir, ikincisi ise sonucu gösterir ve token code alanında yer alır. Mock çalıştırmasında şöyle görünüyordu:
| Sayfa | detect şunu döndürür |
|---|---|
| reCAPTCHA v2 checkbox | type recaptcha_v2, method recaptcha, sitekey ve url |
| hCaptcha widget’ı | type hcaptcha, method hcaptcha, sitekey ve url |
| Cloudflare “Just a moment” sayfası | type turnstile_challenge, needs_browser ayarlı |
| v3 script’i artı bir Turnstile widget’ı | type recaptcha_v3, also listesinde turnstile var |
HTML hiçbir şey söylemediğinde: tarayıcı konsolu
Bazı sayfalar widget’larını yüklendikten sonra JavaScript ile ekler. O zaman düz bir fetch hiç CAPTCHA bulamaz ve algılayıcı, CAPTCHA’sı olan bir sayfa için None döndürür. İki durum bilinir ve beklenir: kodla eklenen bir Google ya da Cloudflare script’i ve kodla ayarlanan bir sitekey. Bunlar için sayfayı Chrome’da açın, F12’ye basın ve bunu Console’a yapıştırın. Chrome yapıştırmayı engellerse önce allow pasting yazıp Enter’a basın. Sitekey’leri sayfanın zaten yüklediği kaynaklardan okur, yani tarayıcının oluşturduğunu görür.
(() => {
const iframes = [...document.querySelectorAll('iframe')].map((f) => f.src);
const urls = performance.getEntriesByType('resource').map((r) => r.name).concat(iframes);
const found = new Map(), add = (o) => found.set(JSON.stringify(o), o);
for (const u of urls) {
let m;
if ((m = u.match(/recaptcha\/(?:api2|(enterprise))\/anchor\?.*?\bk=([\w-]+)/)))
add({ type: 'recaptcha', sitekey: m[2], enterprise: !!m[1], invisible: /size=invisible/.test(u) });
else if ((m = u.match(/recaptcha\/(?:api|enterprise)\.js\?.*?\brender=([\w-]+)/)) && m[1] !== 'explicit')
add({ type: 'recaptcha v3', sitekey: m[1], enterprise: /enterprise\.js/.test(u) });
else if ((m = u.match(/challenges\.cloudflare\.com\/.*turnstile\/.*\/(0x4[\w-]+|[123]x0+[A-Za-z]+)\//)))
add({ type: 'turnstile', sitekey: m[1] });
else if ((m = u.match(/hcaptcha\.com\/captcha\/.*[#&?]sitekey=([\w-]+)/))) add({ type: 'hcaptcha', sitekey: m[1] });
else if (/arkoselabs\.com\/fc\//.test(u)) add({ type: 'arkose: CapSkip cannot solve' });
}
if (window._cf_chl_opt) add({ type: 'cloudflare challenge page' });
document.querySelectorAll('altcha-widget').forEach((e) => add({ type: 'altcha', challenge: e.getAttribute('challenge') || e.getAttribute('challengeurl') }));
document.querySelectorAll('.h-captcha[data-sitekey]').forEach((e) => add({ type: 'hcaptcha', sitekey: e.dataset.sitekey }));
document.querySelectorAll('.frc-captcha, .captchafox').forEach((e) => add({ type: e.classList.contains('captchafox') ? 'captchafox' : 'frc-captcha', sitekey: e.dataset.sitekey }));
const v3 = [...found.values()].filter((o) => o.type === 'recaptcha v3').map((o) => o.sitekey); // a v3 badge also adds an invisible anchor frame
const res = [...found.values()].filter((o) => !(o.type === 'recaptcha' && v3.includes(o.sitekey)));
return res.length ? res : 'no known CAPTCHA found (GeeTest, Capy and CaptchaFox scripts are not checked)';
})();Bir reCAPTCHA v2 sayfasında, bir invisible sayfada, bir v3 sayfasında, bir Turnstile sayfasında ve hCaptcha demosunda sitekey’i ve bayrakları buldu, CAPTCHA olmayan bir sayfada ise bir mesaj yazdırdı. Üç sınır var: tarayıcı varsayılan olarak yalnızca yaklaşık 250 kaynak girdisi tutar, bu yüzden uzun süre açık kalan tek sayfalı bir uygulama erken olanları kaybedebilir ve kod parçası Friendly Captcha 1. sürümü ile 2. sürümü ayırt edemez, GeeTest, Capy veya CaptchaFox script’lerini de aramaz. Cloudflare’in tam sayfa denetiminde sitekey’i bir kaynak adresinden gösterebilir ama render çağrısına bir hook gerektiren data ve pagedata değerlerini gösteremez. Bu hook burada test edilmedi.
Algılamanın başarısız olduğu yerler
| Durum | detect ne döndürür | Ne yapmalı |
|---|---|---|
| JavaScript ile eklenen widget script’i | Yok | Sayfayı bir tarayıcıda yükleyin ve konsol kod parçasını çalıştırın |
| JavaScript ile ayarlanan sitekey | needs_browser | Anahtarı canlı sayfadan okuyun |
| Cloudflare tam sayfa denetimi | needs_browser | Değerleri bir tarayıcıda okuyun, cf_clearance kılavuzuna bakın |
| HTML’de widget olmadan CaptchaFox, Friendly Captcha veya Capy script’i | Yok | Konsol kod parçasını çalıştırın ya da sayfa kaynağını okuyun |
| Sitenin kendisinden gelen bir 403 veya 503 sayfası | Yok | Önce durum kodunu yazdırın: bir engel sayfasında bulunacak CAPTCHA yoktur |
| Arkose, GeeTest v4 | unsupported | CapSkip bunları çözemez |
| rqdata ayarlayan hCaptcha Enterprise | needs_browser | Her görev için canlı sayfadan taze bir rqdata okuyun ve bunu solve_hcaptcha’ya data olarak verin |
| Script ipucu olmayan Friendly Captcha | needs_browser | Sayfanın hangi script’i yüklediğini kontrol edin |
| captcha sözcüğünü içermeyen görsel CAPTCHA | Yok | O site için kendi kuralınızı ekleyin |
Yaygın hatalar
| Hata | Neden | Düzeltme |
|---|---|---|
| ERROR_GOOGLEKEY | Anahtar explicit kelimesiydi ya da bir reCAPTCHA anahtarı olmayan bir anahtardı | Anahtarı render parametresinden değil, widget’tan okuyun |
| ERROR_CAPTCHA_UNSOLVABLE | Başarısız her iş böyle görünür, yanlış bir reCAPTCHA sürümü de dahil, ve nedeni belirtmez | Önce algılanan türü kontrol edin, sonra şuna bakın: API belgeleri |
| ValidationException | Metodun almadığı bir seçenek, örneğin bir v2 çağrısında action | Algılayıcının döndürdüğü argümanları kullanın |
| NetworkException | CapSkip çalışmıyor ya da host veya port yanlış | CapSkip’i başlatın ve bağlantı ayarlarını kontrol edin |
| solve kaynaklı RuntimeError | Sonuç unsupported ya da needs_browser, veya CapSkip bir hCaptcha işini reddetti ve mesaj onun hata kodu | solve çağırmadan önce ilk iki durumu ele alın; üçüncüsü için sitekey’i ve sayfa adresini kontrol edin |
Çözücüyü bir sunucuda çalıştırmak
Algılayıcının CapSkip’ten yalnızca iki şeye ihtiyacı vardır: bir host ve bir port. Local modunda bu 127.0.0.1 ve kendi bilgisayarınızdır. Server modunda CapSkip ağ adresinizi ya da genel bir IP’yi dinler, böylece başka bir bilgisayarda, bir VPS’te ya da barındırılan bir platformda çalışan bir algılayıcı aynı API üzerinden onu çağırabilir. use.py dosyasının başındaki HOST ve PORT değerlerini değiştirin, koddaki başka hiçbir şey değişmez. Kararlı uzaktan erişim için statik bir genel IP önerilir. Server modu yine kendi donanımınızdır ve yine kullanım başına ücretlendirilmez. Server modunun portu her adrese açtığını unutmayın; bu yüzden ulaşmaması gereken adresler için bir Windows Firewall engelleme kuralı ekleyin. Modları nasıl değiştireceğiniz için bağlantı ayarları bölümüne bakın.
FAQ
Kod olmadan bir sitenin hangi CAPTCHA’yı kullandığını söyleyebilir miyim?
Evet. DevTools’u açın, Network sekmesine gidin, sayfayı yeniden yükleyin ve script adreslerine bakın. google.com/recaptcha ya da recaptcha.net script’i reCAPTCHA, challenges.cloudflare.com Turnstile, js.hcaptcha.com ise hCaptcha’dır. Yukarıdaki Console kod parçası aynı aramayı sizin için yapar.
detect neden tahmin yerine needs_browser döndürür?
Çünkü yanlış bir tahmin tam bir çözümü boşa harcar ve site nedenini göstermeden reddeden bir token döndürür. Statik HTML sitekey’i, bir GeeTest challenge’ını ya da bir Friendly Captcha sürümünü içermediğinde bunu söylemek, başarısız bir işten daha çabuktur.
CapSkip hCaptcha çözer mi?
Evet. CapSkip, hCaptcha onay kutusu, invisible ve Enterprise widget’larını HTTP API’si üzerinden method=hcaptcha ile çözer. Ayrıntıları API belgeleri anlatır. Python SDK’sında henüz hCaptcha çağrısı yok, bu yüzden yukarıdaki solve_hcaptcha işi in.php’ye kendisi gönderir. FunCaptcha, Arkose ve GeeTest v4 hâlâ desteklenmez; algılayıcı bunları unsupported olarak bildirir, böylece bir script sayfada başarısız olmak yerine onu atlayabilir.
Barındırılan bir platform algılayıcıyı benim CapSkip’imle kullanabilir mi?
Evet, Server modu ile. CapSkip’i ağ ya da genel IP’nizi dinleyecek şekilde ayarlayın, use.py içindeki HOST ve PORT değerlerini o adrese ayarlayın; algılayıcı ve solve çağrısı tek bir bilgisayardaki gibi çalışır. Çözücü sizin donanımınızda kalır.
Kısa özet
Önce CAPTCHA türünü algılayın, sonra çözün: script’i ve işaretlemeyi sabit bir sırayla okuyun, argümanları doğru metoda verin ve None, needs_browser ile unsupported değerlerini hata değil, yanıt olarak ele alın. CapSkip kendi makinenizde olduğunda bir sayfayı denetlerken işleyen bir sayaç yoktur; bu yüzden sınırsız captcha çözücü yeni bir siteyle birkaç dakikada bir karşılaşan bir tarama için pratiktir. Python SDK’nın kendisi için şuraya bakın: Python CAPTCHA çözücü sayfası.
