Cara Memecahkan CAPTCHA di Locust Tanpa Mengacaukan Statistik Anda

Pemecahan captcha di Locust harus dijauhkan dari dua tempat: statistik Anda dan jalur per iterasi. Locust melaporkan setiap request yang dilakukan lewat self.client, jadi memecahkan CAPTCHA melalui objek itu menjatuhkan waktu respons pemecah ke dalam laporan yang sedang Anda baca. Dan pemecahan yang ditaruh di dalam sebuah task akan berjalan pada setiap iterasi dari setiap pengguna. Keduanya tidak sulit dihindari begitu Anda tahu di mana batasnya.
Apa yang Anda butuhkan
- Locust 2.x di Python 3.10 atau yang lebih baru, yang juga merupakan kebutuhan client CapSkip.
- CapSkip yang berjalan di sebuah mesin Windows, dengan client Python terinstal bersama locustfile Anda.
- sitekey dan URL halaman dari formulir yang dilindungi, dioper masuk alih-alih ditemukan ulang oleh setiap pengguna.
- Mode Server jika Locust berjalan di tempat selain mesin pemecah itu sendiri, yang mencakup setiap container dan setiap mesin worker. Itu hanya satu pengaturan di bagian pengaturan koneksi.
# pip install capskip pip install -U locust capskip
Langkah 1: jauhkan pemecahan dari self.client
Inilah bagian yang diam-diam merusak sebuah laporan. Dokumentasi Locust menyatakan dengan jelas apa itu self.client: sebuah instance HttpSession, yang merupakan subclass sekaligus pembungkus requests.Session, dan yang ditambahkannya adalah pelaporan hasil request ke dalam Locust. Berhasil dan gagal, waktu respons, panjang respons, nama. Semua yang dikirim lewat objek itu berakhir di tabel statistik.
Satu pemecahan memakan waktu beberapa detik. Endpoint aplikasi Anda memakan waktu beberapa milidetik. Taruh yang satu di tabel yang sama dengan yang lain dan persentil ke-95 Anda berubah menjadi ukuran berapa lama sebuah CAPTCHA dikerjakan, angka yang tidak diminta siapa pun.
Kabar baiknya, perbaikannya adalah tidak melakukan apa-apa. Client CapSkip punya transport HTTP sendiri dan tidak pernah menyentuh self.client, jadi secara default sebuah pemecahan tidak terlihat oleh statistik Locust. Locust hanya mencatat apa yang lewat session miliknya sendiri.
# 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 itJika Anda lebih suka membuat sendiri terhadap endpoint mentahnya, aturan yang sama berlaku: pakai requests.Session biasa, bukan self.client. Request yang dibuat langsung dengan library requests tidak dicatat oleh Locust, dan itulah yang Anda inginkan di sini. Kedua endpoint tersebut didokumentasikan di dokumentasi API CapSkip.
Satu-satunya saat untuk melakukan sebaliknya adalah ketika Anda memang sengaja melakukan load test terhadap pemecahnya sendiri. Saat itu kirimkan lewat self.client dengan argumen name, supaya setiap pemecahan dikelompokkan dalam satu baris alih-alih satu baris per sitekey, lalu baca baris itu terpisah dari yang lain.
Langkah 2: seberapa sering pemecahan Anda benar-benar berjalan?
Locust memberi Anda empat tempat untuk menaruhnya dan bedanya bisa berlipat-lipat besarnya. Hitung dulu pengalinya sebelum memilih salah satu.
| Di mana pemecahan ditaruh | Berapa kali ia berjalan |
|---|---|
| Di dalam fungsi task | Sekali per iterasi, per pengguna. Ribuan per menit |
| Di dalam metode on_start | Sekali per pengguna simulasi. Lima ratus pengguna berarti lima ratus pemecahan |
| Di dalam listener test_start | Sekali per node, yang bukan berarti sekali per run. Lihat langkah 3 |
| Di dalam listener test_start yang dibatasi ke master | Sekali per run, lalu hasilnya harus dibagikan |
Jawaban default untuk hampir semua orang adalah on_start. Sebuah pengguna memanggil on_start saat ia mulai berjalan, jadi setiap pengguna simulasi mendapat token miliknya sendiri, menyimpannya untuk session-nya sendiri, dan tidak ada yang perlu dioper antar greenlet. Bentuk itu juga yang paling mirip dengan perilaku pengguna sungguhan.
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,
})Lima ratus pemecahan terdengar mahal karena dengan layanan berbasis kuota memang mahal. Itulah alasan sebagian besar panduan load testing memutar-mutar cara agar cukup memecahkan sekali lalu membagikan hasilnya. Dengan pemecah yang berjalan di perangkat keras Anda sendiri, angka itu berhenti menjadi pertanyaan anggaran dan berubah menjadi pertanyaan kapasitas, yang jauh lebih mudah dijawab: jalankan ramp-nya dan pantau mesinnya.
Langkah 3: test_start dipicu di setiap node, bukan sekali per run
Ini detail Locust yang mengejutkan banyak orang, dan tandanya adalah jumlah pemecahan yang pas merupakan kelipatan dari sesuatu. Dokumentasinya menyebut bahwa test_start dipicu di setiap node ketika sebuah load test baru dimulai. Jalankan dengan empat proses worker dan Anda punya lima node, jadi pemecahan di dalam listener test_start biasa akan berjalan lima kali.
Batasi dengan memeriksa tipe runner-nya. Locust menyediakan MasterRunner dan WorkerRunner justru untuk ini, dan pola dalam panduan distributed miliknya sendiri adalah menguji Anda sedang berada di yang mana.
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.dataDua hal tentang blok itu. Signature handler-nya sudah baku: ia menerima environment, message dan keyword argument, dan payload-nya datang sebagai msg.data. Dan jika sebuah handler akan memakan waktu lama, daftarkan dengan concurrent disetel ke True supaya ia berjalan di greenlet miliknya sendiri alih-alih memblokir heartbeat Locust dan pesan sistem lainnya. Handler yang hanya menyimpan sebuah string tidak membutuhkan itu. Handler yang memecahkan CAPTCHA di dalamnya membutuhkannya.
Baca bagian berikutnya sebelum membangun semua itu, karena satu pemecahan per run biasanya memang target yang keliru.
Langkah 4: sebuah token tidak bertahan sepanjang tes yang panjang
Token reCAPTCHA berlaku sekitar dua menit. Sebuah load test biasanya lebih lama dari dua menit. Jadi arsitektur yang terlihat rapi, satu pemecahan di awal tes yang disiarkan ke setiap worker, menghasilkan run di mana beberapa menit pertama lolos dan semua setelah itu gagal karena token kedaluwarsa, dengan kegagalan yang dituduhkan kepada aplikasi Anda.
Kegagalan itu layak dikenali begitu terlihat, dan itu kegagalan yang sama yang menjebak orang saat merangkai job antrean. Kasusnya dibahas tuntas di panduan tentang kedaluwarsa token reCAPTCHA.
Jadi pakai pola token bersama hanya ketika tesnya singkat, atau ketika token hanya dibutuhkan sekali selama ramp-up dan bukan pada setiap iterasi. Selain itu, lakukan pemecahan per pengguna di on_start, dan untuk soak test yang berjalan berjam-jam, perbarui secara terjadwal di dalam pengguna tersebut.
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,
})Sembilan puluh detik, bukan seratus dua puluh, supaya pembaruan terjadi selagi token masih berlaku.
Langkah 5: Locust memakai gevent, jadi gunakan client sinkron
Locust menjalankan setiap pengguna di dalam greenlet-nya sendiri dan bersifat event-based, dengan gevent. Dokumentasinya menegaskan bahwa inilah yang memungkinkan Anda menulis tes sebagai kode Python blocking biasa alih-alih dengan callback. Blocking adalah gaya asli di sini, jadi client CapSkip yang biasa itulah yang harus dipakai.
Jangan memakai AsyncCapSkip di dalam locustfile. Di Python ia benar-benar implementasi async, bukan sekadar alias, yang membuatnya menjadi client yang tepat di program asyncio, sedangkan Locust bukan program semacam itu. Tidak ada event loop yang menunggunya, dan menjalankan satu event loop per pengguna di dalam greenlet adalah mesin yang terlalu rumit hanya untuk mendapatkan kembali apa yang sudah dilakukan gevent.
Perilaku polling-nya juga membantu di sini. Client tidak melakukan polling pada interval tetap. Ia mulai dari seperempat detik lalu melambat menuju pollingInterval, jadi pemecahan yang cepat kembali dengan cepat alih-alih menunggu jeda tetap. Di lima ratus greenlet yang naik bersamaan, selisih itu adalah sebagian besar ramp Anda. Jika Anda memang punya sekumpulan CAPTCHA yang harus dipecahkan sekaligus di program asyncio di tempat lain, kasus itu dibahas di panduan memecahkan CAPTCHA secara paralel di Python.
Langkah 6: menjalankan pemecah di tempat yang bisa dijangkau worker
Generator beban jarang berada di mesin yang sama dengan hal lain. Mereka mendapat mesin sendiri, atau beberapa, atau sekumpulan container, justru supaya bebannya nyata. CapSkip berjalan di Windows, dan worker Anda mungkin tidak.
Ada dua mode koneksi. Local terikat ke 127.0.0.1 dan hanya melayani perangkat itu. Server terikat ke alamat jaringan atau IP publik Anda, sehingga mesin lain, sebuah container host atau hosted runner bisa menjangkau mesin Windows yang sama lewat API. Keduanya berada di bagian pengaturan koneksi, dan mode Server hanya mengubah di alamat mana pemecah mendengarkan. Perangkat kerasnya tetap milik Anda dan pemecahannya tetap tanpa batas.
| Di mana Locust berjalan | Mode koneksi yang mana |
|---|---|
| Mesin Windows yang sama dengan CapSkip, satu proses | Pakai mode Local, host tetap 127.0.0.1 |
| Proses worker di mesin lain dalam jaringan Anda | Pakai mode Server dengan alamat LAN pemecah CAPTCHA |
| Container atau hosted runner | Pakai mode Server dengan IP publik statis dan sebuah aturan firewall |
Baca alamatnya dari environment, bukan dari locustfile. Client Python tidak membaca CAPSKIP_HOST, CAPSKIP_PORT atau CAPSKIP_API_KEY dengan sendirinya, karena itulah solver di atas membacanya lalu meneruskannya ke konstruktor, jadi file yang sama bekerja tanpa perubahan di laptop Anda maupun di armada worker.
Kesalahan umum dan artinya
| Apa yang Anda lihat | Penyebab | Perbaiki |
|---|---|---|
| Baris pemecah muncul di tabel statistik Locust | Pemecahan dikirim lewat self.client | Gunakan client CapSkip, atau requests.Session biasa |
| Persentil jauh di atas kinerja aplikasi yang sebenarnya | Penyebab yang sama. Waktu pemecahan ikut dimasukkan ke dalam rata-rata | Perbaikan yang sama. Tidak ada lagi yang perlu diubah |
| Jumlah pemecahan merupakan kelipatan jumlah worker Anda | test_start dipicu di setiap node | Batasi listener dengan pemeriksaan WorkerRunner |
| Semuanya gagal beberapa menit setelah run dimulai | Satu token dipecahkan di awal tes dan sudah kedaluwarsa | Pecahkan per pengguna, atau perbarui di dalam task |
| NetworkException dari semua pengguna sekaligus | CapSkip ada di loopback sedangkan Locust di tempat lain | Beralih ke mode Server dan setel CAPSKIP_HOST |
| Peringatan heartbeat selama run terdistribusi | Handler pesan yang lambat memblokir runner | Daftarkan dengan concurrent disetel ke True |
| ERROR_WRONG_USER_KEY di dalam ApiException | CAPSKIP_API_KEY tidak disetel di environment worker | Setel di worker lalu mulai ulang worker tersebut |
Yang terakhir punya panduan tersendiri, karena respons yang sama mencakup key yang hilang dan key yang sekadar salah: cara memperbaiki ERROR_WRONG_USER_KEY.
FAQ
Haruskah saya memecahkan sekali per tes atau sekali per pengguna?
Sekali per pengguna, kecuali seluruh tes berlangsung kurang dari dua menit. Token kedaluwarsa jauh sebelum load test sungguhan selesai, jadi versi token bersama diam-diam berubah menjadi tes terhadap jalur error Anda. Pemecahan per pengguna juga simulasi yang lebih jujur, karena pengguna sungguhan masing-masing membawa tokennya sendiri. Satu-satunya alasan orang menghindarinya adalah tagihan per pemecahan, dan pemecah di perangkat keras Anda sendiri menghapus alasan itu.
Apakah pemecahan dihitung ke dalam request per detik saya?
Tidak, selama tidak lewat self.client. Locust menyusun statistiknya dari session miliknya sendiri, jadi apa pun yang dikirim dengan client CapSkip atau requests.Session biasa tidak terlihat oleh laporan. Itu perilaku default dan tidak perlu konfigurasi apa pun.
Apakah ini bekerja dengan FastHttpUser?
Ya, dan tidak ada yang berubah. FastHttpUser menukar client-nya dengan implementasi yang lebih cepat, yang memang berguna ketika generator bebannya sendiri yang menjadi bottleneck, tetapi pemecahan memang tidak pernah lewat client itu sejak awal. Metode on_start dan event listener berperilaku persis sama.
Apa bedanya dengan panduan k6?
Sarannya hampir berlawanan, dan ada alasan bagusnya. k6 tidak bisa memasang paket Node, jadi panduan itu memakai API HTTP mentah dan memecahkan sekali di tahap setup, karena mengulanginya di sana memang merepotkan. Locust adalah Python, client-nya terpasang secara normal, dan pemecahan per pengguna itu mudah sekaligus lebih akurat. Perbandingannya ada di panduan load test k6.
Versi singkatnya
Pecahkan dengan client CapSkip supaya request-nya tidak pernah sampai ke self.client dan tidak pernah sampai ke statistik Anda. Taruh pemanggilannya di on_start untuk satu token per pengguna simulasi, dan perbarui di dalam task jika run berlangsung lebih dari sekitar sembilan puluh detik. Jika Anda memang memecahkan di listener test_start, batasi dengan pemeriksaan WorkerRunner, karena event itu dipicu di setiap node. Tetap gunakan client sinkron, karena konkurensi Locust memakai gevent. Jalankan CapSkip dalam mode Server setiap kali generator beban tidak berada di mesin pemecah itu sendiri.
- Antarmuka client dan setiap jenis CAPTCHA yang dicakupnya ada di halaman pemecah CAPTCHA Python.
- Tantangan kotak centangnya sendiri dijelaskan di halaman pemecah reCAPTCHA v2.
Satu hal yang perlu dipastikan sebelum Anda menentukan besar ramp: CapSkip adalah pemecah captcha tanpa batas yang berjalan di perangkat keras yang sudah Anda miliki, jadi seribu pengguna simulasi yang masing-masing memecahkan tantangannya sendiri berbiaya persis sama dengan sepuluh pengguna.
