Locust’ta İstatistiklerinizi Bozmadan CAPTCHA Nasıl Çözülür

locust captcha - How to Solve CAPTCHAs in Locust Without Skewing Your Stats

Locust’ta bir captcha çözümünün iki yerden uzak durması gerekir: istatistikleriniz ve her yinelemede çalışan yol. Locust, self.client üzerinden yapılan her isteği raporlar; bu nedenle çözümü onun üzerinden yaparsanız çözücünün yanıt süreleri, okumaya çalıştığınız raporun içine düşer. Bir task içine yerleştirilen çözüm ise her kullanıcının her yinelemesinde çalışır. Sınırların nerede olduğunu bildikten sonra ikisinden de kaçınmak zor değildir.

Neye ihtiyacınız var

  • Python 3.10 veya daha yenisi üzerinde Locust 2.x; CapSkip istemcisinin ihtiyaç duyduğu sürüm de budur.
  • Bir Windows makinesinde çalışan CapSkip ve locustfile dosyanızın yanına kurulmuş Python istemcisi.
  • Korumalı formun sitekey ve sayfa URL’si; her kullanıcının yeniden bulması yerine dışarıdan verilir.
  • Locust, çözücünün kendi makinesi dışında herhangi bir yerde çalışıyorsa Server modu; buna her container ve her worker makinesi dahildir. Bu, bağlantı ayarları altındaki tek bir ayardır.
# pip install capskip
pip install -U locust capskip

Adım 1: çözümü self.client üzerinden geçirmeyin

Bir raporu sessizce bozan kısım burasıdır. Locust dokümantasyonu self.client’ın ne olduğu konusunda açıktır: requests.Session sınıfının bir alt sınıfı ve sarmalayıcısı olan bir HttpSession örneğidir ve eklediği şey, istek sonuçlarının Locust’a raporlanmasıdır. Başarı ve başarısızlık, yanıt süresi, yanıt uzunluğu, ad. Onun üzerinden gönderilen her şey istatistik tablosuna düşer.

Bir çözüm saniyeler sürer. Uygulamanızın uç noktaları milisaniyeler sürer. İkisini aynı tabloya koyarsanız doksan beşinci yüzdeliğiniz, bir CAPTCHA’nın ne kadar sürdüğünün ölçüsü haline gelir; bu da kimsenin istemediği bir sayıdır.

İyi haber şu ki çözüm, hiçbir şey yapmamaktır. CapSkip istemcisinin kendi HTTP taşıma katmanı vardır ve self.client’a hiç dokunmaz; bu yüzden bir çözüm, varsayılan olarak Locust istatistiklerinde görünmez. Locust yalnızca kendi oturumundan geçenleri kaydeder.

# pip install capskip
import os
from capskip import CapSkip

# CAPSKIP_HOST is the solver machine. 127.0.0.1 only works when
# Locust and CapSkip run on the same Windows box.
solver = CapSkip(
    host=os.environ.get("CAPSKIP_HOST", "127.0.0.1"),
    port=int(os.environ.get("CAPSKIP_PORT", "8080")),
    apiKey=os.environ.get("CAPSKIP_API_KEY", "capskip"),
)

def fresh_token(sitekey, page_url):
    result = solver.recaptcha(sitekey=sitekey, url=page_url)
    return result["code"]   # the token, and Locust never sees it

Ham uç noktalara karşı kendi kodunuzu yazmayı tercih ederseniz aynı kural geçerlidir: self.client değil, düz bir requests.Session kullanın. requests kütüphanesiyle doğrudan yapılan istekler Locust tarafından kaydedilmez; burada tam olarak istediğiniz de budur. Bu iki uç nokta şurada belgelenmiştir: CapSkip API dokümantasyonu.

Bunun tersini yapmanız gereken tek durum, bilerek çözücünün kendisine yük testi uyguladığınız durumdur. O zaman isteği bir name argümanıyla self.client üzerinden gönderin; böylece her çözüm, sitekey başına bir satır yerine tek bir satır altında gruplanır ve o satırı geri kalanından ayrı okursunuz.

Adım 2: çözümünüz gerçekte ne sıklıkta çalışıyor?

Locust size onu koyabileceğiniz dört yer sunar ve bunlar birbirinden kat kat farklıdır. Birini seçmeden önce çarpanı hesaplayın.

