Bir Celery Görevi İçinde CAPTCHA Nasıl Çözülür (Python Kuyruğu)

Bir Celery captcha görevinin, geri kalan her şeyi şekillendiren tek bir kuralı vardır: her denemede sıfırdan çözmelidir. Celery size en az bir kez teslim garantisi verir, yani bir görev iki kez çalışabilir ve bir CapSkip sonucu yalnızca bir kez okunabilir. Bir captcha id’si saklayıp yeniden denemeden sonra kaldığınız yerden devam ederseniz elinize hiçbir şey geçmez. Çözün, token’ı kullanın ve bitirin; hepsi tek bir görev gövdesinin içinde.
Neye ihtiyacınız var
- Bir broker ile birlikte Celery 5, Redis ya da RabbitMQ. Broker seçimi ilerideki bir ayarı değiştirir.
- Bir Windows makinesinde çalışan CapSkip ve worker imajına kurulmuş Python istemcisi.
- Neredeyse her gerçek kurulumda Server modu. Worker’lar genellikle Linux konteynerlerinde çalışır, çözücü ise çalışmaz.
- Göreve gömülmek yerine görev argümanları olarak geçirilen sitekey ve sayfa URL’si.
# pip install capskip pip install -U celery[redis] capskip
Adım 1: görev
Çözümün tamamı tek bir çağrıdır. SDK gönderir, sorgular ve token’ı döndürür; böylece adımlar arasında taşınacak bir id ve kalıcı olarak saklanacak hiçbir şey olmaz.
# 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 hereOrada olmayanlara dikkat edin. Dönüş değerinde captcha id’si yok, token’ı göndermek için ikinci bir görev yok, sonradan kullanmak üzere saklanan bir sonuç yok. Token, onu üreten görevin içinde kullanılır. Bunun nedeni düzenlilik değil zamanlamadır ve aşağıda yeniden denemeler başlığı altında ele alınıyor.
Bu çağrı reCAPTCHA v2 içindir. CapSkip’in desteklediği diğer türler de aynı biçimdedir: invisible ya da enterprise değerini 1 olarak geçin, ya da version değerini bir action ile birlikte v3 yapın, ya da bunun yerine turnstile veya geetest çağırın. Tam yüzeyi şurada bulabilirsiniz: Python CAPTCHA çözücü sayfası.
Adım 2: iki zaman limiti ve bunları nereye koyacağınız
Celery’nin varsayılan olarak hiçbir zaman limiti yoktur, iki ayarda da. Bir ağ servisini bekleyen bir görev için bu yanlış bir varsayılandır, çünkü takılan bir çözüm bir worker yuvasını sonsuza kadar işgal eder.
İkisini de ayarlayın ve SDK’nın kendisinin izin verdiği sürenin üzerinde ayarlayın. CapSkip bir reCAPTCHA, Turnstile veya GeeTest çözümüne üç yüz saniye, bir görsel CAPTCHA’ya yüz yirmi saniye tanır; ikisi de istemci üzerinde yapılandırılabilir. Önce soft limit devreye girerse Celery göreviniz içinde SoftTimeLimitExceeded fırlatır ve SDK’nın kendi TimeoutException’ını kaybedersiniz; oysa asıl işe yarayan sinyal odur, çünkü size çözücüye ulaşıldığını ama işin bitmediğini söyler.
| Hangi ayar | Bir çözüm için önerilen değer | Neden |
|---|---|---|
| soft_time_limit | 330 saniye | Çözücünün kendi üst sınırının otuz saniye üzerinde, böylece önce SDK rapor eder |
| time_limit | 360 saniye | Son emniyet. Worker bu noktada süreci sonlandırır |
| İstemcideki recaptchaTimeout | 300 saniye, varsayılan değer | Beklemektense hızlıca hata almayı tercih ediyorsanız düşürün |
| İstemcideki defaultTimeout | 120 saniye, varsayılan değer | Yalnızca görsel CAPTCHA’lar. Dakikalar bir yana, nadiren saniyeler sürerler |
Görsel CAPTCHA’ları ve reCAPTCHA’yı aynı worker üzerinden çalıştırıyorsanız, yüksek limit çiftine sahip tek bir görev yerine ayrı limitleri olan ayrı görevler verin. Normalde bir saniyede biten bir işe konulan üç yüz saniyelik üst sınır, gerçek hataları beş dakika boyunca gizler.
Adım 3: çözmeye özgü yeniden deneme kuralı
Celery’nin otomatik yeniden denemeleri, yeniden başlamakta olan bir çözücü için tam olarak doğru, elinizde zaten tuttuğunuz bir token için tam olarak yanlıştır. Bu fark konusunda net olmakta fayda var.
retry_backoff True olarak ayarlandığında ilk yeniden deneme bir saniye, sonra iki, sonra dört, sonra sekiz saniye bekler ve jitter varsayılan olarak açıktır, yani gerçek gecikme o üst sınıra kadar rastgele bir değerdir. Üst sınır retry_backoff_max’tir ve varsayılanı altı yüz saniyedir. Bunu, yaklaşık iki dakika geçerli olan bir reCAPTCHA token’ı ile karşılaştırın.
Yani başarıyla çözen, token’ı saklayan, gönderimde hata alan ve sonra yeniden denenen bir görev, token’ın ömründen kat kat uzun bir gecikmenin ardından kaldığı yerden devam edebilir. Üretildiği anda kusursuzca geçerli olan bir token yüzünden başarısız olur ve log hedef siteyi suçlar. Bu hata biçimi şu kılavuzda ele alınıyor: reCAPTCHA token süresinin dolması.
Çözüm, yukarıdaki görev biçimidir: çözmeyi yeniden denemenin öncesinde değil içinde yapın.
autoretry_for listesini, bir aktarım sorununu tarif eden istisnalarla sınırlayın. NetworkException, CapSkip’e ulaşılamadığı anlamına gelir ve yeniden denemeye değer. ApiException ve ValidationException, isteğin yanlış olduğunu ve yine yanlış olacağını gösterir. TimeoutException ise bir yorum meselesidir ve genellikle üç değil bir kez yeniden denemeye değer.
Adım 4: acks_late ve bir sonucun neden yalnızca bir kez okunabildiği
Celery varsayılan olarak bir mesajı çalıştırmadan hemen önce onaylar, bu yüzden görevin ortasında ölen bir worker işi kaybeder. task_acks_late’i açmak onayı görev bittikten sonraya taşır, böylece çöken bir worker’ın işi yeniden teslim edilir ve tekrar çalışır. Başka bir yerde paraya mal olan işler için genellikle istediğiniz budur ve bir kez okuma kuralını önemli kılan da budur.
Bir CapSkip sonucu yalnızca bir kez okunabilir. İlk deneme CAPTCHA’yı gönderdi, token’ı okudu ve onaylamadan önce çöktüyse, yeniden teslim edilen deneme o id’yi tekrar okuyamaz. Yeniden göndermesi gerekir. Yukarıdaki görev tam olarak bunu yapar, çünkü denemeler arasında hiçbir durum tutmaz ve yeniden çözmenin bedeli ikinci bir ücret değil, kendi donanımınızdan birkaç saniyedir.
# 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
Sonuncusu, çözüm kuyruklarının çoğundaki sessiz performans hatasıdır. Varsayılan prefetch çarpanı dörttür, yani her worker süreci baştan dört mesaj ayırır. Milisaniyeler süren görevlerde bu bir kazançtır. Bir çözümü bekleyen görevlerde ise bu dördün üçü, sorgulamaktan başka bir şey yapmayan bir işin arkasında ayrılmış olarak bekler, bu sırada başka bir worker boş durur.
Adım 5: çözümleri ikiye katlayan broker ayarı
Broker’ınız Redis ise bir sayı daha var. Redis’te yerleşik bir onay mekanizması yoktur, bu yüzden Celery bunu bir visibility timeout ile taklit eder: mesajı başka bir worker’a yeniden teslim etmeden önce bir worker’ın görevi onaylamasını kaç saniye beklediği. Varsayılanı bir saattir ve kendi başına bir ayar olmak yerine broker_transport_options içinde yer alır.
Bir saat, üç yüz saniyelik bir çözümün rahatça üzerindedir, yani varsayılan güvenlidir. Sorun, biri başarısız işlerin daha hızlı toparlanması için bu değeri düşürdüğünde başlar, çünkü Celery’nin kendi dokümantasyonu, çalışma süresi visibility timeout değerini aşan bir görevin tekrar tekrar, bir döngü içinde çalıştırılacağı uyarısını yapar. O zaman her yavaş çözüm en az iki kez çalışır.
# 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 onaylamayı kendi içinde yapar ve eşdeğer bir ayarı yoktur; yavaş görevlerle dolu kuyruklarda onu tercih etmenin bir nedeni de budur.
Adım 6: çözücüyü worker’ların ulaşabileceği bir yerde çalıştırmak
Celery worker’ları genellikle bir küme üzerindeki Linux konteynerlerinde çalışır. CapSkip ise Windows üzerinde çalışır. Yani pratikte worker ile çözücü farklı makinelerdedir ve loopback bunun cevabı değildir.
CapSkip’in bunun için iki bağlantı modu vardır. Local, 127.0.0.1 adresine bağlanır ve yalnızca o cihaza hizmet eder. Server ise ağ adresinize ya da genel IP’nize bağlanır, böylece başka bir makine, bir konteyner sunucusu ya da barındırılan bir platform aynı Windows makinesine API üzerinden ulaşabilir. Her ikisi de şurada yer alır: 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.
Bu son nokta, sürekli tetiklenen bir kuyruğu çalıştırmayı en baştan makul kılan şeydir.
| Worker’lar nerede çalışıyor | Hangi bağlantı modu |
|---|---|
| CapSkip ile aynı Windows makinesinde | Local modu, host 127.0.0.1 olarak kalır |
| Docker içinde ya da ağınızdaki başka bir makinede | Çözücünün LAN adresiyle Server modu |
| Yönetilen bir platformda ya da bir bulut kümesinde | Statik bir genel IP ve bir güvenlik duvarı kuralı ile Server modu |
Host ve anahtarı koddan değil ortam değişkenlerinden okuyun. Python istemcisi CAPSKIP_HOST, CAPSKIP_PORT ve CAPSKIP_API_KEY değerlerini kendi başına okumaz; Adım 1’deki görevin bunları okuyup istemciye iletmesinin nedeni budur. Böylece bir worker konteynerinin bu değişkenlerden başka hiçbir şeye ihtiyacı olmaz.
Aynı anda çok sayıda çözme
İki yol var ve farklı iş biçimlerine uygunlar. CAPTCHA başına bir görev, paralelliği de worker eşzamanlılığının sağlaması normal cevaptır ve yukarıdaki prefetch notu da bununla ilgilidir. Birlikte gelen bir yığın için ise Python istemcisinin gerçek bir async uygulaması vardır, böylece tek bir görev bir yığını kendi içinde toplayabilir.
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))]Bunu başka bir dile taşımadan önce bilmekte fayda var: Node ve .NET istemcileri de bir sınıfa AsyncCapSkip adını verir, ama orada bu ikinci bir uygulama değil bir takma addır. Anlam taşıdığı dil Python’dır. Tam karşılaştırma şu kılavuzda: Python’da paralel CAPTCHA çözme.
Sık görülen hatalar ve anlamları
| Gördüğünüz | Neden | Düzeltme |
|---|---|---|
| Her görevde NetworkException | CapSkip loopback'e bağlı ve worker başka bir yerde | Server moduna geçin ve CAPSKIP_HOST değerini çözücünün adresi olarak ayarlayın |
| TimeoutException yerine SoftTimeLimitExceeded | Soft limit, istemcinin kendi üst sınırının altında | soft_time_limit değerini 300’ün üzerine çıkarın ya da recaptchaTimeout değerini düşürün |
| Yavaş bir çözümde aynı iş iki kez çalışıyor | Redis visibility timeout değeri görevden kısa | Bu değeri hard time limit’in epey üzerine çıkarın |
| Bir yeniden deneme, süresi dolmuş bir token yüzünden başarısız oluyor | Token, yeniden denemenin içinde değil öncesinde çözülmüş | Çözümü yukarıdaki gibi görev gövdesinin içine taşıyın |
| Aynı captcha id’sini okumak hiçbir şey döndürmüyor | Bir CapSkip sonucu yalnızca bir kez okunabilir | Bir id’yi denemeler arasında asla saklamayın. Bunun yerine yeniden çözün |
| Bir ApiException içinde ERROR_WRONG_USER_KEY | Worker ortamında CAPSKIP_API_KEY tanımlı değil | Bunu worker ortamında ayarlayın ve worker’ı yeniden başlatın |
| Kuyruk birikirken worker’ların boş durması | Prefetch, uzun görevleri uzun görevlerin arkasına ayırıyor | Çözüm kuyruklarında worker_prefetch_multiplier değerini 1 yapın |
| Bir worker sonlandırıldığında işler kayboluyor | Geç onaylama kapalı | task_acks_late ve task_reject_on_worker_lost ayarlarını açın |
Anahtar hataları başlı başına okumaya değer, çünkü aynı yanıt hem eksik bir anahtarı hem de basitçe yanlış olan bir anahtarı kapsar: ERROR_WRONG_USER_KEY nasıl düzeltilir.
FAQ
Çözme ve gönderme iki ayrı görev mi olmalı?
Hayır. Cazip bir ayrım, çünkü iki yarı farklı nedenlerle başarısız olur ve izleme ekranında bir zincir daha derli toplu görünür. Ama bir reCAPTCHA token’ı yaklaşık iki dakika yaşar ve kuyruğa alınmış bir görev bundan daha uzun süre bekleyebilir, bu yüzden ikinci yarı düzenli olarak süresi çoktan dolmuş bir token ile çalışır. Onları bir arada tutun ve tamamının tek bir birim olarak yeniden denenmesine izin verin. Yeniden çözmenin size maliyeti kendi makinenizden birkaç saniyedir.
acks_late bir CAPTCHA çözümüyle birlikte güvenli mi?
Evet, görev kaldığı yerden devam etmek yerine yeniden çözdüğü sürece. Geç onaylama, çöken bir worker’ın işinin yeniden teslim edileceği ve ikinci kez çalışacağı anlamına gelir, yani görevin tekrarlanmaya elverişli olması gerekir. Her seferinde yeni bir CAPTCHA gönderen bir görev elverişlidir. Bir id saklayıp onu yeniden okumaya çalışan bir görev değildir, çünkü bir sonuç yalnızca bir kez okunabilir. Bu kılavuzdaki sürüm güvenli olanıdır.
Çözücü Windows üzerinde çalışırken worker’lar Linux üzerinde çalışabilir mi?
Evet ve normal düzen budur. Worker’ın tek ihtiyacı bir HTTP uç noktasına ulaşmaktır, yani çözücü Server modunda bir Windows makinesinde çalışırken worker ağın herhangi bir yerindeki bir Linux konteyneri olabilir. CAPSKIP_HOST değerini o makineye yönlendirin. Çözümle ilgili hiçbir şey, önemli olan anlamda ücretli ya da uzak hale gelmez: donanım hâlâ sizindir.
Bu, çözümleri Airflow içinde çalıştırmaktan nasıl farklı?
Airflow adımlardan oluşan bir grafiği zamanlar ve aralarında veri geçirir, bu yüzden oradaki asıl soru token’ın hangi sınırı geçtiğidir. Celery bir kuyruktur, dolayısıyla asıl soru aynı mesaj iki kez teslim edildiğinde ne olacağıdır. Çözme çağrısı aynıdır. Sınır sorusu tüm ayrıntısıyla şurada işleniyor: Airflow DAG kılavuzu.
Kısa özet
Çözümü ve token’ı kullanan şeyi tek bir göreve koyun. İstemcinin kendi zaman aşımının önce rapor vermesi için soft limit’i 330, hard limit’i 360 yapın. Yalnızca NetworkException üzerine yeniden deneyin ve yeniden denemenin kaldığı yerden devam etmek yerine yeniden çözmesini sağlayın, çünkü bir geri çekilme süresi bir token’dan çok daha uzun sürebilir ve bir sonuç yalnızca bir kez okunabilir. Geç onaylamayı açın, prefetch çarpanını 1’e düşürün ve Redis visibility timeout değerini hard limit’in çok üzerinde tutun. Worker’lar çözücünün kendi makinesinde değilse CapSkip’i Server modunda çalıştırın.
- reCAPTCHA v2 onay kutusunun kendisi şurada ele alınır: reCAPTCHA v2 çözücü sayfası.
- İstemcinin arkasındaki ham uç noktalar şurada belgelenmiştir: CapSkip API dokümantasyonu.
Kuyruğu boyutlandırmadan önce tartmaya değer son bir nokta: CapSkip bir captcha çözücü olarak zaten sahip olduğunuz donanımda çalışır, bu yüzden onu var gücüyle çalıştıran yüz worker ile aynı işleri tek başına yavaşça bitiren bir worker aynı şeye mal olur: hiçbir şeye.
