Cara Menangani CAPTCHA di Crawlee dengan SDK Node.js

Crawlee tidak punya hook CAPTCHA, dan memang tidak membutuhkannya. Captcha Crawlee dipecahkan di dalam requestHandler, di tengah request yang halaman browser-nya sudah Anda pegang. Tiga hal membuatnya bekerja: deteksi widget sebelum Anda menghabiskan satu pemecahan untuknya, naikkan timeout handler karena nilai default-nya lebih pendek daripada satu pemecahan reCAPTCHA, dan lemparkan error saat gagal agar Crawlee mengulang request lewat queue-nya sendiri, bukan lewat loop Anda. Panduan ini menunjukkan ketiganya pada PlaywrightCrawler.
Apa yang Anda butuhkan
- Node.js 18 atau lebih baru, dan proyek Crawlee yang sudah meng-crawl sesuatu
- CapSkip berjalan dan dapat dijangkau. Mode Local mendengarkan di 127.0.0.1 port 8080 untuk otomatisasi di mesin yang sama, dan Mode Server mendengarkan di jaringan atau IP publik Anda sehingga crawler di mesin lain, VPS atau container host bisa memanggilnya. Keduanya ada di pengaturan koneksi
- Tiga paket, diinstal bersamaan
# 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
Contoh di sini memakai CommonJS, bentuk yang didokumentasikan README CapSkip. Crawlee 3 menyertakan kedua build, jadi proyek ESM bisa memakai statement import untuk crawler sebagai gantinya.
Di mana pemecahan ditempatkan: di dalam requestHandler
Scrapy punya downloader middleware dan Selenium punya wrapper apa pun yang Anda buat sendiri. Crawlee memberi Anda objek page secara langsung, jadi tidak ada lapisan intersepsi yang perlu ditulis. Anda mendeteksi tantangan, memecahkannya, lalu melanjutkan di fungsi yang sama.
Deteksi dulu. Menembakkan pemecahan ke setiap halaman membuang kapasitas pada halaman yang tidak pernah diberi tantangan, dan itu menyembunyikan sinyal berguna tentang seberapa sering Anda benar-benar diblokir.
// 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
}Atribut data-sitekey ada di div widget untuk reCAPTCHA v2 dan juga di div Turnstile, itulah sebabnya satu selector mencakup keduanya. reCAPTCHA v3 tidak punya widget yang terlihat, jadi Anda membaca key-nya dari URL script sebagai gantinya.
Sisipkan token, lalu kirim
Pemecahan memberi Anda sebuah token. Halaman tetap mengharapkan token itu berada di kolom tersembunyi yang seharusnya diisi oleh widget-nya sendiri, jadi tempatkan di sana dan kirim formulir seperti yang dilakukan browser.
// 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]'),
]);Sebagian halaman memanggil callback JavaScript alih-alih mengirim formulir. Jika div widget membawa atribut data-callback , panggil fungsi itu dengan token alih-alih mengklik apa pun, karena handler klik mungkin tidak pernah berjalan.
Naikkan requestHandlerTimeoutSecs sebelum hal lain
Inilah yang paling sering menjebak orang, dan tampak seperti masalah pemecah padahal bukan.
PlaywrightCrawler memberi setiap request handler 60 detik secara default. Job reCAPTCHA v2 belum siap dalam 15 sampai 20 detik pertama, v3 butuh 10 sampai 15, dan itu belum termasuk waktu untuk memuat halaman, menyisipkan token dan menunggu navigasi. Handler dihentikan di tengah pemecahan, Crawlee mencatat timeout, dan request kembali ke queue untuk mengulang semuanya.
// 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 detik adalah batas atas yang masuk akal. Angka itu sekitar sepuluh kali waktu solve normal, dan sengaja berada di bawah batas polling reCAPTCHA 300 detik milik SDK itu sendiri, sehingga Crawlee menyerah pada request yang benar-benar macet alih-alih membiarkannya menahan slot browser selama lima menit penuh. Kalau Anda lebih suka klien solver yang menyerah lebih dulu, turunkan recaptchaTimeout ke nilai di bawah timeout handler Anda.
Contoh lengkap yang berfungsi
Satu file, satu crawler, satu jalur pemecahan. Masukkan URL awal Anda sendiri di pemanggilan run.
// 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']);Pemecahan berjalan di komputer Anda sendiri, jadi anggaran percobaan ulang di atas tidak menelan biaya apa pun selain waktu berjalan. Itulah perbedaan praktisnya dengan layanan berbasis kuota, di mana tiga percobaan per URL menjadi item tagihan.
Biarkan queue yang mengulang, jangan bangun loop sendiri
Naluri pertama adalah membungkus pemecahan dalam loop for. Jangan. Crawlee sudah punya sistem percobaan ulang yang mengenal request queue, session pool dan konfigurasi proxy, dan loop buatan tangan di dalam handler tidak terlihat oleh ketiganya.
Lemparkan error sebagai gantinya. Handler yang melempar error mengembalikan request ke queue, dan Crawlee mengulanginya hingga maxRequestRetries kali dengan konteks browser yang baru.
// 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}`);
},
});Exception mana yang keluar memberi tahu Anda apa yang harus diubah. NetworkException berarti CapSkip tidak dapat dijangkau, jadi periksa host dan port sebelum menyalahkan situsnya. TimeoutException berarti jendela polling sudah habis dan halaman itu kemungkinan menyajikan tantangan yang lebih berat daripada dugaan Anda. ApiException membawa kode error yang dikembalikan, dan itulah yang layak dicatat bersama URL-nya.
Menjalankan crawler dan pemecah di mesin yang berbeda
Crawlee melakukan penskalaan dengan menjalankan lebih banyak dirinya sendiri, dan armada crawl di mesin terpisah tidak bisa semuanya bicara ke 127.0.0.1. Mode Server adalah jawabannya: CapSkip mendengarkan di jaringan atau IP publik Anda, bukan di loopback, dan setiap worker mengarah ke alamat yang sama.
// 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 membaca CAPSKIP_HOST dan CAPSKIP_PORT dari environment dengan sendirinya, jadi fallback di atas hanyalah pengaman tambahan untuk container yang start tanpa keduanya. IP publik statis direkomendasikan untuk mesin pemecah, dan langkah penyiapannya ada di pengaturan koneksi. Perangkat kerasnya tetap milik Anda dan tetap tanpa kuota, jadi satu-satunya yang berubah adalah tempat prosesnya berjalan.
Kesalahan umum dan artinya
| Gejala | Penyebab | Perbaiki |
|---|---|---|
| requestHandler kehabisan waktu setelah 60 detik | Timeout handler bawaan lebih pendek daripada satu pemecahan | Setel requestHandlerTimeoutSecs ke 180 |
| Pemecahan berhasil, halaman tetap memblokir | Token sudah masuk tetapi formulir tidak pernah dikirim | Periksa apakah ada atribut data-callback lalu panggil |
ERROR_GOOGLEKEY | Atribut sitekey kosong atau dibaca dari elemen yang salah | Catat nilainya sebelum memecahkan; key v3 berada di URL script |
ERROR_PAGEURL | Handler meneruskan URL relatif atau URL hasil pengalihan | Gunakan request.loadedUrl, yaitu URL setelah pengalihan |
| NetworkException pada setiap request | Crawler tidak dapat menjangkau pemecah | Mode Local hanya loopback; beralihlah ke Mode Server untuk worker jarak jauh |
| Setiap URL diulang tiga kali, lalu dibuang | Handler melempar error sebelum pemecahan | Baca log errorHandler; kegagalan pertama adalah yang sebenarnya |
Nama parameter dan daftar lengkap kode error ada di dokumentasi API.
FAQ
Apakah ini bekerja dengan CheerioCrawler?
Sebagian. CheerioCrawler tidak punya browser, jadi tidak ada objek page dan tidak ada cara menjalankan JavaScript milik widget itu sendiri. Anda tetap bisa mengurai sitekey dari HTML, memecahkannya, dan mengirim token bersama body formulir sendiri. Itu cukup untuk pengiriman formulir biasa dan tidak cukup untuk apa pun yang mengharapkan callback. Gunakan PlaywrightCrawler saat tantangan mungkin muncul.
Apakah sebaiknya saya memecahkan di preNavigationHook saja?
Tidak. Hook pra-navigasi berjalan sebelum halaman dimuat, jadi belum ada apa pun untuk dideteksi. Hook pasca-navigasi lebih dekat, tetapi di request handler-lah Anda sudah memiliki page, URL setelah pengalihan dan logger. Simpan pemecahan di sana dan pakai hook untuk cookie dan header.
Bisakah crawler berjalan di platform hosting sementara pemecah tetap di rumah?
Bisa, dengan CapSkip dalam Mode Server. Crawler butuh rute ke alamat pemecah, jadi koneksi rumah membutuhkan IP publik statis dan port yang terbuka, dan VPS adalah opsi yang lebih sederhana. Kode client-nya identik pada kedua kasus: hanya nilai host yang berubah.
Berapa banyak pemecahan bersamaan yang bisa didorong sebuah crawl?
Crawlee menskalakan konkurensinya sendiri secara otomatis, dan setiap handler menunggu pemecahannya masing-masing secara independen, jadi tidak ada queue yang perlu dikonfigurasi di sisi client. SDK mulai melakukan polling pada 250 milidetik lalu mundur sampai batas pollingInterval, sehingga pemecahan yang cepat tetap cepat bahkan ketika beberapa berjalan sekaligus. Sesuaikan konkurensi Crawlee Anda dengan yang ditoleransi situs target, bukan dengan pemecahnya.
Versi singkatnya
Deteksi widget, pecahkan di request handler, naikkan timeout handler menjadi 180 detik, dan lemparkan error agar queue yang mengulang. Menjalankan pemecah sendiri itulah yang membuat mengulang tiga kali menjadi default yang wajar alih-alih keputusan biaya, dan itu argumen yang sama untuk memakai bypass captcha lokal di mana pun dalam sebuah crawl. Panduan integrasi Node.js membahas penyiapan client, panduan Playwright berisi detail sisi browser yang diwarisi Crawlee, dan pemecahan CAPTCHA untuk web scraping membahas penanganan session di seluruh crawl. Untuk pola yang sama di Python, lihat artikel middleware Scrapy.
