Cara Memecahkan CAPTCHA di Dalam Apify Actor (Node.js)

Langkah captcha di Apify terdiri dari tiga gerakan yang biasa, plus satu yang khas platform ini. Baca sitekey, pecahkan, lalu kirimkan token itu kembali bersama formulirnya. Gerakan tambahannya adalah menentukan di mana solver berada, karena Actor tidak berjalan di laptop Anda. Ia berjalan di dalam container di infrastruktur Apify, jadi alamat loopback di dalamnya milik container itu dan bukan milik yang lain. Ambil keputusan yang satu itu dengan benar dan sisanya cuma dua puluh baris.
Apa yang Anda butuhkan
- Node.js 18 atau lebih baru, Apify CLI, dan sebuah akun Apify.
- Paket apify dan capskip terinstal di dalam Actor.
- CapSkip yang berjalan dalam mode Server di mesin yang bisa dijangkau Actor, dengan IP publik statis dan validasi key dinyalakan. Mode Local tetap bisa dipakai selama Anda mengembangkannya di mesin Anda sendiri. Kedua mode dijelaskan di pengaturan koneksi.
# Scaffold an Actor, then add the solver client. apify create captcha-actor -t getting_started_node cd captcha-actor npm install capskip
Mode Server bukan pilihan opsional di sini
Ini bagian yang paling sering membuat orang tersandung, jadi ia ditaruh paling depan. Actor dieksekusi di worker Apify. Ketika kode Actor Anda membuka koneksi ke 127.0.0.1:8080, ia sedang berbicara dengan container-nya sendiri, yang tidak menjalankan solver, dan panggilannya gagal dengan error koneksi yang terlihat seolah solver-nya crash. Tidak ada yang crash. Alamat itu memang lokal, tetapi lokal di mesin yang salah.
CapSkip punya dua mode koneksi justru karena alasan ini. Local mengikat ke 127.0.0.1 dan hanya melayani perangkat itu. Server mengikat ke alamat jaringan atau IP publik Anda, sehingga mesin lain, sebuah VPS, atau platform terkelola seperti Apify bisa memanggil solver yang sama lewat API. IP publik statis menjaga alamat itu tetap stabil di antara run.
Perlu dikatakan terus terang, karena pertanyaan ini sering muncul: mode Server tidak mengubah CapSkip menjadi layanan cloud berbayar per pemakaian. Ini tetap mesin Anda dan tetap tanpa batas. Yang berubah hanyalah interface mana yang ia dengarkan. Begitu ia mendengarkan di alamat jaringan, nyalakan validasi key dan beri Actor key-nya sendiri, agar key itu bisa dicabut tanpa mengganggu yang lain.
Langkah 1: deklarasikan alamat solver sebagai input rahasia
Jangan menuliskan host secara hardcode. Skema input Apify mendukung field terenkripsi, dan di situlah tempat yang tepat untuk alamat solver beserta key-nya, sehingga nilainya ditetapkan per run alih-alih ditanam ke dalam build. Enkripsi bekerja dengan editor textfield, textarea, dan hidden.
{
"title": "CAPTCHA actor input",
"type": "object",
"schemaVersion": 1,
"properties": {
"targetUrl": {
"title": "Target URL",
"type": "string",
"editor": "textfield"
},
"solverHost": {
"title": "Solver host",
"type": "string",
"editor": "textfield",
"isSecret": true
},
"solverKey": {
"title": "Solver API key",
"type": "string",
"editor": "textfield",
"isSecret": true
}
},
"required": ["targetUrl", "solverHost"]
}File itu ditaruh di folder .actor bersama actor.json, dan nilainya sampai ke kode Anda lewat objek input.
Langkah 2: pilih host saat runtime
Anda ingin satu Actor yang bekerja di kedua tempat: berbicara dengan 127.0.0.1 saat Anda menjalankannya secara lokal, dan berbicara dengan server Anda begitu ia sudah di-deploy. SDK menyediakan sebuah boolean persis untuk keperluan ini. Actor.isAtHome() mengembalikan true ketika kode dieksekusi di platform Apify dan false ketika tidak.
// npm install apify capskip
import { Actor } from 'apify';
import { CapSkip } from 'capskip';
await Actor.init();
const input = await Actor.getInput();
// Local run talks to the loopback address. A platform run
// talks to the server address that came in as a secret.
const solver = new CapSkip({
host: Actor.isAtHome() ? input.solverHost : '127.0.0.1',
port: 8080,
apiKey: input.solverKey,
});Tidak ada bagian lain dari Actor yang perlu tahu soal perbedaan itu. Jalur kode yang sama menangani keduanya, dan deploy yang gagal tidak lagi berarti Anda harus menyunting konstanta.
Langkah 3: baca sitekey, pecahkan, kirim token
Sitekey berada di dokumen induk sebagai atribut data-sitekey, jadi fetch biasa dan sebuah regular expression sudah cukup untuk mengambilnya tanpa menjalankan browser. Itu penting di platform berbayar: Actor tanpa browser butuh memori jauh lebih sedikit, dan Apify menagih berdasarkan memori dikalikan waktu. Actor.fail() di bawah mengakhiri run alih-alih sekadar return, dan itulah sebabnya kode setelahnya boleh mengasumsikan pencocokannya berhasil.
// Fetch the form page and lift the sitekey out of it.
const html = await (await fetch(input.targetUrl)).text();
const match = html.match(/data-sitekey="([^"]+)"/);
if (!match) {
await Actor.fail('No sitekey on the page. Did the widget render?');
}
const result = await solver.recaptcha(match[1], input.targetUrl);
const token = result.code; // the g-recaptcha-response valueLalu kirim formulirnya dengan token di field yang diharapkan situs. Untuk reCAPTCHA v2, textarea tersembunyinya bernama g-recaptcha-response, dan sebagian besar formulir mengirimkannya dengan nama yang sama. Jika halaman justru menyerahkan token itu ke sebuah callback, periksa apa yang sebenarnya dikirim oleh callback tersebut.
// The token travels as an ordinary form field.
const body = new URLSearchParams({
email: 'someone@example.com',
'g-recaptcha-response': token,
});
const posted = await fetch(input.targetUrl, { method: 'POST', body });
await Actor.pushData({ url: input.targetUrl, status: posted.status });Turnstile dan GeeTest punya metode sendiri pada client yang sama, dan keduanya berbentuk sama. Turnstile juga mengembalikan user agent yang harus dikirim bersama token pada halaman challenge. Daftar parameter lengkapnya ada di dokumentasi API CapSkip.
Contoh lengkap yang berfungsi
Seluruh Actor, sebagai src/main.js. Sebuah Actor adalah modul ES dengan top-level await, jadi tidak ada fungsi pembungkus, dan Actor.exit() di bagian akhir itulah yang menuntaskan penulisan dataset dan menutup run dengan rapi.
// npm install apify capskip
import { Actor } from 'apify';
import { CapSkip, NetworkException, TimeoutException } from 'capskip';
await Actor.init();
const input = await Actor.getInput();
const solver = new CapSkip({
host: Actor.isAtHome() ? input.solverHost : '127.0.0.1',
port: 8080,
apiKey: input.solverKey,
});
try {
const html = await (await fetch(input.targetUrl)).text();
const match = html.match(/data-sitekey="([^"]+)"/);
if (!match) throw new Error('No sitekey found on the page.');
const result = await solver.recaptcha(match[1], input.targetUrl);
const body = new URLSearchParams({
'g-recaptcha-response': result.code,
});
const posted = await fetch(input.targetUrl, { method: 'POST', body });
await Actor.pushData({ url: input.targetUrl, status: posted.status });
} catch (err) {
if (err instanceof NetworkException) {
await Actor.fail('Cannot reach the solver. Check Server mode and the host.');
}
if (err instanceof TimeoutException) {
await Actor.fail('The solve outlasted recaptchaTimeout.');
}
throw err;
}
await Actor.exit();Menangkap dua exception berbentuk koneksi itu secara terpisah sepadan dengan enam barisnya. Actor.fail() adalah Actor.exit() dengan exit code 1 plus sebuah pesan, jadi run berakhir sebagai FAILED dengan satu kalimat di log yang memberi tahu Anda bagian penyiapan mana yang rusak. Tanpa itu, solver yang sekadar tidak terjangkau dan proses pemecahan yang benar-benar tidak bisa dibaca menghasilkan run merah yang sama.
Menjalankannya secara lokal sebelum Anda deploy
Jalankan Actor di mesin Anda sendiri lebih dulu, dengan solver dalam mode Local. isAtHome() mengembalikan false di sana, jadi kodenya menuju 127.0.0.1 tanpa Anda mengubah apa pun, dan Anda bisa membuktikan bahwa pembacaan sitekey dan pengiriman formulirnya bekerja sebelum menambahkan lompatan jaringan.
# Reads INPUT from storage/key_value_stores/default. apify run
Kalau itu lolos, alihkan CapSkip ke mode Server, catat alamat yang kini ia dengarkan, dan masukkan alamat itu ke input solverHost di platform. Satu-satunya yang berubah adalah satu string.
Kesalahan umum dan artinya
| Apa yang Anda lihat | Penyebab | Perbaiki |
|---|---|---|
| NetworkException di platform, tidak pernah di lokal | Actor memanggil 127.0.0.1 dan sampai ke container-nya sendiri | Alihkan solver ke mode Server dan oper alamatnya |
| ERROR_WRONG_USER_KEY | Validasi key menyala dan Actor mengirim key yang salah | Setel key sebagai input rahasia dan baca dari input tersebut |
| ERROR_GOOGLEKEY | Regular expression tidak mencocokkan apa pun dan key kosong ikut terkirim | Periksa hasil pencocokannya sebelum menghabiskan satu proses pemecahan untuknya |
| Status run TIMED-OUT | Timeout run lebih pendek daripada fetch plus pemecahan plus pengiriman | Naikkan timeout di opsi run default Actor |
| TimeoutException | Proses pemecahan melampaui recaptchaTimeout | Naikkan di atas nilai default 300 detik |
| Formulir menolak token yang sebenarnya terpecahkan dengan baik | Token kedaluwarsa di antara proses pemecahan dan pengirimannya | Pecahkan tepat sebelum mengirim, bukan di awal run |
FAQ
Bisakah Apify Actor benar-benar menjangkau solver di mesin saya sendiri?
Bisa, lewat API yang sama seperti yang akan ia pakai untuk layanan internal mana pun. Mode Server membuat CapSkip mendengarkan di alamat jaringan atau IP publik Anda alih-alih alamat loopback, sehingga Actor memanggilnya seperti endpoint HTTP lainnya. IP publik statis disarankan agar alamatnya tidak berpindah di antara run, dan validasi key sebaiknya sudah menyala sebelum Anda membuka port.
Apakah ini bekerja di dalam crawler Crawlee pada Apify?
Bekerja, dan ketiga gerakannya identik. Bedanya ada di tempat mereka diletakkan: pembacaan sitekey dan penyuntikan token masuk ke dalam request handler, dan client solver dibuat sekali di luarnya agar setiap request berbagi satu instance. Detail yang khusus soal crawler, termasuk mengapa Anda sebaiknya tidak membungkus proses pemecahan di dalam retry loop buatan sendiri, dibahas di panduan CAPTCHA Crawlee.
Haruskah saya memecahkan lewat Apify Proxy?
Hanya jika situsnya menilai IP yang melakukan pemecahan. Dukungan proxy tersedia untuk reCAPTCHA, Turnstile, dan GeeTest, dan itu penting ketika token diperiksa terhadap alamat yang memintanya. Oper proxy pada panggilan pemecahan alih-alih mengarahkan seluruh solver lewat proxy tersebut, agar fetch dan pemecahannya bisa memakai exit yang berbeda saat memang itu yang Anda inginkan. Untung ruginya dijabarkan di panduan rotasi proxy CAPTCHA.
Berapa besar memori yang sebaiknya diberikan ke Actor?
Lebih kecil daripada dugaan Anda, kalau Anda melewatkan browser. Actor di atas mengambil HTML, menunggu satu panggilan jaringan, lalu mengirim formulir, jadi ia menganggur di sebagian besar hidupnya dan sama sekali tidak butuh jejak memori sebesar Chromium. Baru pakai browser ketika halaman tidak mau menyerahkan sitekey-nya tanpa menjalankan JavaScript. Proses pemecahannya sendiri tetap terjadi di mesin solver, meskipun Actor Anda tetap berjalan selama menunggu.
Versi singkatnya
Taruh solver dalam mode Server, simpan alamat dan key-nya sebagai input terenkripsi, dan biarkan Actor.isAtHome() memilih antara alamat itu dan 127.0.0.1. Lalu baca sitekey, pecahkan, dan kirim token sebagai g-recaptcha-response. Untuk gambaran Node yang lebih luas, termasuk Puppeteer dan Playwright, lihat halaman pemecah CAPTCHA Node.js. Sisi crawling dari masalah yang sama dibahas di halaman web scraping.
Satu konsekuensi layak dijabarkan sebelum Anda menaikkan skala Actor. Karena bypass captcha yang berjalan di perangkat keras yang sudah Anda miliki, beralih dari sepuluh run ke seribu run menggeser tagihan Apify Anda dan membiarkan tagihan pemecahan CAPTCHA Anda tetap di tempatnya.
