httpx ve aiohttp ile CAPTCHA Nasıl Çözülür (Tarayıcısız)

httpx captcha - How to Solve CAPTCHAs in httpx and aiohttp (No Browser)

Bir httpx captcha akışı üç adım ve yaklaşık yirmi satırdır; insanların bunu yanlış yapmasının nedeni ise çözücüyle hiç ilgili değildir. Bu yığında tarayıcı yoktur. Sitenin JavaScript’ini hiçbir şey çalıştırmaz; dolayısıyla token’ın düşmesi gereken gizli alanı hiçbir şey oluşturmaz ve formu sizin yerinize hiçbir şey göndermez. Üçünü de kendiniz yaparsınız: sitekey’i HTML’den okuyun, çözün, sonra token’ı sayfayı getiren aynı istemci üzerinden sıradan bir form alanı olarak geri gönderin.

Neye ihtiyacınız var

  • Python 3.10 veya daha yenisi ve httpx ya da aiohttp’den biri. CapSkip Python SDK’sı ikisiyle de çalışır.
  • Korumalı sayfanın URL’si. Sitekey’e önceden ihtiyacınız yok, çünkü onu birinci adım okuyor.
  • Betik ile çözücü aynı makinedeyse Yerel modda, değilse Sunucu modunda çalışan CapSkip. Her ikisi de şurada açıklanıyor: bağlantı ayarları.
# pip install capskip
pip install capskip httpx

# Using aiohttp instead? Install it as well. See the note below
# about which HTTP libraries you end up with either way.
pip install capskip aiohttp

SDK zaten httpx ile birlikte geliyor

Bağımlılıkları saymadan önce bilmekte fayda var. CapSkip Python paketi requests, httpx ve aiofiles paketlerini zorunlu çalışma zamanı bağımlılığı olarak bildirir; yani SDK’yı kurmak, siz istemiş olsanız da olmasanız da httpx’i kurar. httpx zaten kazıma istemcinizse, çözücü ortamınıza yeni bir HTTP kütüphanesi eklemez ve AsyncCapSkip sizin olay döngünüzü paylaşır. aiohttp kullanıyorsanız, onun yanında bir de httpx gelir ve artık aynı virtualenv içinde iki istemciniz olur. Hiçbir şey bozulmaz ama bir bağımlılık incelemesinin soracağı türden bir konudur.

Tarayıcı olmayınca ne değişir

Selenium veya Playwright’te, reCAPTCHA’nın oluşturduğu gizli textarea’nın değerini siz ayarlarsınız ve sayfanın kendi submit handler’ı onu birlikte taşır. Burada bunların hiçbiri yok. Bir HTTP istemcisi bayt getirir ve script etiketlerini hiçbir şey değerlendirmez; dolayısıyla widget hiç oluşturulmaz, textarea hiç yaratılmaz ve çalıştırılacak bir submit handler da yoktur.

Bunun yerine elinizde olan şey daha basit ve üzerinde düşünmesi daha kolay. Token yalnızca bir metindir ve site onu bir POST gövdesinde belirli bir alan adı altında bekler. reCAPTCHA v2 için o alanın adı g-recaptcha-response’tur ve alanı tarayıcı da doldursa siz de doldursanız ad aynı kalır. Turnstile cf-turnstile-response kullanır. GeeTest bir yerine üç alan gönderir. Doğrulama, sitenin sunucusunda Google veya Cloudflare’a karşı yapılır; bu yüzden karşı taraftaki hiç kimse bu metni sizin hangi sürecinizin ürettiğini anlayamaz.

Adım 1: sitekey’i, gönderimi yapacağınız istemciyle okuyun

Akışın tamamı için tek bir istemci kullanın. Bu bir düzen meselesi değil, işin ta kendisidir: POST’un GET ile aynı ziyaretçiden gelmiş gibi görünmesini sağlayan şey, cookie jar ile bağlantı havuzudur. Gönderim için yeni bir istemci kurarsanız sayfanın koyduğu bütün oturum çerezlerini çöpe atarsınız; doğru bir token’ın yine de başarısız olmasının çok yaygın bir nedeni budur.

# pip install capskip httpx
import re
import httpx

PAGE_URL = "https://example.com/page-with-recaptcha"

# One client for the whole flow. Its cookie jar is what makes
# the POST later look like the same visitor as this GET.
client = httpx.Client(timeout=30, follow_redirects=True)

html = client.get(PAGE_URL).text
match = re.search(r'data-sitekey=["\']([^"\']+)', html)
if not match:
    raise RuntimeError("No data-sitekey in the HTML.")

sitekey = match.group(1)   # this is what the solver needs

