Cara Memecahkan CAPTCHA di Dalam Celery Task (Antrean Python)

celery captcha - How to Solve CAPTCHAs in a Celery Task (Python Queue)

Sebuah Celery task untuk captcha punya satu aturan yang membentuk semua hal lainnya: ia harus memecahkan dari nol pada setiap percobaan. Celery memberi Anda pengiriman at-least-once, jadi sebuah task bisa berjalan dua kali, dan hasil CapSkip hanya bisa dibaca sekali. Simpan sebuah id captcha lalu lanjutkan dari sana setelah percobaan ulang, dan Anda tidak akan mendapat apa-apa. Pecahkan, pakai tokennya, lalu selesai, semuanya di dalam satu badan task.

Apa yang Anda butuhkan

  • Celery 5 dengan sebuah broker, Redis atau RabbitMQ. Pilihan broker akan mengubah satu pengaturan nanti.
  • CapSkip berjalan di komputer Windows, dengan klien Python terinstal di image worker.
  • Pakai mode Server, di hampir semua deployment nyata. Worker biasanya berjalan di kontainer Linux, sedangkan pemecah CAPTCHA tidak.
  • Sitekey dan URL halaman, dioper sebagai argumen task alih-alih ditanam di dalam task.
# pip install capskip
pip install -U celery[redis] capskip

Langkah 1: task-nya

Seluruh pemecahan hanya satu panggilan. SDK mengirim, melakukan polling dan mengembalikan token, jadi tidak ada id yang perlu dibawa antar langkah dan tidak ada yang perlu disimpan.

# pip install capskip
import os
from celery import Celery
from capskip import CapSkip, NetworkException

app = Celery("solves", broker="redis://redis:6379/0")

# CAPSKIP_HOST is the solver machine. Loopback only works if the
# worker runs on the same Windows box as CapSkip.
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"),
)

@app.task(
    bind=True,
    autoretry_for=(NetworkException,),
    retry_backoff=True,
    max_retries=3,
    soft_time_limit=330,
    time_limit=360,
)
def solve_and_submit(self, sitekey, page_url):
    result = solver.recaptcha(sitekey=sitekey, url=page_url)
    return submit_form(page_url, result["code"])   # token, used here

Perhatikan apa yang tidak ada di dalamnya. Tidak ada id captcha di nilai kembalian, tidak ada task kedua untuk mengirim token, tidak ada hasil yang disimpan untuk nanti. Token dipakai di task yang sama dengan yang membuatnya. Alasannya soal waktu, bukan soal kerapian, dan itu dibahas di bagian percobaan ulang di bawah.

Panggilan itu adalah untuk reCAPTCHA v2. Tipe lain yang didukung CapSkip berbentuk sama: oper invisible atau enterprise bernilai 1, atau version bernilai v3 dengan sebuah action, atau panggil turnstile atau geetest. Seluruh cakupannya ada di halaman pemecah CAPTCHA Python.

Langkah 2: dua batas waktu, dan di mana menaruhnya

Celery tidak punya batas waktu secara default, pada kedua pengaturannya. Itu default yang salah untuk sebuah task yang menunggu layanan jaringan, karena pemecahan yang macet akan menempati satu slot worker selamanya.

Setel keduanya, dan setel di atas batas yang diizinkan SDK itu sendiri. CapSkip memberi tiga ratus detik untuk pemecahan reCAPTCHA, Turnstile atau GeeTest dan seratus dua puluh detik untuk CAPTCHA gambar, keduanya bisa dikonfigurasi di klien. Jika batas lunak yang lebih dulu terpicu, Celery memunculkan SoftTimeLimitExceeded di dalam task Anda dan Anda kehilangan TimeoutException milik SDK, yang sebenarnya sinyal yang lebih berguna karena ia memberi tahu bahwa pemecah CAPTCHA berhasil dijangkau tetapi tidak selesai.

Pengaturan yang manaNilai yang disarankan untuk sebuah pemecahanMengapa
soft_time_limit330 detikTiga puluh detik di atas batas maksimum pemecah CAPTCHA itu sendiri, agar SDK yang melapor lebih dulu
time_limit360 detikJaring pengamannya. Worker mematikan prosesnya pada titik ini
recaptchaTimeout di klien300 detik, nilai defaultTurunkan jika Anda lebih memilih gagal cepat daripada menunggu
defaultTimeout di klien120 detik, nilai defaultHanya untuk CAPTCHA gambar. Jarang memakan waktu hitungan detik, apalagi menit