Çözümün bulunduğu yerKaç kez çalışır
Bir task fonksiyonunun içindeKullanıcı başına her yinelemede bir kez. Dakikada binlerce
on_start metodundaSimüle edilen kullanıcı başına bir kez. Beş yüz kullanıcı, beş yüz çözüm demektir
Bir test_start dinleyicisindeNode başına bir kez; bu, çalıştırma başına bir kez demek değildir. Adım 3’e bakın
Master ile sınırlandırılmış bir test_start dinleyicisindeÇalıştırma başına bir kez, sonra da paylaştırılması gerekir

Neredeyse herkes için varsayılan yanıt on_start’tır. Bir kullanıcı çalışmaya başladığında on_start’ı çağırır; böylece simüle edilen her kullanıcı kendi token’ını alır, onu kendi oturumu boyunca tutar ve greenlet’ler arasında hiçbir şeyin aktarılması gerekmez. Gerçek bir kullanıcının yaptığına uyan biçim de budur.

from locust import HttpUser, task, between

class Signup(HttpUser):
    wait_time = between(1, 3)

    def on_start(self):
        # One solve per simulated user, off the statistics.
        self.token = fresh_token(SITEKEY, PAGE_URL)

    @task
    def submit(self):
        self.client.post("/signup", data={
            "email": "[email protected]",
            "g-recaptcha-response": self.token,
        })

Beş yüz çözüm pahalı geliyor, çünkü sayaçlı bir serviste gerçekten pahalıdır. Çoğu yük testi rehberinin bir kez çözüp sonucu paylaşmak için kendini paralamasının nedeni budur. Kendi donanımınızda çalışan bir çözücüyle bu sayı bir bütçe sorusu olmaktan çıkar ve bir kapasite sorusuna dönüşür; bu da yanıtlanması çok daha kolay bir sorudur: rampayı çalıştırın ve makineyi izleyin.

Adım 3: test_start her node’da tetiklenir, çalıştırma başına bir kez değil

İnsanları şaşırtan Locust ayrıntısı budur ve kendini, bir şeyin tam katı olan bir çözüm sayısı olarak gösterir. Dokümantasyon, yeni bir yük testi başlatıldığında test_start’ın her node’da tetiklendiğini söyler. Dört worker süreciyle çalıştırın, beş node’unuz olur; yani düz bir test_start dinleyicisindeki çözüm beş kez çalışır.

Runner türünü kontrol ederek onu sınırlandırın. Locust tam da bunun için MasterRunner ve WorkerRunner sınıflarını sunar ve kendi dağıtık rehberindeki kalıp, hangisinin üzerinde olduğunuzu sınamaktır.

from locust import events
from locust.runners import WorkerRunner

@events.init.add_listener
def on_init(environment, **kwargs):
    # Workers listen. Registering here runs before the test starts.
    if isinstance(environment.runner, WorkerRunner):
        environment.runner.register_message("captcha_token", take_token)

@events.test_start.add_listener
def on_test_start(environment, **kwargs):
    # The master solves once and broadcasts. Workers skip this.
    if not isinstance(environment.runner, WorkerRunner):
        environment.runner.send_message(
            "captcha_token", fresh_token(SITEKEY, PAGE_URL)
        )

def take_token(environment, msg, **kwargs):
    environment.shared_token = msg.data

O blokla ilgili iki nokta. Handler imzası sabittir: environment, message ve keyword argümanlarını alır ve veri msg.data olarak gelir. Bir handler uzun sürecekse, concurrent değeri True olacak şekilde kaydedin; böylece Locust’un heartbeat ve diğer sistem mesajlarını bloke etmek yerine kendi greenlet’inde çalışır. Yalnızca bir dize saklayan handler’ın buna ihtiyacı yoktur. İçinde çözüm yapan handler’ın vardır.

Bunların hiçbirini kurmadan önce bir sonraki bölümü okuyun, çünkü çalıştırma başına tek çözüm zaten genellikle yanlış bir hedeftir.

Adım 4: bir token uzun bir testi atlatamaz

Bir reCAPTCHA token’ı yaklaşık iki dakika geçerlidir. Bir yük testi ise genellikle iki dakikadan uzundur. Yani derli toplu mimari, yani test başında bir kez çözüp her worker’a yayınlamak, ilk birkaç dakikanın geçtiği ve sonrasındaki her şeyin süresi dolmuş bir token yüzünden başarısız olduğu bir çalıştırma üretir; üstelik başarısızlıklar uygulamanıza yazılır.

Bu başarısızlığı görür görmez tanımakta fayda var; kuyruk işlerini zincirleyenleri de aynı şey yakalıyor. Konu şu rehberde adım adım ele alınıyor: reCAPTCHA token süresinin dolması.

