Node.js SDK ile Crawlee'de CAPTCHA Nasıl Ele Alınır

Crawlee'de CAPTCHA hook'u yok ve olmasına gerek de yok. Bir Crawlee captcha'sı şunun içinde çözülür: requestHandler, yani zaten bir tarayıcı sayfasına sahip olduğunuz isteğin tam ortasında. Üç şey bunu çalışır hâle getirir: bir çözüm harcamadan önce widget'ı tespit edin, varsayılan süre bir reCAPTCHA çözümünden kısa olduğu için handler zaman aşımını yükseltin ve başarısızlıkta hata fırlatın ki Crawlee isteği sizin döngünüz yerine kendi kuyruğu üzerinden yeniden denesin. Bu rehber üçünü de PlaywrightCrawler üzerinde gösteriyor.
Neye ihtiyacınız var
- Node.js 18 veya daha yenisi ve halihazırda bir şeyi tarayan bir Crawlee projesi
- CapSkip'in çalışıyor ve erişilebilir olması. Local mod, aynı makinedeki otomasyon için 127.0.0.1 üzerinde 8080 portunu dinler; Server mod ise ağınızı veya genel IP'nizi dinler, böylece başka bir makinedeki, bir VPS'teki ya da bir konteyner sunucusundaki tarayıcı ona çağrı yapabilir. Her ikisi de şurada anlatılıyor: bağlantı ayarları
- Üç paket, hepsi birlikte kurulur
# One install for the crawler, the browser and the solver client. npm install crawlee playwright capskip # Crawlee drives a real browser, so fetch one. npx playwright install chromium
Buradaki örnekler CommonJS biçiminde; CapSkip README dosyasının belgelediği biçim de bu. Crawlee 3 her iki derlemeyi de sunar, dolayısıyla bir ESM projesi tarayıcı için bunun yerine import ifadelerini kullanabilir.
Çözüm nereye girer: requestHandler içine
Scrapy'de downloader middleware vardır, Selenium'da ise ne yazdıysanız o sarmalayıcı vardır. Crawlee size page nesnesini doğrudan verir, dolayısıyla yazılacak bir araya girme katmanı yoktur. Challenge'ı tespit eder, çözer ve aynı fonksiyon içinde yolunuza devam edersiniz.
Önce tespit edin. Her sayfaya çözüm göndermek, hiç doğrulama çıkmamış sayfalarda kapasite harcar ve gerçekte ne sıklıkta engellendiğinize dair faydalı sinyali gizler.
// npm install crawlee playwright capskip
const { PlaywrightCrawler } = require('crawlee');
const { CapSkip } = require('capskip');
// Local mode. Point host at a server IP to share one solver.
const solver = new CapSkip({ host: '127.0.0.1', port: 8080 });
async function solveIfChallenged(page, url, log) {
const widget = page.locator('[data-sitekey]').first();
if ((await widget.count()) === 0) return false;
const sitekey = await widget.getAttribute('data-sitekey');
log.info(`Solving sitekey ${sitekey}`);
const result = await solver.recaptcha(sitekey, url);
return result.code; // the token
}data-sitekey özniteliği reCAPTCHA v2 için widget div'inde, ayrıca Turnstile div'inde de bulunur; tek bir seçicinin ikisini birden kapsamasının nedeni budur. reCAPTCHA v3'ün görünür bir widget'ı yoktur, bu yüzden anahtarı bunun yerine script URL'sinden okursunuz.
Token'ı enjekte edin, sonra gönderin
Çözüm size bir token verir. Sayfa bu token'ı hâlâ, kendi widget'ının dolduracağı gizli alanda bekler; bu yüzden token'ı oraya koyun ve formu bir tarayıcının göndereceği gibi gönderin.
// The widget writes into a hidden textarea. Do the same.
await page.evaluate((token) => {
const field = document.getElementById('g-recaptcha-response');
field.value = token;
}, token);
// Then submit exactly as the page would, and wait for the result.
await Promise.all([
page.waitForNavigation(),
page.click('button[type=submit]'),
]);Bazı sayfalar form göndermek yerine bir JavaScript callback'i çağırır. Widget div'i bir data-callback özniteliği taşıyorsa, herhangi bir yere tıklamak yerine o fonksiyonu token ile çağırın, çünkü tıklama işleyicisi hiç çalışmayabilir.
Her şeyden önce requestHandlerTimeoutSecs değerini yükseltin
İnsanları asıl tuzağa düşüren bu ve öyle olmadığı hâlde bir çözücü sorunu gibi görünüyor.
PlaywrightCrawler her istek işleyicisine 60 saniye verir, varsayılan budur. Bir reCAPTCHA v2 işi ilk 15 ila 20 saniye boyunca hazır olmaz, v3 10 ila 15 saniye alır ve bu, sayfayı yüklemeden, token'ı enjekte etmeden ve bir gezinmeyi beklemeden önceki süredir. Handler çözümün ortasında sonlandırılır, Crawlee bir zaman aşımı kaydeder ve istek her şeyi baştan yapmak üzere kuyruğa geri döner.
// npm install crawlee playwright capskip
const crawler = new PlaywrightCrawler({
// 60 is the default and it is shorter than a v2 solve plus a submit.
requestHandlerTimeoutSecs: 180,
// Three tries per URL, which is Crawlee's default and the right one.
maxRequestRetries: 3,
async requestHandler({ page, request, log }) {
// your handler
},
});180 saniye makul bir üst sınırdır. Normal bir çözümün yaklaşık on katıdır ve SDK’nın reCAPTCHA için koyduğu 300 saniyelik yoklama sınırının bilerek altında kalır; böylece gerçekten takılmış bir isteği Crawlee bırakır, tarayıcı yuvasını tam beş dakika tutmasına izin vermez. Bunun yerine önce çözücü istemcinin vazgeçmesini istiyorsanız, recaptchaTimeout değerini handler zaman aşımınızın altına çekin.
Tam çalışan örnek
Tek dosya, tek tarayıcı, tek çözüm yolu. Kendi başlangıç URL'nizi run çağrısına yerleştirin.
// npm install crawlee playwright capskip
const { PlaywrightCrawler, Dataset } = require('crawlee');
const { CapSkip } = require('capskip');
const solver = new CapSkip({ host: '127.0.0.1', port: 8080 });
const crawler = new PlaywrightCrawler({
requestHandlerTimeoutSecs: 180,
maxRequestRetries: 3,
async requestHandler({ page, request, log }) {
const widget = page.locator('[data-sitekey]').first();
if ((await widget.count()) > 0) {
const sitekey = await widget.getAttribute('data-sitekey');
const result = await solver.recaptcha(sitekey, request.loadedUrl);
await page.evaluate((token) => {
document.getElementById('g-recaptcha-response').value = token;
}, result.code);
await Promise.all([
page.waitForNavigation(),
page.click('button[type=submit]'),
]);
log.info(`Cleared the challenge on ${request.loadedUrl}`);
}
await Dataset.pushData({ url: request.loadedUrl, title: await page.title() });
},
});
await crawler.run(['https://example.com/page-with-recaptcha']);Çözüm kendi makinenizde çalışır, dolayısıyla yukarıdaki yeniden deneme bütçesi duvar saati süresinden başka hiçbir şeye mal olmaz. Sayaçlı bir servisten pratik farkı budur; orada URL başına üç deneme bir fatura kalemidir.
Yeniden denemeyi kuyruğa bırakın, kendi döngünüzü kurmayın
İlk içgüdü, çözümü bir for döngüsüne sarmaktır. Sarmayın. Crawlee'de zaten istek kuyruğunu, oturum havuzunu ve proxy yapılandırmasını bilen bir yeniden deneme sistemi var; handler içinde elle yazılmış bir döngü ise bu üçünün de gözüne görünmez.
Bunun yerine hata fırlatın. Hata fırlatan bir handler isteği kuyruğa geri gönderir ve Crawlee onu yeni bir tarayıcı bağlamıyla en fazla maxRequestRetries kez yeniden dener.
// npm install capskip
const { ApiException, NetworkException, TimeoutException } = require('capskip');
const crawler = new PlaywrightCrawler({
requestHandlerTimeoutSecs: 180,
// Runs between retries, while attempts remain.
errorHandler({ request, log }, error) {
log.warning(`Retry ${request.retryCount} for ${request.url}: ${error.message}`);
},
// Runs once, after the last attempt fails.
failedRequestHandler({ request, log }) {
log.error(`Gave up on ${request.url}`);
},
});Hangi istisnanın çıktığı size neyi değiştireceğinizi söyler. NetworkException, CapSkip'e erişilemediği anlamına gelir; siteyi suçlamadan önce host ve portu kontrol edin. TimeoutException, yoklama penceresinin dolduğu ve sayfanın muhtemelen sandığınızdan daha zor bir challenge sunduğu anlamına gelir. ApiException ise dönen bir hata kodu taşır; URL'yi de ekleyerek loglamaya değer olan budur.
Tarayıcı ile çözücüyü farklı makinelerde çalıştırmak
Crawlee, kendisinden daha fazlasını çalıştırarak ölçeklenir ve ayrı makinelerdeki bir tarama filosunun tamamı 127.0.0.1 ile konuşamaz. Cevap Server mod: CapSkip loopback yerine ağınızı veya genel IP'nizi dinler ve her worker aynı adresi gösterir.
// npm install capskip
const { CapSkip } = require('capskip');
// Same client, different address. Nothing else in the code changes.
const solver = new CapSkip({
host: process.env.CAPSKIP_HOST || '127.0.0.1',
port: Number(process.env.CAPSKIP_PORT || 8080),
});SDK, CAPSKIP_HOST ve CAPSKIP_PORT değerlerini ortamdan kendi başına okur; dolayısıyla yukarıdaki yedek değer, bunlar olmadan başlayan bir konteyner için fazladan bir önlemdir. Çözücü makinesi için statik bir genel IP önerilir ve kurulum adımları şurada: bağlantı ayarları. Bu hâlâ sizin donanımınız ve hâlâ sayaçsız; dolayısıyla değişen tek şey, sürecin nerede çalıştığı.
Sık görülen hatalar ve anlamları
| Belirti | Neden | Düzeltme |
|---|---|---|
| requestHandler 60 saniye sonra zaman aşımına uğradı | Varsayılan handler zaman aşımı bir çözümden kısa | requestHandlerTimeoutSecs değerini 180 yapın |
| Çözüm başarılı, sayfa yine de engelliyor | Token yerleşti ama form hiç gönderilmedi | data-callback özniteliğini kontrol edin ve onu çağırın |
ERROR_GOOGLEKEY | sitekey özniteliği boştu veya yanlış elemandan okundu | Çözmeden önce değeri loglayın; v3 anahtarları script URL'sinde bulunur |
ERROR_PAGEURL | Handler göreli veya yönlendirilmiş bir URL geçirdi | Yönlendirmelerden sonraki URL olan request.loadedUrl kullanın |
| Her istekte NetworkException | Tarayıcı çözücüye erişemiyor | Local mod yalnızca loopback'tir; uzak bir worker için Server moda geçin |
| Her URL üç kez denenip sonra bırakılıyor | Handler, çözümden önce hata fırlatıyor | errorHandler logunu okuyun; asıl olan ilk başarısızlıktır |
Parametre adları ve tam hata kodu listesi şurada: API dokümantasyonu.
FAQ
Bu, CheerioCrawler ile çalışır mı?
Kısmen. CheerioCrawler'da tarayıcı yoktur, dolayısıyla page nesnesi de yoktur ve widget içindeki JavaScript kodunu çalıştırmanın bir yolu bulunmaz. Yine de sitekey'i HTML içinden ayrıştırıp çözebilir ve token'ı form gövdesiyle birlikte kendiniz gönderebilirsiniz. Bu, düz bir form gönderimi için yeterli, callback bekleyen herhangi bir şey için yetersizdir. Challenge ihtimali varsa PlaywrightCrawler kullanın.
Bunun yerine preNavigationHook içinde mi çözmeliyim?
Hayır. Gezinme öncesi hook'lar sayfa yüklenmeden önce çalışır, yani tespit edilecek bir şey henüz yoktur. Gezinme sonrası hook'lar daha yakındır, ama sayfaya, yönlendirmelerden sonraki URL'ye ve logger'a zaten sahip olduğunuz yer istek işleyicisidir. Çözümü orada tutun, hook'ları da çerezler ve başlıklar için saklayın.
Çözücü evde kalırken tarayıcı barındırılan bir platformda çalışabilir mi?
Evet, CapSkip Server modda olduğu sürece. Tarayıcının çözücünün adresine giden bir yola ihtiyacı vardır; bu yüzden ev bağlantısı için statik bir genel IP ve açık bir port gerekir, VPS ise daha basit seçenektir. İstemci kodu her iki durumda da aynıdır: yalnızca host değeri değişir.
Bir tarama aynı anda kaç çözüm yollayabilir?
Crawlee kendi eşzamanlılığını otomatik ölçekler ve her handler kendi çözümünü bağımsız olarak bekler; dolayısıyla istemci tarafında yapılandırılacak bir kuyruk yoktur. SDK yoklamaya 250 milisaniyede başlar ve pollingInterval üst sınırına kadar geri çekilir; bu da birkaç çözüm aynı anda yoldayken bile hızlı bir çözümü hızlı tutar. Crawlee eşzamanlılığınızı çözücüye göre değil, hedef sitenin tolere ettiğine göre ayarlayın.
Kısa özet
Widget'ı tespit edin, çözümü istek işleyicisinde yapın, handler zaman aşımını 180 saniyeye çıkarın ve kuyruğun yeniden denemesi için hata fırlatın. Çözücüyü kendiniz çalıştırmanız, üç kez yeniden denemeyi bir maliyet kararı değil makul bir varsayılan hâline getiren şeydir; bu da bir taramanın herhangi bir yerinde yerel bir captcha atlatma kullanmanın gerekçesiyle aynıdır. Node.js entegrasyon kılavuzu istemci kurulumunu anlatır, Playwright kılavuzu Crawlee'nin devraldığı tarayıcı tarafı ayrıntıları içerir ve web scraping için CAPTCHA çözme tüm bir tarama boyunca oturum yönetimini ele alır. Aynı desenin Python hâli için bkz. Scrapy middleware yazısı.