Jika Anda menjalankan CAPTCHA gambar dan reCAPTCHA lewat worker yang sama, beri keduanya task terpisah dengan batas terpisah, bukan satu task dengan pasangan angka yang lebih tinggi. Batas tiga ratus detik pada pekerjaan yang biasanya selesai dalam satu detik akan menyembunyikan kegagalan nyata selama lima menit.

Langkah 3: aturan percobaan ulang yang khusus untuk pemecahan CAPTCHA

Percobaan ulang otomatis milik Celery sangat tepat untuk pemecah CAPTCHA yang sedang restart dan sangat salah untuk token yang sudah Anda pegang. Perbedaannya layak dijelaskan dengan cermat.

Dengan retry_backoff disetel True, percobaan ulang pertama menunggu satu detik, lalu dua, lalu empat, lalu delapan, dan jitter aktif secara default sehingga jeda sebenarnya adalah nilai acak sampai batas maksimum itu. Batasnya adalah retry_backoff_max, yang defaultnya enam ratus detik. Bandingkan itu dengan token reCAPTCHA, yang hanya berlaku sekitar dua menit.

Jadi sebuah task yang berhasil memecahkan, menyimpan token, gagal saat mengirim lalu dicoba ulang bisa dilanjutkan setelah jeda yang beberapa kali lipat lebih panjang daripada masa hidup token itu. Task itu akan gagal dengan token yang tadinya benar-benar valid saat dibuat, dan log akan menyalahkan situs targetnya. Mode kegagalan itu dibahas dalam panduan tentang kedaluwarsa token reCAPTCHA.

Perbaikannya adalah bentuk task di atas: pecahkan di dalam percobaan ulang, bukan sebelum percobaan ulang.

Batasi autoretry_for hanya pada eksepsi yang menggambarkan masalah transport. NetworkException berarti CapSkip tidak bisa dijangkau, dan itu layak dicoba ulang. ApiException dan ValidationException berarti permintaannya salah dan akan tetap salah. TimeoutException adalah soal pertimbangan, dan biasanya layak dicoba ulang satu kali, bukan tiga kali.

Langkah 4: acks_late, dan mengapa sebuah hasil hanya bisa dibaca sekali

Secara default Celery memberi acknowledgement pada sebuah pesan tepat sebelum menjalankannya, sehingga worker yang mati di tengah task akan kehilangan pekerjaan itu. Mengaktifkan task_acks_late memindahkan acknowledgement ke setelah task selesai, sehingga pekerjaan dari worker yang crash dikirim ulang dan dijalankan lagi. Itu biasanya yang Anda inginkan untuk pekerjaan yang menimbulkan biaya di tempat lain, dan itu pula yang membuat aturan baca sekali menjadi penting.

Hasil CapSkip hanya bisa dibaca sekali. Jika percobaan pertama sudah mengirim tantangannya, membaca tokennya, lalu crash sebelum memberi acknowledgement, percobaan yang dikirim ulang tidak bisa membaca ulang id itu. Ia harus mengirim ulang. Task di atas melakukan persis hal itu, karena ia tidak menyimpan state apa pun antar percobaan, dan biaya memecahkan ulang hanya beberapa detik perangkat keras Anda sendiri, bukan tagihan kedua.

# Redelivery is safe here because the task resolves rather
# than resuming. Pair it with reject_on_worker_lost so a
# killed worker requeues instead of dropping the job.
app.conf.task_acks_late = True
app.conf.task_reject_on_worker_lost = True

# Long tasks and a prefetch of 4 means idle workers sit on
# queued jobs. Drop it to 1 for solve queues.
app.conf.worker_prefetch_multiplier = 1

Yang terakhir itu adalah bug performa yang diam-diam ada di kebanyakan antrean pemecahan CAPTCHA. Prefetch multiplier bawaannya adalah empat, jadi setiap proses worker memesan empat pesan di muka. Untuk task yang selesai dalam hitungan milidetik, itu keuntungan. Untuk task yang menunggu sebuah pemecahan, tiga dari empat pesan itu tertahan di belakang pekerjaan yang tidak melakukan apa pun selain polling, sementara worker lain tidak punya pekerjaan.

Langkah 5: pengaturan broker yang menggandakan pemecahan

Jika broker Anda Redis, ada satu angka lagi. Redis tidak punya acknowledgement bawaan, jadi Celery menirunya dengan sebuah visibility timeout: jumlah detik yang ia tunggu agar sebuah worker memberi acknowledgement pada task sebelum pesannya dikirim ulang ke worker lain. Nilai defaultnya satu jam, dan ia berada di dalam broker_transport_options, bukan sebagai pengaturan tersendiri.