Bu yüzden paylaşılan token kalıbını yalnızca test kısaysa ya da token her yinelemede değil, rampa sırasında bir kez gerekiyorsa kullanın. Aksi halde on_start içinde kullanıcı başına çözüm yapın; saatlerce süren bir dayanıklılık testi içinse kullanıcının içinde belirli aralıklarla token’ı yenileyin.

import time

class Signup(HttpUser):
    wait_time = between(1, 3)

    def on_start(self):
        self.token = fresh_token(SITEKEY, PAGE_URL)
        self.solved_at = time.monotonic()

    @task
    def submit(self):
        # Refresh before the token ages out, not after it fails.
        if time.monotonic() - self.solved_at > 90:
            self.token = fresh_token(SITEKEY, PAGE_URL)
            self.solved_at = time.monotonic()
        self.client.post("/signup", data={
            "g-recaptcha-response": self.token,
        })

Yüz yirmi yerine doksan saniye; böylece yenileme, token hâlâ geçerliyken gerçekleşir.

Adım 5: Locust gevent tabanlıdır, bu yüzden senkron istemciyi kullanın

Locust her kullanıcıyı kendi greenlet’i içinde çalıştırır ve gevent kullanarak olay tabanlı çalışır. Dokümantasyonu, testlerinizi geri çağırmalarla değil de normal, bloke eden Python kodu olarak yazmanızı sağlayan şeyin bu olduğunu vurgular. Burada doğal stil bloke etmektir, bu yüzden başvurulacak istemci düz CapSkip istemcisidir.

Bir locustfile içinde AsyncCapSkip’e başvurmayın. Python’da bu, bir takma ad değil gerçek bir async uygulamasıdır; bu da onu bir asyncio programında doğru istemci yapar, ama Locust öyle bir program değildir. Onu bekleyen bir olay döngüsü yoktur ve bir greenlet içinde kullanıcı başına bir döngü başlatmak, gevent’in zaten yaptığı şeyi geri kazanmak için çok fazla mekanizma demektir.

Yoklama davranışı da burada yardımcı olur. İstemci sabit aralıkla yoklama yapmaz. Saniyenin dörtte biriyle başlar ve pollingInterval değerine doğru geri çekilir; böylece hızlı bir çözüm, sabit bir gecikmeyi beklemek yerine hızlıca döner. Birlikte rampalanan beş yüz greenlet boyunca bu fark, rampanızın büyük kısmını oluşturur. Başka bir yerde, bir asyncio programında aynı anda çözülecek bir CAPTCHA yığınınız varsa, o durum şurada ele alınıyor: Python’da paralel CAPTCHA çözme.

Adım 6: çözücüyü worker’ların erişebileceği bir yerde çalıştırmak

Yük üreticileri nadiren başka bir şeyle aynı makinede durur. Yük gerçek olsun diye kendi makinelerini, birkaç makineyi ya da bir container havuzunu alırlar. CapSkip Windows üzerinde çalışır, worker’larınız ise çalışmıyor olabilir.

İki bağlantı modu vardır. Local, 127.0.0.1 adresine bağlanır ve yalnızca o cihaza yanıt verir. Server, ağ adresinize veya genel IP adresinize bağlanır; böylece başka bir makine, bir container sunucusu ya da barındırılan bir runner aynı Windows makinesine API üzerinden ulaşabilir. İkisi de şurada bulunur: bağlantı ayarları. Server modu yalnızca çözücünün hangi adreste dinlediğini değiştirir. Donanım yine sizindir ve çözüm yine sayaçsızdır.

Locust’un çalıştığı yerHangi bağlantı modu
CapSkip ile aynı Windows makinesi, tek süreçLocal modu, host 127.0.0.1 olarak kalır
Ağınızdaki diğer makinelerde worker süreçleriÇözücünün LAN adresiyle Server modu
Container’lar veya barındırılan bir runnerStatik bir genel IP ve bir güvenlik duvarı kuralı ile Server modu

Adresi locustfile yerine ortamdan okuyun. Python istemcisi CAPSKIP_HOST, CAPSKIP_PORT ve CAPSKIP_API_KEY değerlerini kendi başına okumaz, bu yüzden yukarıdaki çözücü bunları okuyup istemciye iletir; böylece aynı dosya hem dizüstü bilgisayarınızda hem de bir worker filosunda değişmeden çalışır.

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

