Selenium Grid’de (RemoteWebDriver) CAPTCHA Nasıl Çözülür

Bir Selenium Grid captcha çözümü, çoğu kişinin gözünden kaçan tek bir farkla, tam olarak yerel bir çözüm gibi çalışır. Tarayıcı node üzerinde çalışır. Test kodunuz çalışmaz. CapSkip çağrısı sizin test sürecinizde gerçekleşir; dolayısıyla çözücünün, tarayıcıyı barındıran makineden değil, pytest’i çalıştırdığınız yerden erişilebilir olması gerekir. Bu tek gerçeği doğru yönde kavrayın, geri kalanı yerel bir Chrome’a karşı yazacağınız kodun aynısıdır.
Neye ihtiyacınız var
- Erişebildiğiniz bir Selenium Grid. Standalone, hub ile node ya da tamamen dağıtık kurulum, istemci tarafından bakıldığında aynı davranır.
- Testlerinizi çalıştıran makineden erişilebilen, bir Windows makinesinde çalışan CapSkip.
- Grid adresi. Selenium 4, RemoteWebDriver isteklerini varsayılan olarak 4444 portunda dinler.
- Test edilen sitenin sitekey’i ve sayfa URL’si.
Çözücüyle aslında hangi makine konuşur
Herhangi bir şeyi yapılandırmadan önce üç kutuyu çizin, çünkü ikisi birbirinin yerine geçermiş gibi görünür ama geçmez.
| Hangi makine | Orada ne çalışır | CapSkip ile konuşur mu? |
|---|---|---|
| Test koşucunuz | pytest, SDK çağrısı, RemoteWebDriver | Evet. Konuşan tek makine budur |
| Grid hub’ı | Yönlendirme ve oturum kuyruğu | Hayır. Yalnızca oturumları yönlendirir |
| Tarayıcı node’u | Asıl tarayıcı | Hayır. Çözücüyü hiç görmez |
İnsanlar bunu şaşırtıcı derecede sık ters kuruyor; genellikle Docker’da çalışan kısım Grid node’ları olduğu ve ağ sorununu Docker’da bekledikleri için. Node tarafında ağ sorunu yok. Token oraya, tıpkı başka herhangi bir dizenin ulaştığı gibi, bir betik yürütme komutunun argümanı olarak sıradan WebDriver protokolü üzerinden ulaşır.
Yani ihtiyacınız olan bağlantı modu, testlerinizin nerede yürüdüğünden çıkar. CapSkip, 127.0.0.1 adresine bağlanıp yalnızca o cihaza hizmet veren Local’i ve ağ adresinize ya da genel IP’nize bağlanan Server’ı sunar; böylece başka bir makine, bir konteyner host’u ya da bir CI runner 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 nerede çalıştığını değiştirir, başka bir şeyi değil: donanım hâlâ sizindir ve hâlâ ölçümsüzdür.
| Testlerinizin çalıştığı yer | Hangi mod ve host değeri |
|---|---|
| Kendi Windows makinenizde, uzaktaki bir Grid’i sürerek | Local modu. Tarayıcı başka yerde olsa da host değeri 127.0.0.1 olarak kalır |
| Çözücüyle aynı ağdaki bir derleme ajanında | Server modu. Host değeri, çözücü makinenin LAN adresidir |
| Ağınızın dışında, barındırılan bir CI runner üzerinde | Statik genel IP’li Server modu, anahtar doğrulaması açık ve bir güvenlik duvarı kuralı |
Fark edilmeye değer olan ilk satır. Paylaşılan bir Grid’e karşı testleri yerelde çalıştıran bir geliştirici loopback adresinde kalır, çünkü çözüm masasından hiç ayrılmaz.
Adım 1: sürücüyü Grid’e yöneltin
Selenium 4, Grid adresini ve bir options nesnesini alır. Eski hub yol soneki artık gerekmiyor; temel URL yeterli.
# pip install selenium capskip
from selenium import webdriver
GRID = "http://grid.internal:4444"
options = webdriver.ChromeOptions()
options.add_argument("--no-sandbox")
# command_executor is the Grid, not the browser. Everything you
# call on this driver is a request over the wire to the node.
driver = webdriver.Remote(command_executor=GRID, options=options)Burada CAPTCHA ile ilgili hiçbir şey değişmez. Değişen şey, artık her sürücü çağrısının bir gidiş dönüş olmasıdır; bu da aşağıdaki zaman aşımı bölümünde önem kazanır.
Adım 2: çözün, sonra token’ı enjekte edin
Çözüm, test sürecinizde yerel bir fonksiyon çağrısıdır. Enjeksiyon ise node üzerinde bir betik yürütmedir. İkisini zihninizde ayrı tutun, kod kendini yazar.
# pip install capskip
import os
from capskip import CapSkip
# The host is 127.0.0.1 when the tests run on the solver machine,
# and the solver's address when they do not. It is never the node.
solver = CapSkip(
host=os.environ.get("CAPSKIP_HOST", "127.0.0.1"),
port=8080,
)
result = solver.recaptcha(
sitekey="YOUR_SITEKEY",
url="https://example.com/page-with-recaptcha",
)
# This runs on the node. The token travels as a script argument.
driver.execute_script(
"document.getElementById('g-recaptcha-response')"
".value = arguments[0];",
result["code"],
)Token’ı, etrafına betik dizesi kurmak yerine argüman olarak geçirmek, Grid’de yerele göre daha da önemlidir; çünkü betik serileştirilip node’a gönderilir. Dize birleştirme, tırnak hatalarının sessizce boş bir alana dönüştüğü yerdir.
Her reCAPTCHA varyantı, fazladan bir anahtar kelimeyle aynı yöntemdir: invisible 1 olarak, enterprise 1 olarak ya da version bir action adıyla birlikte v3 olarak ayarlanır. Turnstile ve GeeTest aynı biçime sahip kendi yöntemleridir ve parametre listesi şurada: CapSkip API dokümantasyonu.
Adım 3: yavaş bir çözümü öldüren oturum zaman aşımı
İşte Grid’e özgü olan hata ve gerçekten iyi bir örnek. Bir node, oturum zaman aşımı süresi boyunca hiçbir etkinlik görmediği her oturumu sonlandırır; bu sürenin varsayılanı 300 saniyedir. SDK bir yanıt için yoklama yaparken test süreciniz sürücüyü hiç çağırmaz. Tarayıcı orada hiçbir şey yapmadan bekler ve node’un bakış açısından oturum terk edilmiş görünür.
Bir reCAPTCHA v2 onay kutusunda bu hiç gündeme gelmez, çünkü yanıt genellikle bir dakikanın epey altında gelir. Turnstile doğrulama sayfalarında ve GeeTest’te, meşgul bir çözücüde ve SDK’nın kendi üst sınırına denk geldiğiniz her koşuda gündeme gelir. O üst sınır, recaptchaTimeout ile belirlenen 300 saniyedir, yani tam olarak node varsayılanı. Varsayılan değerlerde bu yarışı kazanamazsınız. Node’un boşta kalma sayacı, SDK yoklamaya başlamadan önce işlemeye başlar; bu yüzden önce oturum toplanır ve sonraki sürücü çağrısı, CAPTCHA ile ilgili bir şeyle değil, geçersiz bir oturum kimliğiyle başarısız olur.
Denenmeye değer sıraya göre üç çözüm.
- Node üzerindeki oturum zaman aşımını, oturum zaman aşımı seçeneğini kullanarak 300 saniyenin rahatça üzerine çıkarın. Dürüst çözüm budur ve hiçbir maliyeti yoktur.
- Çözüm sürerken oturumu meşgul tutun. Senkron istemci bloke olur; dolayısıyla bu, çözümü bir thread’e taşımak ya da asenkron istemciyi kullanmak, sonra da boşta kalma sayacını sıfırlamak için arada bir ucuz bir sürücü çağrısı yapmak demektir. İşe yarar, ama daha fazla hareketli parça pahasına.
- SDK’nın kendi üst sınırını düşürün ki önce o pes etsin ve testiniz yeniden denesin. Kendi seçtiğiniz bir üst sınırdan gelen TimeoutException, bir raporda ölü bir oturumdan daha kolay okunur.
Oradayken bilinmeye değer iki Grid sınırı daha var. Node başına azami oturum sayısı varsayılan olarak o node’daki işlemci sayısıdır ve gerçek paralelliğinizi asıl sınırlayan budur. Bir de kuyrukta, varsayılanı yine 300 saniye olan oturum isteği zaman aşımından daha uzun süre bekleyen yeni bir oturum isteği, daha bir tarayıcı başlamadan reddedilir.
Tam çalışan örnek
Sayfayı açan, çözen, enjekte eden ve gönderen eksiksiz bir test. Gönderim, enjeksiyondan hemen sonra bilerek gerçekleşir: bir reCAPTCHA token’ı yaklaşık iki dakika geçerlidir ve bir Grid her adım arasına gidiş dönüşler ekler. Bu konuda daha fazlası şu rehberde: reCAPTCHA token süresinin dolması.
# pip install selenium capskip
import os
from selenium import webdriver
from selenium.webdriver.common.by import By
from capskip import CapSkip
from capskip.exceptions import NetworkException, TimeoutException
GRID = "http://grid.internal:4444"
PAGE = "https://example.com/page-with-recaptcha"
def test_login_through_recaptcha():
options = webdriver.ChromeOptions()
driver = webdriver.Remote(command_executor=GRID, options=options)
try:
driver.get(PAGE)
# Read the sitekey off the rendered page rather than
# hardcoding it. It is on the node, so this is a round trip.
sitekey = driver.find_element(
By.CSS_SELECTOR, ".g-recaptcha"
).get_attribute("data-sitekey")
solver = CapSkip(host=os.environ.get("CAPSKIP_HOST", "127.0.0.1"))
result = solver.recaptcha(sitekey=sitekey, url=PAGE)
driver.execute_script(
"document.getElementById('g-recaptcha-response')"
".value = arguments[0];",
result["code"],
)
driver.find_element(By.CSS_SELECTOR, "form").submit()
except NetworkException:
raise AssertionError("CapSkip unreachable from the test runner")
except TimeoutException:
raise AssertionError("Solve did not finish before the ceiling")
finally:
driver.quit()Bir Grid’de sürücüyü her zaman bir finally bloğunda kapatın. Sızdıran yerel bir Chrome, kendi makinenizdeki başıboş tek bir süreçtir. Sızdırılan bir Grid oturumu ise zaman aşımı onu toplayana kadar o node’un oturum yuvalarından birini tutar; dört işlemciye göre boyutlandırılmış bir node’da bu, toplanana kadar kapasitenizin dörtte birinin gitmesi demektir.
Paralel oturumlar arasında çözüm çalıştırmak
Bir Grid’in amacı aynı anda birçok tarayıcı çalıştırmaktır ve bu oturumların her birinin kendi token’ına ihtiyacı vardır. Çözücü eşzamanlı gönderimleri kabul eder; dolayısıyla işe yarayan biçim, test paketinizi tek bir bloklayan çağrının arkasında sıraya dizmek yerine çözümü asenkron tutmaktır.
Python SDK’sı bunun için gerçek bir asenkron istemciyle gelir ve bunu açıkça söylemek gerekir, çünkü Node.js, PHP ve .NET SDK’larındaki eşdeğer sınıflar ayrı birer uygulama değil, takma adlardır. Toplu işleme kalıbı şurada anlatılıyor: Python ile CAPTCHA’ları paralel çözme kılavuzu, ve tarayıcıların uzakta olduğu durumda da değişmeden geçerlidir.
Bunlardan daha fazlasını çalıştırmanın hiçbir ek maliyeti yok. Üst sınırı olan şey Grid: node başına azami oturum sayısı ve onun önündeki kuyruk zaman aşımı. Test paralelliğini çözücüye göre değil, Grid’e göre boyutlandırın.
Sık görülen hatalar ve anlamları
| Gördüğünüz | Neden | Düzeltme |
|---|---|---|
| NetworkException, 8080 portunda bağlantı reddedildi | Çözücü Local modunda ve testler başka bir makinede çalışıyor | Server moduna geçin ve host değerini çözücünün adresi olarak ayarlayın |
| Node’u çözücüye erişecek şekilde yapılandırdınız ve hiçbir şey değişmedi | Node CapSkip’i hiç çağırmaz. Test süreciniz çağırır | Bunun yerine host değerini test koşucusundan çözücüye yöneltin |
| Uzun bir çözümün hemen ardından geçersiz oturum kimliği | SDK yoklama yaparken node, boştaki oturumu topladı | Node’un oturum zaman aşımını 300 saniyenin üzerine çıkarın |
| Yeni bir oturum isteği, tarayıcı başlamadan zaman aşımına uğruyor | Kuyruk dolu ve istek eskiyip düştü | Node ekleyin ya da oturum isteği zaman aşımını yükseltin |
| Betik çalıştıktan sonra yanıt alanı boş | Token betik dizesine birleştirildi ve tırnaklama bozuldu | Yukarıdaki örneklerdeki gibi, onu betik argümanı olarak geçirin |
| Geçerli bir token site tarafından reddediliyor | Çözüm ile gönderim arasında süresi doldu | Arada hiç bekleme olmadan, bir sonraki ifadede gönderin |
| Gönderimde ERROR_GOOGLEKEY | Sayfadan okunan sitekey boştu ya da yanlış elemandan geldi | Seçiciyi ve okumadan önce widget’ın render edilmiş olduğunu kontrol edin |
| Oturumlar tek bir node’da birikiyor | Bir test, sürücüyü kapatmadan çöktü | Tam örnekteki gibi, finally bloğunda kapatın |
FAQ
Grid node’larına bir şey kurmam gerekiyor mu?
Hayır. Node’lar tarayıcı çalıştırır, başka bir şey değil. SDK, test projenizin bir bağımlılığıdır, çözüm sizin test sürecinizde gerçekleşir ve node’a ulaşan tek şey, bir betik yürütmenin argümanı olarak token’dır. Yalnızca Windows üzerinde çalışan bir çözücüyle bir Linux Docker node’unun sorunsuz çalışmasının nedeni de budur: ikisi birbiriyle hiç konuşmaz.
Testlerim CI’da çalışıyor. Neyin dışarı açılması gerekir?
Çözücünün portunun, testleri çalıştıran makineye açılması. Runner barındırılıyorsa bu, statik genel IP’li Server modu, artı API anahtarı doğrulamasının açık olması ve tüm internetten daha dar bir güvenlik duvarı kuralı demektir. CI runner’larınız kendi ağınızda kendi barındırdığınız makinelerse LAN adresi yeterlidir ve hiçbir şeyin ağdan çıkması gerekmez. Her iki durumda da Grid etkilenmez, çünkü bu yolun parçası değildir.
Turnstile çözümünün ortasında oturumum neden öldü?
Çünkü node, oturum zaman aşımı süresi boyunca hiçbir etkinlik görmedi ve temizlik yaptı. Varsayılan 300 saniyedir ve SDK’nın Turnstile için kendi üst sınırı da 300 saniyedir; üstelik node’un boşta kalma sayacı önce işlemeye başlar, dolayısıyla bu varsayılan değerlerde node, SDK pes etmeden önce oturumu her zaman toplar. Node’un oturum zaman aşımını yükseltin ve hatanın ölü bir oturum yerine temiz bir istisna olarak geri dönmesi için SDK üst sınırını düşürmeyi değerlendirin.
Bu, yerel bir sürücüyle çözmekten farklı mı?
Yalnızca işlerin nerede çalıştığı bakımından. Çözüm çağrısı ile enjeksiyon aynıdır ve zaten mesele de budur. Farklar operasyoneldir: node’un boştaki oturum zaman aşımı, her sürücü çağrısındaki gidiş dönüş ve çözücü adresinin test koşucunuzun konumuyla belirlenmesi. Tek tarayıcılı sürümler şurada anlatılıyor: SeleniumBase rehberi ve undetected-chromedriver rehberi.
Kısa özet
Bir Grid’de tarayıcı yer değiştirir, kodunuz değiştirmez; bu yüzden çözücü adresini testlerinizin nerede çalıştığı belirler. RemoteWebDriver’ı 4444 portuna yöneltin, SDK’yı test sürecinden çağırın ve token’ı dize kurarak değil, betik argümanı olarak node’a geçirin. Yavaş bir çözümün oturumunun toplanmaması için node’un oturum zaman aşımını 300 saniyenin üzerine çıkarın, enjeksiyondan hemen sonra gönderin ve sürücüyü her zaman bir finally bloğunda kapatın.
- Framework’ün bütünü şurada anlatılıyor: Selenium CAPTCHA çözücü sayfası.
- Python istemci kütüphanesi şurada anlatılıyor: Python CAPTCHA çözücü sayfası.
- Doğrulamanın kendisi şurada anlatılıyor: reCAPTCHA v2 çözücü sayfası.
Test paketini ölçeklendirmeden önce bilinmeye değer: CapSkip bir captcha çözücü olup zaten sahip olduğunuz donanımda çalışır; dolayısıyla yüz paralel oturum ile tek bir oturum tam olarak aynı maliyettedir.
