Script Puppeteer Anda berhenti di layar "pilih semua kotak dengan lampu lalu lintas", dan tidak ada selector yang bisa menebak jawabannya. Jalan keluarnya bukan menebak: ambil screenshot kisi, kirim gambarnya ke CaptchaAI, terima daftar nomor sel yang harus diklik, lalu klik ubin itu dari kode yang sama. Empat langkah, satu file Node.js, tanpa perlu melatih model gambar sendiri.
Artikel ini memakai reCAPTCHA sebagai contoh karena tantangan kisi paling sering muncul di sana. Alur yang sama berlaku untuk grid image CAPTCHA lain, sebab yang dikirim ke API tetap satu hal: gambar kisi plus teks instruksinya.
Alur kerjanya dalam satu paragraf
Grid image CAPTCHA memberi Anda dua informasi: gambar kisi 3×3 atau 4×4 dan satu kalimat instruksi. CaptchaAI mengembalikan array berisi nomor sel, misalnya [1, 3, 6, 9], dengan penomoran mulai dari 1 di kiri atas lalu bergerak ke kanan dan ke bawah. Tugas kode Node.js Anda hanya memetakan nomor itu ke elemen ubin di halaman. Polanya sama dengan tipe CAPTCHA lain di CaptchaAI: kirim → simpan task ID → polling → pakai hasilnya.
Yang perlu disiapkan
| Kebutuhan | Nilai |
|---|---|
| API key CaptchaAI | Dari captchaai.com |
| Node.js | 14+ |
| Library | axios, puppeteer |
Tambahkan juga form-data, karena gambar dikirim sebagai multipart upload. Simpan API key di environment variable, bukan di file yang ikut ter-commit ke GitHub.
Langkah 1: ambil screenshot kisi tantangan
reCAPTCHA merender tantangan gambar di dalam iframe terpisah (bframe), jadi Anda perlu berpindah ke frame itu dulu, membaca teks instruksinya, lalu memotret elemen kisinya saja — bukan seluruh halaman. Screenshot yang terlalu lebar membuat pemetaan sel meleset.
const puppeteer = require('puppeteer');
const fs = require('fs');
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com/page-with-recaptcha');
// Switch to the reCAPTCHA challenge iframe
const frames = page.frames();
const challengeFrame = frames.find((f) => f.url().includes('recaptcha/api2/bframe'));
// Get the instruction text
const instruction = await challengeFrame.$eval(
'.rc-imageselect-desc-no-canonical',
(el) => el.textContent.trim()
);
// Screenshot the grid
const grid = await challengeFrame.$('.rc-imageselect-target');
await grid.screenshot({ path: 'grid.png' });
Kirim teks instruksi apa adanya. Jangan diterjemahkan ke bahasa Indonesia — penilaian gambar mengacu pada instruksi asli yang tampil di halaman.
Langkah 2: kirim gambar kisi ke API CaptchaAI
Permintaan dikirim sebagai multipart/form-data ke in.php. Tiga parameter yang menentukan hasil adalah grid_size (3x3 atau 4x4), img_type (recaptcha), dan instructions. Salah satu saja keliru, jawaban yang kembali tidak akan cocok dengan ubin di layar.
const axios = require('axios');
const FormData = require('form-data');
const API_KEY = 'YOUR_API_KEY';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const form = new FormData();
form.append('key', API_KEY);
form.append('method', 'post');
form.append('grid_size', '3x3');
form.append('img_type', 'recaptcha');
form.append('instructions', instruction);
form.append('json', '1');
form.append('file', fs.createReadStream('grid.png'));
const { data: submitData } = await axios.post('https://ocr.captchaai.com/in.php', form, {
headers: form.getHeaders(),
});
if (submitData.status !== 1) throw new Error(submitData.request);
const taskId = submitData.request;
console.log(`Task submitted: ${taskId}`);
Nilai submitData.request adalah task ID. Simpan di variabel, jangan hanya di log — task ID inilah kunci pengambilan hasil pada langkah berikutnya.
Langkah 3: polling hasil sampai nomor sel keluar
Beri jeda awal sekitar 5 detik sebelum polling pertama, lalu ulangi dengan interval yang sama. Tipe Grid Image tergolong cepat: metrik resmi CaptchaAI mencatat waktu penyelesaian <1 detik, sehingga sebagian besar task sudah siap pada iterasi pertama. Loop 30 kali di bawah ini berfungsi sebagai jaring pengaman saat jaringan tersendat, bukan sebagai ekspektasi normal.
await sleep(5000);
let cellsToClick;
for (let i = 0; i < 30; i++) {
const { data: pollData } = await axios.get('https://ocr.captchaai.com/res.php', {
params: { key: API_KEY, action: 'get', id: taskId, json: 1 },
});
if (pollData.status === 1) {
cellsToClick = JSON.parse(pollData.request);
console.log('Click cells:', cellsToClick);
break;
}
if (pollData.request !== 'CAPCHA_NOT_READY') {
throw new Error(pollData.request);
}
await sleep(5000);
}
Perhatikan pemisahan penanganan respons: CAPCHA_NOT_READY berarti "tunggu lagi", sedangkan kode error lain berarti "berhenti sekarang". Loop yang menelan semua kesalahan akan membuang 30 percobaan hanya karena API key salah ketik.
Langkah 4: klik ubin sesuai nomor sel
Nomor sel dari API dimulai dari 1, sedangkan array elemen di DOM dimulai dari 0 — itulah alasan ada - 1 pada indeks. Jeda 300 milidetik antar-klik memberi waktu render pada ubin dan menjaga urutan interaksi tetap wajar.
const tiles = await challengeFrame.$$('.rc-imageselect-tile');
for (const cellNum of cellsToClick) {
await tiles[cellNum - 1].click();
await sleep(300);
}
// Click verify
await challengeFrame.click('#recaptcha-verify-button');
console.log(`Solved: clicked tiles ${JSON.stringify(cellsToClick)}`);
await browser.close();
Keluaran yang Anda harapkan di terminal:
Click cells: [1, 3, 6, 9]
Solved: clicked tiles [1,3,6,9]
Empat kesalahan yang paling sering terjadi
- Memotret seluruh halaman, bukan elemen kisi. Nomor sel dihitung relatif terhadap kisi; menyertakan header atau border membuat semua klik bergeser satu baris.
grid_sizetidak diperbarui. Tantangan 4×4 yang dikirim dengan nilai3x3tetap mengembalikan jawaban — hanya saja jawaban untuk kisi yang salah.- Instruksi kosong. Jika selector
.rc-imageselect-desc-no-canonicaltidak cocok,instructionmenjadi string kosong dan akurasi turun tajam. Cek nilainya sebelum dikirim. - Task ID diperlakukan sebagai angka. Simpan sebagai string; format ID bisa berubah dan parsing numerik diam-diam merusak URL polling.
Saat menguji di lingkungan sendiri, arahkan skrip ke halaman staging Anda (staging.example.com) supaya iterasi tidak bergantung pada halaman produksi.
Biaya dan throughput untuk tim automasi di Indonesia
Banyak pekerjaan scraping di Indonesia dikerjakan secara kontrak — freelancer Upwork atau Fastwork, agensi price monitoring, dan tim data startup — sehingga biaya bulanan lebih menentukan daripada biaya per solve. CaptchaAI menagih per thread bersamaan, bukan per CAPTCHA, dengan solve tak terbatas per thread selama bulan berjalan. Satu thread berarti satu CAPTCHA yang sedang diproses; begitu selesai, thread itu langsung bebas menerima task berikutnya.
Untuk satu crawler Node.js, paket BASIC ($15/bulan, 5 thread) sudah menampung lonjakan tantangan kisi. Worker paralel yang membuka puluhan halaman sekaligus lebih cocok di ADVANCE ($90/bulan, 50 thread). Jika job Anda berjalan di AWS ap-southeast-1 (Singapura) atau ap-southeast-3 (Jakarta), latensi jaringan ke API jarang menjadi faktor penentu — yang menentukan adalah jumlah thread saat antrean tantangan menumpuk. Semua nilai di atas dalam USD; daftar paket terbaru selalu ada di halaman pricing CaptchaAI.
Pertanyaan umum
Berapa lama satu grid image CAPTCHA diselesaikan?
Metrik resmi CaptchaAI mencatat waktu penyelesaian tipe Grid Image di kisaran <1 detik. Dalam praktik, jeda terbesar justru datang dari interval polling yang Anda tetapkan sendiri — perkecil sleep awal bila throughput lebih penting daripada jumlah request.
Apakah kode ini bisa jalan headless di server tanpa GUI?
Bisa. puppeteer.launch() berjalan dalam mode headless secara default, dan seluruh alur — screenshot, upload, polling, klik — tidak membutuhkan display. Pastikan dependensi Chromium terpasang di image Docker Anda.
Bagaimana jika kisi dimuat ulang dengan gambar baru setelah diklik?
Sebagian tantangan reCAPTCHA mengganti ubin setelah klik pertama. Tangani dengan loop: potret ulang kisi, kirim lagi ke in.php, lalu klik hasil putaran berikutnya sampai tombol verifikasi diterima.
Apakah CaptchaAI juga menyelesaikan tantangan gambar hCaptcha?
Belum. hCaptcha dan FunCaptcha (Arkose Labs) tidak didukung saat ini, jadi pendekatan di artikel ini berlaku untuk grid image reCAPTCHA serta tipe image/OCR yang didukung. GeeTest v4 masih berstatus segera hadir.
Berapa thread yang saya butuhkan untuk ribuan solve per hari?
Hitung dari jumlah tantangan bersamaan, bukan total harian. Jika crawler membuka 20 halaman paralel dan sekitar seperempatnya memunculkan kisi, 5 thread pada paket BASIC ($15/bulan) sudah cukup; naikkan ke STANDARD ($30/bulan, 15 thread) ketika antrean mulai menunggu.