O regex hiçbir şey bulamazsa, widget işaretlemeyle birlikte sunulmuyor demektir: JavaScript tarafından enjekte ediliyordur ve bir HTTP istemcisi onu asla göremez. Sayfayı bir tarayıcıda açın, sitekey’i ağ sekmesinden bir kez alın ve koda sabit olarak yazın. Sitekey herkese açıktır ve sabittir; yani bu, kendinizi kötü hissetmeniz gereken bir kestirme yol değildir.

Adım 2: kendi makinenizde çözün

Çözüm, kendi donanımınızda dinleyen bir servise yapılan tek bir çağrıdır. Her reCAPTCHA varyantı, farklı anahtar kelime argümanlarıyla aynı metottur: invisible değeri 1, enterprise değeri 1 ya da version değeri v3 ve bir action. Turnstile ile GeeTest’in de aynı biçimde kendi metotları vardır. Tam parametre listesi şurada: CapSkip API dokümantasyonu.

# CapSkip listens on your machine, so this is a loopback call.
from capskip import CapSkip

solver = CapSkip(host="127.0.0.1", port=8080)
token = solver.recaptcha(sitekey=sitekey, url=PAGE_URL)["code"]

O çağrı, yoklama yaparken bloklar. SDK sabit bir aralıkta yoklama yapmaz: 250 milisaniyeden başlar ve pollingInterval değerine doğru aralığı açar; bir SDK çözümünün, ham uç noktaya karşı elle yazılmış bir döngüden genellikle daha erken dönmesinin nedeni budur. Üst sınır recaptchaTimeout’tur ve varsayılan değeri 300 saniyedir.

Adım 3: token’ı bir form alanı olarak gönderin

Şimdi onu aynı istemci üzerinden, tarayıcının göndereceği aynı formda geri gönderin. Formun taşıdığı diğer alanları da ekleyin; birinci adımda HTML’den okuduğunuz gizli CSRF veya nonce değeri dahil.

# g-recaptcha-response is an ordinary form field. Same client,
# so the session cookies from the GET go along with it.
reply = client.post(
    PAGE_URL,
    data={
        "username": "demo",
        "g-recaptcha-response": token,
    },
)

print(reply.status_code)

Token’ı hemen gönderin. Bir reCAPTCHA token’ı yaklaşık iki dakika geçerlidir; çözümle gönderim arasındaki bir kuyruk, bir yeniden deneme beklemesi ya da bir uyku bu bütçeyi siz fark etmeden yakar. Token’ın ömrü ve bunun pratikte ne anlama geldiği şurada ele alınıyor: reCAPTCHA token ömrü kılavuzu.

Tam çalışan örnek, async

Aynı üç adım, async istemciyle. Python’daki AsyncCapSkip, senkron olanın takma adı değil gerçek bir async uygulamadır; bu yüzden sizin olay döngünüzü paylaşır ve yoklama yaparken bir thread’i beklemede bırakmaz.

# pip install capskip httpx
import asyncio
import re
import httpx
from capskip import AsyncCapSkip

PAGE_URL = "https://example.com/page-with-recaptcha"
SITEKEY_RE = re.compile(r'data-sitekey=["\']([^"\']+)')

async def submit_once(client, solver):
    html = (await client.get(PAGE_URL)).text
    match = SITEKEY_RE.search(html)
    if not match:
        raise RuntimeError("No data-sitekey in the HTML.")

    result = await solver.recaptcha(sitekey=match.group(1), url=PAGE_URL)

    reply = await client.post(
        PAGE_URL,
        data={"g-recaptcha-response": result["code"]},
    )
    return reply.status_code

async def main():
    solver = AsyncCapSkip(host="127.0.0.1", port=8080)
    async with httpx.AsyncClient(timeout=30, follow_redirects=True) as client:
        print(await submit_once(client, solver))

asyncio.run(main())

Bunlardan birkaçını aynı anda çalıştırmak için gather kullanın. Tüm parti için tek bir çözücü örneği yeterlidir; istemci tarafında ise genellikle her kimlik için bir istemci istersiniz, çünkü paylaşılan bir cookie jar paylaşılan bir oturum demektir. İşin toplu çalıştırma tarafı şurada anlatılıyor: Python ile CAPTCHA’ları paralel çözme kılavuzu.

aiohttp sürümü

Yukarıdakilerin hepsi geçerli. Yalnızca üç ad değişir: oturum tipi, gövdeyi okuma şekliniz ve durum kodunu tutan öznitelik.

# pip install capskip aiohttp
import aiohttp
from capskip import AsyncCapSkip

async def submit_once(session, solver):
    async with session.get(PAGE_URL) as reply:
        html = await reply.text()

    match = SITEKEY_RE.search(html)
    result = await solver.recaptcha(sitekey=match.group(1), url=PAGE_URL)

    async with session.post(
        PAGE_URL,
        data={"g-recaptcha-response": result["code"]},
    ) as submitted:
        return submitted.status   # not status_code