Satu jam nyaman berada di atas pemecahan tiga ratus detik, jadi nilai default itu aman. Masalah muncul ketika seseorang menurunkannya agar pekerjaan yang gagal pulih lebih cepat, karena dokumentasi Celery sendiri memperingatkan bahwa task yang waktu eksekusinya melebihi visibility timeout akan dijalankan lagi, dan lagi, dalam sebuah loop. Setiap pemecahan yang lambat lalu berjalan setidaknya dua kali.

# Keep this above your hard time limit, not near it.
# 3600 is the default and it is fine. If you must lower it,
# stay well clear of the 360 second time_limit above.
app.conf.broker_transport_options = {"visibility_timeout": 3600}

RabbitMQ memberi acknowledgement secara native dan tidak punya pengaturan setara, dan itu salah satu alasan untuk lebih memilihnya pada antrean yang penuh task lambat.

Langkah 6: menjalankan pemecah CAPTCHA di tempat yang bisa dijangkau worker

Worker Celery biasanya berjalan di kontainer Linux pada sebuah cluster. CapSkip berjalan di Windows. Jadi dalam praktiknya worker dan pemecah CAPTCHA berada di komputer yang berbeda, dan loopback bukan jawabannya.

CapSkip punya dua mode koneksi untuk ini. Local mengikat ke 127.0.0.1 dan hanya melayani perangkat itu saja. Server mengikat ke alamat jaringan atau IP publik Anda, sehingga komputer lain, host kontainer atau platform hosting bisa menjangkau komputer Windows yang sama lewat API. Keduanya ada di pengaturan koneksi, dan mode Server hanya mengubah di alamat mana pemecah mendengarkan. Perangkat kerasnya tetap milik Anda dan pemecahannya tetap tanpa batas.

Poin terakhir itulah yang membuat antrean yang terus-menerus berjalan tetap masuk akal untuk dijalankan.

Di mana worker berjalanMode koneksi yang mana
Di mesin Windows yang sama dengan CapSkipPakai mode Local, host tetap 127.0.0.1
Di Docker atau di komputer lain pada jaringan AndaPakai mode Server dengan alamat LAN pemecah CAPTCHA
Di platform terkelola atau cluster cloudPakai mode Server dengan IP publik statis dan sebuah aturan firewall

Baca host dan key dari environment, bukan dari kodenya. Klien Python tidak membaca CAPSKIP_HOST, CAPSKIP_PORT atau CAPSKIP_API_KEY dengan sendirinya, karena itulah task di Langkah 1 membacanya lalu meneruskannya ke klien. Dengan begitu, kontainer worker hanya membutuhkan variabel tersebut dan tidak ada yang lain.

Memecahkan banyak sekaligus

Ada dua cara, dan keduanya cocok untuk bentuk pekerjaan yang berbeda. Satu task per CAPTCHA, dengan concurrency worker yang menangani paralelismenya, adalah jawaban yang normal dan itulah yang dibicarakan catatan prefetch di atas. Untuk batch yang datang bersamaan, klien Python punya implementasi async yang sungguhan, jadi satu task bisa mengumpulkan satu batch di dalam dirinya sendiri.

import asyncio
import os
from capskip import AsyncCapSkip

# AsyncCapSkip in Python is a real async client, not an alias.
async def solve_batch(pairs):
    solver = AsyncCapSkip(
        host=os.environ.get("CAPSKIP_HOST", "127.0.0.1"), port=8080)
    return await asyncio.gather(*[
        solver.recaptcha(sitekey=k, url=u) for k, u in pairs
    ])

@app.task(soft_time_limit=330, time_limit=360)
def solve_many(pairs):
    return [r["code"] for r in asyncio.run(solve_batch(pairs))]

Perlu diketahui sebelum Anda menyalinnya ke bahasa lain: klien Node dan .NET juga menamai sebuah kelas AsyncCapSkip, tetapi di sana itu hanya alias, bukan implementasi kedua. Python-lah satu-satunya tempat nama itu benar-benar berarti. Perbandingan lengkapnya ada di panduan tentang memecahkan CAPTCHA secara paralel di Python.

Kesalahan umum dan artinya

