Node.js est monothread mais excelle dans la concurrence I/O - parfait pour résoudre CAPTCHA lorsque vous attendez les réponses de l'API. Ce guide couvre les modèles de file d'attente, du simple Promise.all aux systèmes de tâches de production.
Lot simple : Promise.allSettled
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`);
File d'attente limitée en concurrence
Contrôlez le nombre de résolutions CAPTCHA exécutées en parallèle :
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}`);
File d'attente basée sur EventEmitter
Pour un suivi des progrès en temps réel :
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}`,
});
}
File d'attente prioritaire
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
);
}
File d'attente de nouvelle tentative avec gestion des lettres mortes
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);
}
}
Tableau de bord de surveillance
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)}%`,
};
}
}
Dépannage
| Symptôme | Parce que | Corriger |
|---|---|---|
| Toutes les promesses sont rejetées simultanément | Limite de débit de l'API atteinte | Inférieur maxConcurrent |
| La mémoire augmente avec le temps | Les résultats s’accumulent | Traiter et clarifier les résultats périodiquement |
| La file d'attente s'épuise mais les tâches restent | Appel drain() manquant après la fin |
Vérifiez la gâchette de vidange dans le bloc final |
ERROR_NO_SLOT_AVAILABLE |
Trop d'appels API simultanés | Ajouter un délai entre les soumissions |
| La file d'attente des lettres mortes se remplit | Erreurs persistantes | Vérifiez les types d'erreurs - peut nécessiter une correction des paramètres |
Questions fréquemment posées
Combien de résolutions simultanées dois-je exécuter ?
Commencez par 5-10 et augmentez en fonction de votre plan CaptchaAI. Surveillez ERROR_NO_SLOT_AVAILABLE comme signal d'accélérateur.
Dois-je utiliser une bibliothèque comme p-queue ou bull ?
Pour les cas d'utilisation simples, les modèles intégrés ci-dessus sont suffisants. Utilisez bull ou bullmq pour les files d'attente persistantes sauvegardées par Redis dans les configurations multi-serveurs de production.
Comment gérer la contre-pression dans la file d’attente ?
Limitez la taille de la file d’attente et rejetez ou retardez les nouvelles soumissions lorsqu’elles sont pleines. Le modèle ConcurrencyQueue gère cela naturellement.
Résumé
Node.js excelle avec I/O simultané – parfait pour les systèmes de file d'attente CAPTCHA avecCaptchaAI. Utilisez Promise.allSettled pour les lots simples, EventEmitter pour le suivi de la progression, les files d'attente prioritaires pour les flux critiques pour l'entreprise et les files d'attente de nouvelles tentatives pour plus de fiabilité.
Articles connexes
- Création d'une file d'attente de résolution de Captcha Python
Étapes suivantes
- Démarrage rapide CaptchaAI : votre première résolution de CAPTCHA en 5 minutes
- Comment résoudre reCAPTCHA v2 via l'API : guide pas à pas
- Comment résoudre Cloudflare Turnstile via l'API
- Comment résoudre GeeTest v3 à l'aide de l'API