async def main():
    solver = AsyncCapSkip(host="127.0.0.1", port=8080)
    async with aiohttp.ClientSession() as session:
        print(await submit_once(session, solver))

Bir ClientSession kendi cookie jar’ını tutar; yani tek istemci kuralı olduğu gibi geçerli kalır. aiohttp’te HTTP/2 desteği yoktur, ki bu iş için size hiçbir şeye mal olmaz.

Proxy’ler ve yalnızca HTTP istemcilerini yakalayan tuzak

Site, token’ın onu gönderen adresten üretildiğini kontrol ediyorsa, çözüm ile gönderimin aynı proxy üzerinden çıkması gerekir. CapSkip reCAPTCHA, Turnstile ve GeeTest için görev başına bir proxy alır. Görsel CAPTCHA’lar proxy kabul etmez ve zaten buna ihtiyaçları da yoktur.

# Same exit address for the solve and for the submit.
result = await solver.recaptcha(
    sitekey=sitekey,
    url=PAGE_URL,
    proxy={"type": "HTTP", "uri": "user:[email protected]:3128"},
)

# httpx 0.26 and newer spell this proxy=. Before that it was
# proxies=, which 0.28 removed outright.
async with httpx.AsyncClient(proxy="http://user:[email protected]:3128") as client:
    await client.post(PAGE_URL, data={"g-recaptcha-response": result["code"]})

İnsanları yakalayan kısım şurası. CapSkip proxy tipi olarak SOCKS5 ve SOCKS5H kabul eder; ama aiohttp yalnızca HTTP proxy’lerini ve CONNECT tüneli üzerinden HTTPS’i konuşur, httpx ise SOCKS kullanabilmek için önce socks ekstrasının kurulmasını ister. Çözücüye, istemcinizin kullanamayacağı bir SOCKS5 proxy verirseniz çözüm bir adresten başarılı olur ama gönderim başka bir adresten çıkar; bu da tam olarak bozuk bir token gibi görünür, oysa öyle değildir. İki tarafı da, her ikisinin de konuşabildiği bir proxy tipinde tutun. Proxy tipleri arasında seçim yapmak şurada ele alınıyor: çözüm sırasında proxy döndürme kılavuzu.

aiohttp’e özgü bir konu daha: requests’in aksine, oturumu trust_env değeri True olacak şekilde kurmadığınız sürece HTTP_PROXY ve HTTPS_PROXY ortam değişkenlerini yok sayar. Uygulandığını sandığınız bir proxy aslında uygulanmamış olur ve bunu size kimse söylemez.

HTTP/2 ve onu açmanın neyi değiştirdiği

httpx HTTP/2 konuşabilir ama varsayılan olarak değil ve ekstra olmadan da değil: httpx’i http2 ekstrasıyla kurun ve istemciyi oluştururken http2 değerini True olarak verin. Bunun, trafiğinizin hat üzerinde nasıl göründüğünü de değiştirdiğini bilmekte fayda var; çünkü protokol anlaşması ve başlık sıralaması HTTP/1.1’den farklıdır. Bu, çözümden çok bir parmak izi meselesidir ve ayrı bir konudur; ana hatlarıyla şurada anlatılıyor: Python’da TLS parmak izi üzerine yazı.

Çözücüyü başka bir makinede çalıştırmak

Betikler yer değiştirir. Bir konteyner, bir VPS ya da zamanlanmış bir worker, çözücünün üzerinde olduğu makine değildir; oradaki loopback ise konteyneri gösterir ve orada dinleyen hiçbir şey yoktur.

CapSkip’in iki bağlantı modu var. Local, 127.0.0.1 adresine bağlanır ve yalnızca o cihaza yanıt verir. Server ise ağınıza veya genel IP’nize bağlanır; böylece bir konteyner, bir VM ya da barındırılan bir worker, aynı Windows makinesine API üzerinden ulaşır. Statik bir genel IP, adresi sabit tutar. Her iki modda da bu hâlâ sizin donanımınız ve kullanım hâlâ ölçülmez; yani yoğun bir gün ikisinde de aynı maliyettedir.

# The SDK reads these three itself, so the same code works
# whether the solver is local or on another machine:
#   CAPSKIP_HOST=192.0.2.10
#   CAPSKIP_PORT=8080
#   CAPSKIP_API_KEY=your-key

solver = AsyncCapSkip()

Çözücü bir ağ adresini dinlemeye başladığında anahtar doğrulamasını açın ve her worker’a kendi anahtarını verin; böylece biri, diğerlerine dokunmadan iptal edilebilir. Her iki mod da şurada adım adım anlatılıyor: CapSkip kurulum kılavuzu.

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

