Untuk scraping situs yang dilindungi CAPTCHA di Node.js, Anda tidak perlu menjalankan browser penuh: ambil sitekey dari HTML, kirim ke API CaptchaAI, tunggu token yang sudah diselesaikan, lalu kirim token itu bersama form. Dengan pola ini, axios yang menangani HTTP dan cheerio yang mem-parsing hasil sudah cukup untuk sebagian besar target — jauh lebih ringan daripada menyalakan Puppeteer di setiap halaman.
Tutorial ini membangun satu modul solver yang bisa dipakai ulang, lalu memakainya untuk reCAPTCHA v2, reCAPTCHA v3, dan Cloudflare Turnstile. Setelahnya Anda akan melihat cara scraping banyak halaman secara concurrent, menjaga sesi login lewat cookie, dan mem-parsing hasil dengan cheerio.
Yang Anda butuhkan sebelum mulai
| Persyaratan | Detail |
|---|---|
| Node.js 16+ | Dengan npm |
axios |
npm install axios |
cheerio |
npm install cheerio |
| API key CaptchaAI | Dari halaman utama CaptchaAI |
Kalau Anda benar-benar baru, pastikan dulu API key dan saldo thread sudah aktif di dashboard sebelum menempelkannya ke skrip Node.js.
Cara kerja API: kirim, simpan task ID, polling, pakai token
Semua tipe CAPTCHA di CaptchaAI mengikuti alur empat langkah yang sama, dan ini yang membuat satu modul solver bisa menangani reCAPTCHA maupun Turnstile:
- Kirim parameter CAPTCHA (sitekey dan URL halaman) ke endpoint
in.php. - Simpan task ID yang dikembalikan dalam respons
OK|<id>. - Polling ke
res.phpsetiap beberapa detik sampai statusnya bukan lagiCAPCHA_NOT_READY. - Pakai token yang sudah selesai — tempelkan ke field form (
g-recaptcha-responseuntuk reCAPTCHA,cf-turnstile-responseuntuk Turnstile) dan kirim permintaan berikutnya.
Skrip Anda tidak pernah menyelesaikan CAPTCHA sendiri; ia hanya menunggu token dan meneruskannya. Itu sebabnya scraping berbasis HTTP seperti ini bisa berjalan tanpa browser, cocok untuk worker yang di-deploy ke region seperti AWS ap-southeast-1 (Singapura) atau ap-southeast-3 (Jakarta) tanpa perlu Chrome headless di dalam container.
Modul solver CaptchaAI yang reusable
Bungkus keempat langkah tadi ke dalam satu class. Method _submit dan _poll menangani protokol dasar, sementara method publik (solveRecaptchaV2, solveRecaptchaV3, solveTurnstile) hanya mengganti parameter per tipe:
// captcha-solver.js
const axios = require("axios");
class CaptchaSolver {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = "https://ocr.captchaai.com";
}
async _submit(params) {
params.key = this.apiKey;
const resp = await axios.get(`${this.baseUrl}/in.php`, { params });
if (!resp.data.startsWith("OK|")) {
throw new Error(`Submit error: ${resp.data}`);
}
return resp.data.split("|")[1];
}
async _poll(taskId, timeout = 300000) {
const deadline = Date.now() + timeout;
while (Date.now() < deadline) {
await new Promise((r) => setTimeout(r, 5000));
const resp = await axios.get(`${this.baseUrl}/res.php`, {
params: { key: this.apiKey, action: "get", id: taskId },
});
if (resp.data === "CAPCHA_NOT_READY") continue;
if (resp.data.startsWith("OK|")) return resp.data.split("|")[1];
throw new Error(`Solve error: ${resp.data}`);
}
throw new Error("Solve timed out");
}
async solveRecaptchaV2(siteKey, pageUrl) {
const taskId = await this._submit({
method: "userrecaptcha",
googlekey: siteKey,
pageurl: pageUrl,
});
return this._poll(taskId);
}
async solveRecaptchaV3(siteKey, pageUrl, action = "verify") {
const taskId = await this._submit({
method: "userrecaptcha",
googlekey: siteKey,
pageurl: pageUrl,
version: "v3",
action,
});
return this._poll(taskId);
}
async solveTurnstile(siteKey, pageUrl) {
const taskId = await this._submit({
method: "turnstile",
sitekey: siteKey,
pageurl: pageUrl,
});
return this._poll(taskId);
}
}
module.exports = CaptchaSolver;
Interval polling 5000 ms dan timeout 300000 ms adalah titik awal yang aman. Untuk reCAPTCHA v2 yang bisa memakan waktu di bawah 60 detik, jangan buru-buru memperkecil timeout — lebih baik biarkan longgar daripada gagal di solve yang sebenarnya hampir jadi.
Scraping halaman yang dilindungi reCAPTCHA v2
Alurnya lurus: muat halaman, tarik data-sitekey dari elemen .g-recaptcha, selesaikan lewat solver, lalu kirim ulang form dengan token pada field g-recaptcha-response. Perhatikan User-Agent yang wajar agar permintaan tidak langsung ditolak:
const axios = require("axios");
const cheerio = require("cheerio");
const CaptchaSolver = require("./captcha-solver");
const solver = new CaptchaSolver("YOUR_API_KEY");
async function scrapeProtectedPage(url) {
// Step 1: Load the page
const { data: html } = await axios.get(url, {
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
},
});
const $ = cheerio.load(html);
// Step 2: Extract site key
const siteKey = $(".g-recaptcha").attr("data-sitekey");
if (!siteKey) {
console.log("No CAPTCHA found, page loaded directly");
return html;
}
console.log("Site key found:", siteKey);
// Step 3: Solve the CAPTCHA
const token = await solver.solveRecaptchaV2(siteKey, url);
console.log("Token received:", token.substring(0, 50));
// Step 4: Submit with the token
const result = await axios.post(
url,
new URLSearchParams({
"g-recaptcha-response": token,
q: "search query",
}),
{
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
},
}
);
return result.data;
}
Cek if (!siteKey) di langkah dua penting: banyak halaman hanya memunculkan CAPTCHA pada kondisi tertentu, jadi skrip harus tetap jalan saat halaman terbuka langsung tanpa tantangan. Kalau target Anda memakai reCAPTCHA v3, tinggal panggil solver.solveRecaptchaV3() dengan parameter action yang sesuai; sisa alurnya identik.
Scraping banyak halaman secara concurrent
Di sinilah Node.js benar-benar terasa cepat. Alih-alih menyelesaikan satu halaman lalu menunggu, jalankan beberapa worker yang mengambil URL dari satu antrean bersama. Parameter concurrency mengatur berapa solve berjalan bersamaan:
async function scrapePages(urls, siteKey, concurrency = 3) {
const results = [];
const queue = [...urls];
const worker = async () => {
while (queue.length > 0) {
const url = queue.shift();
try {
const token = await solver.solveRecaptchaV2(siteKey, url);
const { data } = await axios.post(
url,
new URLSearchParams({ "g-recaptcha-response": token }),
{
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
},
}
);
results.push({ url, data, success: true });
console.log(`Scraped: ${url}`);
} catch (err) {
results.push({ url, error: err.message, success: false });
console.error(`Failed: ${url} - ${err.message}`);
}
}
};
// Run workers concurrently
const workers = Array(concurrency)
.fill(null)
.map(() => worker());
await Promise.all(workers);
return results;
}
// Usage
const urls = [
"https://example.com/page/1",
"https://example.com/page/2",
"https://example.com/page/3",
];
const results = await scrapePages(urls, "6Le-wvkS...", 3);
Angka concurrency yang masuk akal terikat pada jumlah thread di paket CaptchaAI Anda, karena satu thread menampung satu CAPTCHA yang sedang berjalan. Paket BASIC ($15/bulan, 5 thread) nyaman untuk pekerjaan kecil; kalau Anda menggarap proyek price-monitoring lewat Fastwork atau Upwork yang harus memantau ratusan halaman produk per jam, ADVANCE ($90/bulan, 50 thread) atau PREMIUM ($170/bulan, 100 thread) memberi ruang concurrency yang jauh lebih longgar. Karena penagihan berbasis thread dengan solve tak terbatas per thread, biaya bulanan Anda tetap dapat diprediksi berapa pun volume halaman — cocok untuk anggaran proyek freelance yang ketat.
Menangani cookie dan sesi login
Banyak situs menaruh CAPTCHA di balik alur yang bergantung pada session cookie. Pakai axios-cookiejar-support supaya cookie dari pemuatan awal terbawa ke permintaan berikutnya:
const { wrapper } = require("axios-cookiejar-support");
const { CookieJar } = require("tough-cookie");
const jar = new CookieJar();
const client = wrapper(
axios.create({
jar,
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
},
})
);
async function scrapeWithSession(url, siteKey) {
// Initial page load sets cookies
await client.get(url);
// Solve CAPTCHA
const token = await solver.solveRecaptchaV2(siteKey, url);
// Submit with maintained cookies
const result = await client.post(
url,
new URLSearchParams({ "g-recaptcha-response": token })
);
return result.data;
}
Kuncinya adalah memuat halaman lebih dulu agar server menaruh cookie, baru menyelesaikan CAPTCHA. Kalau Anda mengirim token tanpa cookie sesi yang benar, server sering membalas 403 meskipun tokennya valid.
Parsing hasil dengan cheerio
Setelah HTML respons di tangan, cheerio memberi Anda sintaks selektor mirip jQuery untuk menariknya menjadi data terstruktur:
function parseResults(html) {
const $ = cheerio.load(html);
const items = [];
$(".result-item").each((_, el) => {
items.push({
title: $(el).find(".title").text().trim(),
url: $(el).find("a").attr("href"),
description: $(el).find(".description").text().trim(),
});
});
return items;
}
Sesuaikan selektor .result-item, .title, dan .description dengan struktur halaman target Anda. Satu catatan kepatuhan yang relevan di Indonesia: scrape hanya data yang memang boleh Anda proses. UU Pelindungan Data Pribadi (UU 27/2022) dan UU ITE membuat "hindari data pribadi dan data di balik login milik orang lain" bukan sekadar etika, tapi kepatuhan.
Troubleshooting yang sering muncul
| Masalah | Penyebab | Solusi |
|---|---|---|
CAPTCHA_NOT_READY berputar tanpa henti |
Sitekey salah atau solve masih berjalan | Verifikasi sitekey; naikkan nilai timeout |
403 Forbidden saat POST |
Cookie atau header kurang | Pakai session cookie; tambahkan header Referer |
| Cheerio tidak menemukan elemen | Konten dirender JavaScript | Pindah ke Puppeteer untuk halaman dinamis |
ECONNREFUSED |
Situs target membatasi laju permintaan | Tambah jeda; rotasi proxy |
Kalau CAPTCHA_NOT_READY benar-benar tak pernah berubah, sitekey yang salah adalah tersangka nomor satu — pastikan Anda mengambilnya dari elemen yang benar dan bukan nilai lama yang di-cache.
Pertanyaan yang sering diajukan
Berapa thread yang saya butuhkan untuk scraping concurrent?
Set concurrency tidak lebih dari jumlah thread di paket Anda, karena satu thread setara satu CAPTCHA yang sedang berjalan. BASIC ($15/bulan) memberi 5 thread, ADVANCE ($90/bulan) memberi 50 thread. Solve per thread tak terbatas, jadi throughput dibatasi oleh jumlah thread dan kecepatan solve per tipe, bukan oleh kuota harian.
Apakah CaptchaAI bisa menyelesaikan hCaptcha saat scraping?
Belum. hCaptcha tidak didukung saat ini, begitu juga FunCaptcha (Arkose Labs); untuk kedua tipe itu Anda memerlukan layanan lain. Yang didukung mencakup reCAPTCHA v2/v3, Cloudflare Turnstile dan Challenge, GeeTest v3, serta CAPTCHA gambar/OCR dan grid.
Berapa lama waktu penyelesaian reCAPTCHA v2 dan Turnstile?
Cloudflare Turnstile biasanya selesai di bawah 10 detik dan reCAPTCHA v2 di bawah 60 detik, dengan tingkat keberhasilan tinggi pada tipe yang didukung. Karena itu interval polling 5 detik pada modul di atas sudah pas untuk sebagian besar kasus.
Bagaimana menangani situs yang memakai Cloudflare Turnstile?
Panggil solver.solveTurnstile() dan kirim hasilnya pada field cf-turnstile-response. Untuk halaman Cloudflare Challenge penuh, ikuti panduan menyelesaikan Cloudflare via API yang mengembalikan cookie qa_validation_cookie untuk dipakai pada permintaan berikutnya.