GördüğünüzNedenDüzeltme
Locust istatistik tablosunda çözücü satırlarıÇözüm self.client üzerinden geçtiCapSkip istemcisini ya da düz bir requests.Session kullanın
Uygulamanın gerçekte yaptığının çok üzerinde yüzdeliklerAynı neden. Çözüm süreleri ortalamaya dahil ediliyorAynı düzeltme. Başka hiçbir şeyin değişmesi gerekmez
Çözüm sayısı, worker sayınızın katıtest_start her node’da tetiklenirDinleyiciyi bir WorkerRunner kontrolüyle sınırlandırın
Çalıştırmanın birkaç dakika sonrasında her şey başarısız oluyorTest başında tek bir token çözüldü ve süresi dolduKullanıcı başına çözüm yapın ya da task içinde yenileyin
Aynı anda her kullanıcıdan NetworkExceptionCapSkip loopback üzerinde, Locust ise başka yerdeServer moduna geçin ve CAPSKIP_HOST değerini ayarlayın
Dağıtık bir çalıştırma sırasında heartbeat uyarılarıYavaş bir mesaj handler’ı runner’ı bloke ediyorconcurrent değerini True yaparak kaydedin
Bir ApiException içinde ERROR_WRONG_USER_KEYWorker ortamında CAPSKIP_API_KEY tanımlı değilWorker’larda ayarlayın ve onları yeniden başlatın

Sonuncusunun kendi rehberi var, çünkü aynı yanıt hem eksik bir anahtarı hem de yalnızca yanlış olan bir anahtarı kapsıyor: ERROR_WRONG_USER_KEY nasıl düzeltilir.

FAQ

Test başına bir kez mi yoksa kullanıcı başına bir kez mi çözmeliyim?

Testin tamamı iki dakikanın altında değilse kullanıcı başına bir kez. Bir token, gerçek bir yük testi bitmeden çok önce geçerliliğini yitirir; bu yüzden paylaşılan token sürümü sessizce hata yolunuzun testine dönüşür. Kullanıcı başına çözüm aynı zamanda daha dürüst bir simülasyondur, çünkü gerçek kullanıcıların her biri kendi token’ını getirir. Bundan kaçınmanın tek nedeni çözüm başına faturalandırmadır ve kendi donanımınızda çalışan bir çözücü bu nedeni ortadan kaldırır.

Çözüm, saniyedeki istek sayıma dahil oluyor mu?

self.client üzerinden geçmediği sürece hayır. Locust istatistiklerini kendi oturumundan oluşturur; bu yüzden CapSkip istemcisiyle veya düz bir requests.Session ile gönderilen her şey rapora görünmez. Bu varsayılan davranıştır ve hiçbir yapılandırma gerektirmez.

Bu, FastHttpUser ile çalışır mı?

Evet ve hiçbir şey değişmez. FastHttpUser, istemciyi daha hızlı bir uygulamayla değiştirir; darboğaz yük üreticisinin kendisi olduğunda bunu yapmaya değer, ama çözüm zaten en başından beri o istemciden geçmiyordu. on_start metodu ve olay dinleyicileri aynı şekilde davranır.

Bunun k6 rehberinden farkı nedir?

Tavsiye neredeyse taban tabana zıt ve bunun iyi bir nedeni var. k6 bir Node paketi kuramaz; bu yüzden o rehber ham HTTP API üzerinden ilerler ve çözümü setup aşamasında bir kez yapar, çünkü orada tekrarlamak gerçekten zahmetlidir. Locust ise Python’dur, istemci normal şekilde kurulur ve kullanıcı başına çözüm hem kolay hem de daha doğrudur. Karşılaştırma şurada: k6 yük testi rehberi.

Kısa özet

Çözümü CapSkip istemcisiyle yapın; böylece istek ne self.client’a ne de istatistiklerinize ulaşır. Çağrıyı on_start içine koyun ki simüle edilen kullanıcı başına bir token olsun; çalıştırma yaklaşık doksan saniyeden uzun sürüyorsa token’ı task içinde yenileyin. Çözümü bir test_start dinleyicisinde yapıyorsanız, o olay her node’da tetiklendiği için dinleyiciyi bir WorkerRunner kontrolüyle sınırlandırın. Locust’un eşzamanlılığı gevent olduğundan senkron istemcide kalın. Yük üreticileri çözücünün kendi makinesinde değilse CapSkip’i Server modunda çalıştırın.

Rampayı boyutlandırmadan önce netleştirilecek bir nokta var: CapSkip, sınırsız captcha çözücü olarak zaten sahip olduğunuz donanım üzerinde çalışır; bin sanal kullanıcının her birinin kendi doğrulamasını çözmesi, on kullanıcının çözmesiyle tam olarak aynı maliyettedir.