Actor Apify Anda berjalan mulus sampai halaman target menampilkan reCAPTCHA v2, lalu berhenti. Perbaikannya singkat: panggil solver CaptchaAI di dalam requestHandler, suntikkan token g-recaptcha-response yang dikembalikan, submit form, dan crawl lanjut seperti biasa. Karena CaptchaAI hanyalah panggilan API HTTP, integrasinya berjalan di dalam Actor tanpa dependensi tambahan yang berat dan tanpa mengubah arsitektur Crawlee Anda.
Panduan ini menunjukkan pola produksi lengkapnya di atas Apify: skema input Actor, kelas solver, penyimpanan API key yang aman, kombinasi dengan proxy Apify, plus catatan biaya yang relevan untuk tim scraping di Indonesia.
Alur integrasi dalam empat langkah
Sebelum masuk ke kode, ini kerangka yang dipakai di seluruh contoh — sama seperti integrasi CaptchaAI mana pun:
- Kirim sitekey dan URL halaman ke
in.phpdenganmethod=userrecaptcha. - Simpan task ID yang dikembalikan pada respons pertama.
- Polling
res.phpsampai statusnya1(biasanya butuh belasan detik untuk reCAPTCHA v2). - Pakai token yang diterima: suntikkan ke field
g-recaptcha-response, panggil callback jika ada, lalu submit.
Kelas CaptchaAISolver di bawah membungkus keempat langkah ini menjadi satu method solve(), sehingga logika crawl Anda tetap bersih.
Menyiapkan Actor: skema input
Skema input memberi Actor sebuah form di UI Apify. Tandai captchaaiApiKey sebagai isSecret supaya nilainya tidak pernah tampil sebagai teks biasa di log atau riwayat run.
{
"title": "CAPTCHA Scraper Input",
"type": "object",
"properties": {
"startUrls": {
"title": "Start URLs",
"type": "array",
"editor": "requestListSources"
},
"captchaaiApiKey": {
"title": "CaptchaAI API Key",
"type": "string",
"isSecret": true
},
"maxConcurrency": {
"title": "Max Concurrency",
"type": "integer",
"default": 3
}
},
"required": ["startUrls", "captchaaiApiKey"]
}
maxConcurrency di sini menentukan berapa banyak halaman yang diproses paralel. Selaraskan angka ini dengan jumlah thread pada paket CaptchaAI Anda — misalnya paket BASIC ($15/bulan, 5 thread) nyaman untuk concurrency 3–5, sementara volume besar butuh tier yang lebih tinggi.
Kode Actor lengkap
Kelas solver di bawah memakai pola submit-lalu-polling standar. Perhatikan requestHandlerTimeoutSecs: 180 — nilai ini penting karena satu penyelesaian reCAPTCHA v2 bisa memakan belasan detik, dan timeout default Crawlee terlalu ketat untuk itu. Setelah token didapat, kode menyuntikkannya ke g-recaptcha-response, memicu callback bila halaman mendefinisikannya, lalu menekan tombol submit.
const { Actor } = require('apify');
const { PlaywrightCrawler } = require('crawlee');
Actor.main(async () => {
const input = await Actor.getInput();
const { startUrls, captchaaiApiKey, maxConcurrency = 3 } = input;
const solver = new CaptchaAISolver(captchaaiApiKey);
const crawler = new PlaywrightCrawler({
maxConcurrency,
requestHandlerTimeoutSecs: 180,
async requestHandler({ request, page, log }) {
await page.goto(request.url, { waitUntil: 'networkidle' });
// Check for CAPTCHA
const sitekey = await page.evaluate(() => {
const el = document.querySelector('[data-sitekey]');
return el ? el.getAttribute('data-sitekey') : null;
});
if (sitekey) {
log.info(`Solving CAPTCHA on ${request.url}`);
const token = await solver.solve(sitekey, request.url);
// Inject and submit
await page.evaluate((t) => {
document.querySelector('[name="g-recaptcha-response"]').value = t;
const cb = document.querySelector('.g-recaptcha')?.getAttribute('data-callback');
if (cb && window[cb]) window[cb](t);
}, token);
await page.click('button[type="submit"]');
await page.waitForNavigation({ timeout: 15000 });
}
// Extract data
const title = await page.title();
const items = await page.$$eval('.item', els =>
els.map(el => ({
name: el.querySelector('.name')?.textContent?.trim(),
price: el.querySelector('.price')?.textContent?.trim(),
url: el.querySelector('a')?.href,
}))
);
// Push to Apify dataset
await Actor.pushData({
url: request.url,
title,
items,
scrapedAt: new Date().toISOString(),
});
log.info(`Scraped ${items.length} items from ${request.url}`);
},
});
await crawler.run(startUrls);
});
class CaptchaAISolver {
constructor(apiKey) {
this.apiKey = apiKey;
}
async solve(sitekey, pageurl) {
const params = new URLSearchParams({
key: this.apiKey,
method: 'userrecaptcha',
googlekey: sitekey,
pageurl: pageurl,
json: '1',
});
const submitResp = await fetch('https://ocr.captchaai.com/in.php', {
method: 'POST',
body: params,
});
const submitResult = await submitResp.json();
if (submitResult.status !== 1) {
throw new Error(`Submit: ${submitResult.request}`);
}
const taskId = submitResult.request;
await new Promise(r => setTimeout(r, 15000));
for (let i = 0; i < 24; i++) {
const pollResp = await fetch(
`https://ocr.captchaai.com/res.php?key=${this.apiKey}&action=get&id=${taskId}&json=1`
);
const result = await pollResp.json();
if (result.status === 1) return result.request;
if (result.request !== 'CAPCHA_NOT_READY') {
throw new Error(`Solve: ${result.request}`);
}
await new Promise(r => setTimeout(r, 5000));
}
throw new Error('Timeout');
}
}
Deteksi sitekey di atas mengambil atribut data-sitekey yang pertama ditemukan. Jika reCAPTCHA berada di dalam iframe, akses page.frames() atau tunggu selector container Turnstile/reCAPTCHA muncul lebih dulu sebelum membaca sitekey.
Menyimpan API key sebagai variabel lingkungan
Melewatkan API key lewat input Actor praktis untuk sekali jalan, tetapi untuk Actor yang dijadwalkan rutin lebih rapi menyimpannya sebagai variabel lingkungan rahasia:
- Buka pengaturan Actor -> Environment variables
- Tambahkan
CAPTCHAAI_API_KEY= key Anda, lalu tandai sebagai rahasia - Akses di kode lewat
process.env.CAPTCHAAI_API_KEY
// Alternative: use env var instead of input
const apiKey = input.captchaaiApiKey || process.env.CAPTCHAAI_API_KEY;
Pola input.captchaaiApiKey || process.env.CAPTCHAAI_API_KEY memberi Anda keduanya: input mengalahkan environment saat ada, dan variabel lingkungan jadi cadangan untuk run terjadwal.
Menggabungkan proxy Apify dengan CaptchaAI
Proxy dan penyelesaian CAPTCHA adalah dua urusan terpisah. Biarkan proxy Apify menangani egress permintaan crawl, dan biarkan CaptchaAI fokus mengembalikan token. Anda tidak perlu meneruskan proxy yang sama ke solver — untuk mayoritas kasus, memisahkan keduanya adalah pendekatan paling hemat.
Grup RESIDENTIAL pada contoh di bawah cocok saat target menandai IP pusat data, tetapi Apify juga menyediakan grup datacenter yang jauh lebih murah untuk situs yang tidak seketat itu. Mulai dari grup termurah, lalu naik hanya jika crawl Anda mulai kena blokir — biaya proxy sering kali jadi komponen terbesar dalam total tagihan sebuah Actor.
const crawler = new PlaywrightCrawler({
proxyConfiguration: await Actor.createProxyConfiguration({
groups: ['RESIDENTIAL'],
}),
// ... rest of config
});
Catatan biaya dan kepatuhan untuk tim di Indonesia
Total biaya Anda punya dua komponen: biaya komputasi Apify (dihitung dari waktu jalan Actor) plus paket CaptchaAI. Karena CaptchaAI menagih per thread dengan penyelesaian tanpa batas per thread, bukan per solve, biaya penyelesaian CAPTCHA Anda tetap flat berapa pun jumlah halaman yang di-crawl dalam sebulan. Untuk pekerja freelance scraping dan agensi price-monitoring yang sensitif terhadap biaya, model flat ini jauh lebih mudah diprediksi ketimbang tarif per solve saat volume naik.
Soal latensi, jika Actor Anda deploy dekat region Asia Tenggara — misalnya ap-southeast-1 (Singapura) atau ap-southeast-3 (Jakarta) — polling ke res.php menambah beberapa detik per solve; setel requestHandlerTimeoutSecs sesuai. Terakhir, satu catatan kepatuhan: scrape hanya data yang memang boleh Anda proses dan hindari data pribadi, sejalan dengan UU Pelindungan Data Pribadi (UU 27/2022). Ini bukan nasihat hukum, hanya pengingat praktis.
Pertanyaan umum
Berapa biaya menjalankan solver CAPTCHA di Actor Apify?
Dua bagian: biaya komputasi Apify plus paket CaptchaAI berbasis thread. Karena setiap thread memberi penyelesaian tanpa batas selama sebulan, biaya solver-nya tetap. Paket BASIC ($15/bulan, 5 thread) cukup untuk banyak Actor kecil.
Apakah CaptchaAI mendukung reCAPTCHA v3 dan Turnstile selain v2?
Ya. Selain reCAPTCHA v2, CaptchaAI menyelesaikan reCAPTCHA v3, Cloudflare Turnstile dan Challenge, GeeTest v3, serta CAPTCHA gambar/OCR. hCaptcha dan FunCaptcha belum didukung. Ubah method dan parameter sesuai tipe target.
Bagaimana jika sitekey berada di dalam iframe?
Selector [data-sitekey] hanya membaca dokumen utama. Untuk CAPTCHA di iframe, iterasi page.frames() dan baca sitekey dari frame yang tepat, atau tunggu container-nya ter-render sebelum mengekstrak nilainya.
Bisakah Actor terhenti karena run terlalu lama saat menunggu solve?
Bisa, kalau timeout terlalu ketat. Set requestHandlerTimeoutSecs minimal 180 detik agar ada ruang untuk polling penyelesaian, lalu tambah jika target Anda kerap menampilkan CAPTCHA berlapis.
Panduan terkait
- Integrasi Crawlee dengan CaptchaAI untuk scraping modern
- Membangun framework scraping Python sendiri
Pasang solver CAPTCHA di Actor Apify Anda — ambil API key CaptchaAI.