GördüğünüzNedenDüzeltme
Regex hiç data-sitekey bulmuyorWidget JavaScript ile enjekte ediliyor, yani sunulan HTML’de yokSitekey’i bir tarayıcıda bir kez okuyup koda sabitleyin. Herkese açık ve sabittir
Form yine doğrulamayla birlikte geri geliyorPOST için ikinci bir istemci kuruldu, oturum çerezleri kaybolduGET ve POST için tek bir istemci kullanın
Geçerli bir token reddediliyorÇözüm ile gönderim farklı adreslerden çıktıİkisinde de aynı proxy’yi ve her iki tarafın konuşabildiği bir tipi kullanın
Geçerli bir token, bir gecikmeden sonra reddediliyorÇözümle gönderim arasında bir kuyrukta süresi dolduMümkün olduğunca geç çözün ve hemen gönderin
aiohttp’te proxy yok sayılıyor gibi görünüyoraiohttp varsayılan olarak proxy ortam değişkenlerini okumazproxy= değerini açıkça verin ya da oturumu trust_env değeri True ile kurun
httpx istemci yapıcısında TypeErrorproxies= httpx 0.28’de kaldırıldı0.26’dan beri geçerli ad olan proxy= kullanın
NetworkExceptionCapSkip çalışmıyor ya da host ve port yanlışUygulamayı başlatın ya da CAPSKIP_HOST’u sunucu adresine yönlendirin
ValidationExceptionSDK’nın o CAPTCHA türü için kabul etmediği bir argümanTürü kontrol edin. v2’de action ya da v3’te invisible göndermek buna yol açar

FAQ

reCAPTCHA v2 gerçekten tarayıcı olmadan çalışır mı?

Evet. Sitenin sunucusu token’ı Google ile doğrular ve bu kontrol token’a, sitekey’e ve isteğin geldiği adrese bakar. Bu kontrolün, metni hangi sürecin oluşturduğunu sorma imkânı yoktur. Tarayıcı, en başından beri yalnızca o alanı koymak için elverişli bir yerdi.

SDK’yı kurmak bana httpx’i zorunlu kılar mı?

Kılar. requests, httpx ve aiofiles paketin bildirilmiş bağımlılıklarıdır; yani bir aiohttp projesinde bile httpx onunla birlikte gelir. Bu bir çakışma değil, ortamda ikinci bir HTTP istemcisinin bulunması demektir ve kendi kodunuzda onu kullanmak zorunda değilsiniz.

Bunu reCAPTCHA v3 için nasıl yaparım?

version değerini v3 olarak ve sayfanın kullandığı action’ı gönderin, sonra token’ı sitenin kendi betiğinin yazdığı alan adı hangisiyse onun altında gönderin. O ad, g-recaptcha-response gibi standartlaştırılmış değildir; bu yüzden sayfadan bir kez okuyun. action eşleşmek zorundadır, aksi hâlde token gerçek olsa bile kontrol sunucu tarafında başarısız olur.

Bunun için httpx mi yoksa aiohttp mi kullanmalıyım?

İkisi de olur. httpx zaten SDK ile birlikte kuruluyor ve doğru ekstralarla HTTP/2 ve SOCKS yapabiliyor; bu da onu ortamınıza yeni hiçbir şey eklemeyen seçenek hâline getiriyor. aiohttp çok yüksek eşzamanlılıkta daha hızlıdır ve mevcut birçok crawler onun üzerine kuruludur. Çözücü kodu ikisinde de birebir aynı; bu yüzden kararı CAPTCHA tarafına göre değil, crawler’ınızın kendi gerekçelerine göre verin.

Kısa özet

Sitekey’i sunulan HTML’den okuyun, yerel olarak çözün, sonra token’ı sayfayı getiren istemci üzerinden sıradan bir form alanı olarak gönderin. Akışın tamamı için tek bir istemci kullanın ki çerezler hayatta kalsın. Çözüm ile gönderim için aynı çıkış adresini, üstelik her iki tarafın gerçekten konuşabildiği bir proxy tipinde kullanın. Token kısa ömürlü olduğu için hemen gönderin.

Daha geniş Python hikâyesi şurada: Python CAPTCHA çözücü sayfası. reCAPTCHA v2’nin ayrıntıları ise şurada: reCAPTCHA v2 çözücü sayfası. Aynı üç çağrı Node.js, PHP ve C#'ta da mevcuttur ve şurada listelenmiştir: CAPTCHA çözme SDK sayfası.

Bunu büyük bir taramaya yöneltmeden önce bilinmeye değer son bir şey. CapSkip, zaten sahip olduğunuz donanımda çalışan bir sınırsız captcha çözücü olduğundan, elli bin doğrulama çözen bir tarama, elli tane çözen bir taramayla tam olarak aynıya mal olur.