Onlarca ya da yüzlerce CAPTCHA'yı aynı anda göndermeye çalışırsanız kısa sürede ERROR_NO_SLOT_AVAILABLE hatasına, boşa giden isteklere ve tutarsız çözüm sürelerine çarparsınız. Çözüm nettir: gönderimleri bir kuyruğun arkasına alıp eşzamanlılığı planınızın thread sayısına göre sınırlamak. Bu rehber, tek satırlık Promise.allSettled'dan üretimde çalışan öncelik ve yeniden deneme kuyruklarına kadar CaptchaAI API'yi Node.js'te ölçekli kullanmanın kalıplarını gösterir.
Eşzamanlılık doğrudan CaptchaAI planınızdaki thread sayısıyla eşleşir: BASIC ($15/ay, 5 thread) ile aynı anda 5 çözüm, ADVANCE ($90/ay, 50 thread) ile 50 çözüm çalıştırabilirsiniz. maxConcurrent değerini bu sınırın üstüne çıkarmak yalnızca hata üretir. Aşağıdaki modellerin tamamı bu tek sayıyı merkeze alır.
Hangi kuyruğu ne zaman kullanacağınızı iş yükünüz belirler:
- Sabit ve küçük gruplar →
Promise.allSettledile tek satırlık toplu çözüm. - Sürekli akan iş → eşzamanlılığı sınırlayan kuyruk.
- Canlı ilerleme veya panel →
EventEmittertabanlı kuyruk. - Karışık öncelikli iş (ödeme + veri kazıma) → öncelik kuyruğu.
- Kırılgan hedefler ve ağ dalgalanması → yeniden deneme ve dead-letter kuyruğu.
En basit yöntem: Promise.allSettled ile toplu çözüm
Sabit ve küçük bir görev grubunuz varsa en pratik başlangıç Promise.allSettled'dır. Tüm çözümleri paralel başlatır, biri başarısız olsa bile diğerlerini beklemeye devam eder ve her görevin sonucunu ayrı ayrı döndürür:
const API_KEY = "YOUR_API_KEY";
function sleep(ms) {
return new Promise((r) => setTimeout(r, ms));
}
async function solveSingle(method, params) {
const submitResp = await fetch("https://ocr.captchaai.com/in.php", {
method: "POST",
body: new URLSearchParams({ key: API_KEY, method, json: "1", ...params }),
});
const submitData = await submitResp.json();
if (submitData.status !== 1) throw new Error(submitData.request);
const taskId = submitData.request;
for (let i = 0; i < 30; i++) {
await sleep(5000);
const pollResp = await fetch(
`https://ocr.captchaai.com/res.php?${new URLSearchParams({
key: API_KEY,
action: "get",
id: taskId,
json: "1",
})}`
);
const data = await pollResp.json();
if (data.status === 1) return data.request;
if (data.request === "ERROR_CAPTCHA_UNSOLVABLE") throw new Error("Unsolvable");
}
throw new Error("Timed out");
}
// Solve all at once
async function solveBatch(tasks) {
const results = await Promise.allSettled(
tasks.map((task) => solveSingle(task.method, task.params))
);
return results.map((result, i) => ({
taskId: tasks[i].id,
status: result.status,
value: result.status === "fulfilled" ? result.value : null,
error: result.status === "rejected" ? result.reason.message : null,
}));
}
// Usage
const tasks = Array.from({ length: 10 }, (_, i) => ({
id: i,
method: "userrecaptcha",
params: { googlekey: `KEY_${i}`, pageurl: `https://example.com/${i}` },
}));
const results = await solveBatch(tasks);
console.log(`Solved: ${results.filter((r) => r.status === "fulfilled").length}/10`);
Eşzamanlılığı sınırlayan kuyruk
Promise.allSettled on görev için sorunsuzdur; ama aynı anda 500 istek başlatırsanız plan thread'lerinizi anında doldurup ERROR_NO_SLOT_AVAILABLE hatasına düşersiniz. Aşağıdaki kuyruk, paralel çalışan çözüm sayısını maxConcurrent ile sabit tutar:
Pratik kural:
maxConcurrentdeğerini plan thread sayınızın biraz altında tutun. 5 thread'lik BASIC planında 4–5, 50 thread'lik ADVANCE planında 40–45 iyi bir başlangıç noktasıdır; böylece anlık trafik dalgalanmalarında bile boş slot kalır.
class ConcurrencyQueue {
constructor(maxConcurrent = 5) {
this.maxConcurrent = maxConcurrent;
this.running = 0;
this.queue = [];
this.results = [];
}
add(fn) {
return new Promise((resolve, reject) => {
this.queue.push({ fn, resolve, reject });
this.#process();
});
}
async #process() {
if (this.running >= this.maxConcurrent || this.queue.length === 0) return;
this.running++;
const { fn, resolve, reject } = this.queue.shift();
try {
const result = await fn();
resolve(result);
} catch (error) {
reject(error);
} finally {
this.running--;
this.#process();
}
}
async addBatch(fns) {
return Promise.allSettled(fns.map((fn) => this.add(fn)));
}
}
// Usage
const queue = new ConcurrencyQueue(5);
const tasks = Array.from({ length: 20 }, (_, i) => () =>
solveSingle("userrecaptcha", {
googlekey: `KEY_${i}`,
pageurl: `https://example.com/${i}`,
})
);
const results = await queue.addBatch(tasks);
const solved = results.filter((r) => r.status === "fulfilled");
console.log(`Solved: ${solved.length}/${results.length}`);
EventEmitter ile canlı ilerleme takibi
Uzun süren toplu işlerde hangi görevin çözüldüğünü, hangisinin başarısız olduğunu anlık görmek istersiniz. EventEmitter tabanlı kuyruk her adımda bir olay yayar; böylece ilerlemeyi log'a, panele veya webhook'a bağlayabilirsiniz:
const { EventEmitter } = require("events");
class CaptchaQueue extends EventEmitter {
#apiKey;
#maxConcurrent;
#pending;
#active;
constructor(apiKey, maxConcurrent = 5) {
super();
this.#apiKey = apiKey;
this.#maxConcurrent = maxConcurrent;
this.#pending = [];
this.#active = 0;
this.stats = { submitted: 0, solved: 0, failed: 0 };
}
submit(id, method, params) {
this.#pending.push({ id, method, params });
this.stats.submitted++;
this.emit("submitted", { id, total: this.stats.submitted });
this.#drain();
}
async #drain() {
while (this.#active < this.#maxConcurrent && this.#pending.length > 0) {
const task = this.#pending.shift();
this.#active++;
this.#solve(task).finally(() => {
this.#active--;
this.#drain();
if (this.#active === 0 && this.#pending.length === 0) {
this.emit("complete", this.stats);
}
});
}
}
async #solve(task) {
try {
const token = await solveSingle(task.method, task.params);
this.stats.solved++;
this.emit("solved", { id: task.id, token, stats: { ...this.stats } });
} catch (error) {
this.stats.failed++;
this.emit("failed", { id: task.id, error: error.message, stats: { ...this.stats } });
}
}
}
// Usage
const queue = new CaptchaQueue("YOUR_API_KEY", 5);
queue.on("submitted", ({ id, total }) => {
console.log(`Submitted #${id} (total: ${total})`);
});
queue.on("solved", ({ id, stats }) => {
console.log(`Solved #${id} — ${stats.solved}/${stats.submitted}`);
});
queue.on("failed", ({ id, error }) => {
console.log(`Failed #${id}: ${error}`);
});
queue.on("complete", (stats) => {
const rate = ((stats.solved / stats.submitted) * 100).toFixed(1);
console.log(`Done: ${stats.solved}/${stats.submitted} (${rate}%)`);
});
// Submit tasks
for (let i = 0; i < 15; i++) {
queue.submit(i, "userrecaptcha", {
googlekey: `KEY_${i}`,
pageurl: `https://example.com/${i}`,
});
}
Olayları izleme altyapınıza bağlayın
Yayılan solved, failed ve complete olaylarını doğrudan bir log toplayıcıya, bir metrik sayacına veya Slack webhook'una bağlayabilirsiniz. Böylece binlerce görevlik bir toplu işte hangi görevin nerede takıldığını gerçek zamanlı görür, sorun çıktığında toplu işin bitmesini beklemeden müdahale edersiniz.
Öncelik kuyruğu: kritik işleri öne alın
Her CAPTCHA aynı aciliyette değildir. Türkiye'deki bir e-ticaret entegrasyonunda kullanıcının beklediği ödeme adımı (checkout) doğrulaması, arka planda çalışan ürün veri kazıma görevinden önce çözülmelidir. Öncelik kuyruğu tam olarak bunu yapar — düşük öncelik numarasına sahip görevi önce işler:
class PriorityQueue {
#items = [];
enqueue(item, priority) {
this.#items.push({ item, priority });
this.#items.sort((a, b) => a.priority - b.priority);
}
dequeue() {
return this.#items.shift()?.item;
}
get length() {
return this.#items.length;
}
}
class PriorityCaptchaQueue {
#apiKey;
#maxConcurrent;
#queue;
#active;
#results;
constructor(apiKey, maxConcurrent = 5) {
this.#apiKey = apiKey;
this.#maxConcurrent = maxConcurrent;
this.#queue = new PriorityQueue();
this.#active = 0;
this.#results = new Map();
}
submit(id, method, params, priority = 5) {
return new Promise((resolve, reject) => {
this.#queue.enqueue({ id, method, params, resolve, reject }, priority);
this.#drain();
});
}
async #drain() {
while (this.#active < this.#maxConcurrent && this.#queue.length > 0) {
const task = this.#queue.dequeue();
this.#active++;
solveSingle(task.method, task.params)
.then((token) => {
this.#results.set(task.id, { status: "solved", token });
task.resolve(token);
})
.catch((err) => {
this.#results.set(task.id, { status: "error", error: err.message });
task.reject(err);
})
.finally(() => {
this.#active--;
this.#drain();
});
}
}
}
// Usage: high-priority checkout, low-priority scraping
const pq = new PriorityCaptchaQueue("YOUR_API_KEY", 3);
// Priority 1 (highest) — checkout
const checkoutToken = pq.submit(
"checkout_1",
"turnstile",
{ sitekey: "KEY", pageurl: "https://shop.com/checkout" },
1
);
// Priority 5 (normal) — product scraping
for (let i = 0; i < 5; i++) {
pq.submit(
`product_${i}`,
"userrecaptcha",
{ googlekey: "KEY", pageurl: `https://shop.com/p/${i}` },
5
);
}
Yeniden deneme ve dead-letter kuyruğu
Geçici hatalar — zaman aşımı ya da anlık ERROR_NO_SLOT_AVAILABLE — çoğu zaman ikinci denemede çözülür. Kalıcı hatalar ise belirli bir deneme sayısından sonra ayrı bir dead-letter kuyruğuna alınıp incelenmelidir. Aşağıdaki model bu iki durumu ayırır:
Hata türünü ayırmak önemlidir: ERROR_NO_SLOT_AVAILABLE gibi geçici durumlarda üstel geri çekilme (exponential backoff) ile bekleyip yeniden deneyin. Buna karşılık ERROR_WRONG_GOOGLEKEY gibi parametre hatalarında tekrar denemek yalnızca sürenizi ve kotanızı tüketir — bu görevleri doğrudan dead-letter kuyruğuna gönderip parametreleri elle düzeltmek daha doğrudur.
class RetryQueue {
#apiKey;
#maxRetries;
#results;
#deadLetter;
constructor(apiKey, maxRetries = 3) {
this.#apiKey = apiKey;
this.#maxRetries = maxRetries;
this.#results = [];
this.#deadLetter = [];
}
async processBatch(tasks, maxConcurrent = 5) {
const queue = tasks.map((t) => ({ ...t, attempts: 0 }));
while (queue.length > 0) {
const batch = queue.splice(0, maxConcurrent);
const results = await Promise.allSettled(
batch.map((task) => this.#solveWithRetry(task))
);
for (let i = 0; i < results.length; i++) {
const result = results[i];
const task = batch[i];
if (result.status === "fulfilled") {
this.#results.push({ id: task.id, token: result.value });
} else {
task.attempts++;
if (task.attempts < this.#maxRetries) {
queue.push(task); // Retry
console.log(`Retry ${task.attempts}/${this.#maxRetries}: ${task.id}`);
} else {
this.#deadLetter.push({
id: task.id,
error: result.reason.message,
attempts: task.attempts,
});
}
}
}
}
return {
solved: this.#results,
failed: this.#deadLetter,
};
}
async #solveWithRetry(task) {
return solveSingle(task.method, task.params);
}
}
İzleme panosu
Kuyruğun sağlığını ölçmeden yönetemezsiniz. Aşağıdaki izleyici; ortalama çözüm süresini, çıktıyı (throughput) ve başarı oranını tek raporda toplar:
class QueueMonitor {
#startTime;
#solveTimes;
constructor() {
this.#startTime = Date.now();
this.#solveTimes = [];
this.counts = { submitted: 0, solving: 0, solved: 0, failed: 0 };
}
recordSubmit() {
this.counts.submitted++;
this.counts.solving++;
}
recordSolved(solveTime) {
this.counts.solving--;
this.counts.solved++;
this.#solveTimes.push(solveTime);
}
recordFailed() {
this.counts.solving--;
this.counts.failed++;
}
report() {
const elapsed = (Date.now() - this.#startTime) / 1000;
const avgTime =
this.#solveTimes.length > 0
? this.#solveTimes.reduce((a, b) => a + b, 0) / this.#solveTimes.length
: 0;
const throughput = this.counts.solved / (elapsed / 60);
const successRate =
this.counts.solved + this.counts.failed > 0
? (this.counts.solved / (this.counts.solved + this.counts.failed)) * 100
: 0;
return {
elapsed: `${elapsed.toFixed(0)}s`,
submitted: this.counts.submitted,
solving: this.counts.solving,
solved: this.counts.solved,
failed: this.counts.failed,
avgSolveTime: `${(avgTime / 1000).toFixed(1)}s`,
throughput: `${throughput.toFixed(1)}/min`,
successRate: `${successRate.toFixed(1)}%`,
};
}
}
Üretim kontrol listesi
Kuyruğu canlıya almadan önce şu maddeleri doğrulayın:
maxConcurrentdeğeri plan thread sayınızın altında mı?- Kalıcı hatalar için dead-letter kuyruğu ve bir alarm mekanizması tanımlı mı?
res.phpsorgulama aralığı ve zaman aşımı makul mü (örneğin 5 saniyede bir, en fazla 30 deneme)?- Bellek büyümesini önlemek için sonuçlar periyodik olarak işlenip temizleniyor mu?
- İzleme; ortalama çözüm süresi, çıktı (throughput) ve başarı oranını raporluyor mu?
Sorun giderme
| Belirti | Sebep | Düzeltme |
|---|---|---|
| Tüm Promise'ler aynı anda reddediliyor | API hız sınırına takıldınız | maxConcurrent değerini düşürün |
| Bellek zamanla şişiyor | Sonuçlar bellekte birikiyor | Sonuçları periyodik olarak işleyip temizleyin |
| Kuyruk boşalıyor ama görevler kalıyor | Tamamlanma sonrası drain() çağrısı eksik |
finally bloğundaki drain tetikleyicisini doğrulayın |
ERROR_NO_SLOT_AVAILABLE |
Çok fazla eşzamanlı API çağrısı | Gönderimler arasına gecikme ekleyin, maxConcurrent'i düşürün |
| Dead-letter kuyruğu doluyor | Kalıcı hatalar | Hata türlerini inceleyin; genelde parametre düzeltmesi gerekir |
Sık sorulan sorular
maxConcurrent değerini CaptchaAI planıma göre nasıl seçerim?
Başlangıç noktanız planınızın thread sayısıdır: BASIC'te 5, ADVANCE'te ($90/ay, 50 thread) 50 eşzamanlı çözüm. Bu sınırın hemen altında başlayın ve ERROR_NO_SLOT_AVAILABLE görünce geri çekilin.
Sunucu yeniden başladığında kuyruktaki görevler kaybolur mu?
Bu yazıdaki kuyruklar bellekte tutulur; süreç yeniden başlarsa bekleyen görevler silinir. Kalıcılık gerekiyorsa bull veya bullmq gibi Redis destekli bir kuyruk kullanın — görevler disk üzerinde saklanır ve çökme sonrası kaldığı yerden devam eder.
ERROR_NO_SLOT_AVAILABLE hatasını sürekli alıyorum, ne yapmalıyım?
Bu hata, aynı anda çalışan çözüm sayısının plan thread'lerinizi aştığını gösterir. maxConcurrent değerini düşürün, gönderimler arasına kısa bir gecikme koyun veya daha yüksek thread sunan bir plana geçin.
Dead-letter kuyruğuna düşen görevlerle ne yapmalıyım?
Bu görevler yeniden deneme hakkını tüketmiş kalıcı hatalardır ve genellikle yanlış sitekey, pageurl veya method parametresine işaret eder. Log'lardaki hata mesajına bakıp parametreleri düzeltin, ardından düzeltilen görevleri yeniden gönderin.
Birden fazla CAPTCHA türünü aynı kuyrukta çözebilir miyim?
Evet. solveSingle fonksiyonu method parametresini olduğu gibi API'ye iletir; aynı kuyruğa reCAPTCHA v2 (userrecaptcha), Cloudflare Turnstile (turnstile) ve GeeTest v3 (geetest) görevlerini birlikte gönderebilirsiniz. CaptchaAI planınız türe göre ayrı ücret almaz; tüm görevler aynı thread havuzunu paylaşır.
Özet
Node.js'in olay döngüsü ve asenkron I/O modeli, API yanıtı beklenen CAPTCHA çözümü için doğal bir kuyruk altyapısıdır. Küçük gruplar için Promise.allSettled, canlı ilerleme için EventEmitter, iş açısından kritik akışlar için öncelik kuyruğu ve dayanıklılık için yeniden deneme kuyruğu kullanın. Hepsinin ortak noktası tek bir ayardır: eşzamanlılığı CaptchaAI planınızın thread sayısına göre sınırlamak.