Apa yang Anda lihatPenyebabPerbaiki
NetworkException di setiap taskCapSkip terikat ke loopback sementara worker-nya ada di tempat lainBeralih ke mode Server dan setel CAPSKIP_HOST ke alamat pemecah CAPTCHA
SoftTimeLimitExceeded muncul alih-alih TimeoutExceptionBatas lunaknya berada di bawah batas maksimum milik klien itu sendiriNaikkan soft_time_limit di atas 300, atau turunkan recaptchaTimeout
Pekerjaan yang sama berjalan dua kali pada pemecahan yang lambatVisibility timeout Redis lebih pendek daripada task-nyaNaikkan jauh di atas batas waktu keras
Percobaan ulang gagal dengan token yang kedaluwarsaToken dipecahkan sebelum percobaan ulang, bukan di dalamnyaPindahkan pemecahan ke dalam badan task, seperti di atas
Membaca id captcha yang sama tidak mengembalikan apa punHasil CapSkip hanya bisa dibaca sekaliJangan pernah menyimpan sebuah id antar percobaan. Pecahkan ulang saja
ERROR_WRONG_USER_KEY di dalam sebuah ApiExceptionCAPSKIP_API_KEY tidak disetel di environment workerSetel di environment worker lalu restart worker-nya
Worker menganggur sementara antrean menumpukPrefetch memesan task panjang di belakang task panjangSetel worker_prefetch_multiplier ke 1 pada antrean pemecahan
Pekerjaan hilang ketika sebuah worker dimatikanAcknowledgement tertunda tidak aktifAktifkan task_acks_late dan task_reject_on_worker_lost

Kesalahan terkait key layak dibaca tersendiri, karena respons yang sama mencakup key yang hilang dan key yang memang salah: cara memperbaiki ERROR_WRONG_USER_KEY.

FAQ

Haruskah pemecahan dan pengiriman dipisah menjadi dua task?

Tidak. Pembagian itu memang menggoda, karena kedua bagiannya gagal karena alasan yang berbeda dan sebuah chain terlihat lebih rapi di monitor. Tetapi token reCAPTCHA hanya bertahan sekitar dua menit dan sebuah task yang mengantre bisa menunggu lebih lama dari itu, jadi bagian keduanya sering berjalan dengan token yang sudah kedaluwarsa. Satukan keduanya dan biarkan seluruhnya dicoba ulang sebagai satu kesatuan. Memecahkan ulang hanya memakan beberapa detik komputer Anda sendiri.

Apakah acks_late aman untuk pemecahan CAPTCHA?

Aman, selama task itu memecahkan ulang, bukan melanjutkan. Acknowledgement tertunda berarti pekerjaan dari worker yang crash dikirim ulang dan berjalan untuk kedua kalinya, jadi task itu harus aman untuk diulang. Task yang mengirim tantangan baru setiap kali memang aman. Task yang menyimpan sebuah id lalu mencoba membacanya ulang tidak aman, karena sebuah hasil hanya bisa dibaca sekali. Versi dalam panduan ini adalah bentuk yang aman.

Bisakah worker berjalan di Linux jika pemecah CAPTCHA berjalan di Windows?

Bisa, dan itulah susunan yang normal. Worker hanya perlu menjangkau sebuah endpoint HTTP, jadi ia bisa berupa kontainer Linux di mana pun pada jaringan sementara pemecah CAPTCHA berjalan di komputer Windows dalam mode Server. Arahkan CAPSKIP_HOST ke komputer itu. Tidak ada bagian dari pemecahan yang menjadi berbayar per pemakaian atau jarak jauh dalam arti yang penting: perangkat kerasnya tetap milik Anda.

Apa bedanya dengan menjalankan pemecahan di Airflow?

Airflow menjadwalkan sebuah graf langkah dan mengoper data di antaranya, jadi pertanyaan menariknya di sana adalah batas mana yang dilintasi token. Celery adalah sebuah antrean, jadi pertanyaan menariknya adalah apa yang terjadi ketika pesan yang sama dikirim dua kali. Panggilan pemecahannya identik. Pertanyaan soal batas itu dibahas lengkap di panduan DAG Airflow.

Versi singkatnya

Taruh pemecahan dan apa pun yang memakai tokennya di dalam satu task. Setel batas lunak 330 dan batas keras 360 agar timeout milik klien sendiri yang melapor lebih dulu. Coba ulang hanya pada NetworkException, dan biarkan percobaan ulang itu memecahkan dari awal, bukan melanjutkan, karena sebuah backoff bisa bertahan jauh lebih lama daripada sebuah token dan sebuah hasil hanya bisa dibaca sekali. Aktifkan acknowledgement tertunda, turunkan prefetch multiplier ke 1, dan jaga visibility timeout Redis tetap jauh di atas batas keras. Jalankan CapSkip dalam mode Server setiap kali worker tidak berada di komputer pemecah CAPTCHA itu sendiri.

Satu hal terakhir yang perlu ditimbang sebelum Anda menentukan ukuran antrean: CapSkip adalah pemecah captcha yang berjalan di perangkat keras yang sudah Anda miliki, jadi seratus worker yang menghajarnya dan satu worker yang mengerjakan pekerjaan yang sama sedikit demi sedikit sama-sama tidak berbiaya.