إبقاء عمليات حل الكابتشا مستمرة دون توقّف يتطلّب أكثر من مفتاح API واحد. فبتوزيع الطلبات على مجموعة من المفاتيح والتبديل بينها آلياً، يواصل خط المعالجة عمله حتى لو نفد رصيد أحد الحسابات أو بلغ حدّ المعدل.
يعرض هذا الدليل أربع استراتيجيات عملية للتدوير — الدائري، والمرجّح حسب الرصيد، وتجاوز الفشل التلقائي، والتحديث الدوري للأرصدة — مع أمثلة جاهزة بلغتي Python وJavaScript يمكنك تكييفها مباشرة مع حجم تشغيلك.
لماذا لا يكفي مفتاح API واحد؟
عند تمرير كل الطلبات عبر مفتاح واحد، يصبح هذا المفتاح نقطة الاعتماد الوحيدة، وأي خلل فيه يوقف العملية بأكملها. من أبرز أسباب توقّف المفتاح المفرد:
- نفاد رصيد الحساب في منتصف التشغيل.
- بلوغ حدّ معدل الطلبات في أوقات الذروة.
- تعطيل المفتاح أو تغييره من لوحة التحكم.
- رفض المفتاح بسبب قيود عنوان IP أو صيانة مؤقتة للحساب.
توزيع الحمل على عدة مفاتيح يحوّل كلّاً من هذه الأعطال من توقّف كامل إلى تراجع مؤقت في السعة فقط.
استراتيجيات التدوير الأربع في سطور
قبل الدخول في التفاصيل، إليك متى تناسبك كل استراتيجية:
- التدوير الدائري: توزيع متساوٍ بين مفاتيح متقاربة الرصيد.
- التدوير المرجّح: حصص أكبر للمفاتيح الأعلى رصيداً.
- تجاوز الفشل: الانتقال إلى مفتاح آخر فور فشل الطلب.
- التحديث الدوري: إعادة قراءة الأرصدة لإعادة المفاتيح المُعاد شحنها إلى الخدمة.
يمكنك دمج أكثر من استراتيجية معاً؛ فكثير من عمليات الإنتاج تجمع التدوير المرجّح مع تجاوز الفشل.
التدوير الدائري (Round-robin): توزيع الطلبات بالتساوي
أبسط الاستراتيجيات: مرّر الطلبات على المفاتيح بالتناوب، فيأخذ كل مفتاح نصيباً متساوياً من الحمل.
اختر هذه الطريقة عندما:
- تتقارب أرصدة الحسابات وقدراتها.
- تريد حلاً بسيطاً بأقل قدر من المنطق البرمجي.
- لا تحتاج إلى مراعاة فروق الرصيد بين المفاتيح.
Python
import itertools
import requests
API_KEYS = [
"KEY_ACCOUNT_1",
"KEY_ACCOUNT_2",
"KEY_ACCOUNT_3",
]
key_cycle = itertools.cycle(API_KEYS)
def get_next_key():
return next(key_cycle)
def solve_captcha(sitekey, page_url):
api_key = get_next_key()
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": api_key,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"json": "1",
})
data = resp.json()
if data["status"] != 1:
raise Exception(f"[{api_key[:8]}...] {data['request']}")
print(f"Submitted with key {api_key[:8]}...")
return data["request"], api_key
task_id, used_key = solve_captcha("6Le-SITEKEY", "https://example.com")
التدوير المرجّح حسب الرصيد
عندما تتفاوت أرصدة الحسابات، وجّه نصيباً أكبر من الطلبات إلى المفاتيح الأعلى رصيداً. يقرأ المُدوِّر التالي رصيد كل مفتاح عبر action=getbalance، ثم يوزّع الحمل عشوائياً بترجيح الرصيد ويستبعد أي مفتاح نفد.
مثال عملي: وكالة أتمتة في الرياض أو القاهرة تدير حسابات عملاء متعددة، لكل منها حساب مستقل على خطة ADVANCE ($90 شهريًا، 50 مساراً متزامناً). بتوزيع الطلبات عبر مفاتيح هذه الحسابات تجمع الوكالة مسارات المعالجة، ولا يوقف نفاد رصيد عميل واحد بقية العمليات.
هذه الطريقة مفيدة عند:
- اختلاف خطط الحسابات أو أرصدتها بوضوح.
- رغبتك في استهلاك الرصيد الأكبر أولاً لتأخير أول نفاد.
import random
import requests
import threading
SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"
class KeyRotator:
def __init__(self, keys):
self.keys = {k: {"balance": 0, "failures": 0, "disabled": False} for k in keys}
self._lock = threading.Lock()
self.refresh_balances()
def refresh_balances(self):
for key in self.keys:
try:
resp = requests.get(RESULT_URL, params={
"key": key, "action": "getbalance", "json": "1"
}, timeout=10).json()
if resp["status"] == 1:
self.keys[key]["balance"] = float(resp["request"])
self.keys[key]["disabled"] = False
else:
self.keys[key]["disabled"] = True
except Exception:
self.keys[key]["disabled"] = True
def get_key(self):
with self._lock:
available = {
k: v for k, v in self.keys.items()
if not v["disabled"] and v["balance"] > 0.01
}
if not available:
raise Exception("No API keys with balance available")
# Weighted random by balance
keys = list(available.keys())
weights = [available[k]["balance"] for k in keys]
return random.choices(keys, weights=weights, k=1)[0]
def report_failure(self, key, error_code):
with self._lock:
self.keys[key]["failures"] += 1
if error_code in ("ERROR_WRONG_USER_KEY", "ERROR_KEY_DOES_NOT_EXIST",
"ERROR_ZERO_BALANCE", "ERROR_IP_NOT_ALLOWED"):
self.keys[key]["disabled"] = True
print(f"[rotator] Disabled key {key[:8]}...: {error_code}")
def report_success(self, key, cost=0.003):
with self._lock:
self.keys[key]["balance"] -= cost
self.keys[key]["failures"] = 0
rotator = KeyRotator(["KEY_1", "KEY_2", "KEY_3"])
# Usage
api_key = rotator.get_key()
# ... solve captcha ...
rotator.report_success(api_key)
تجاوز الفشل التلقائي (Failover)
بدل إيقاف العملية عند أول خطأ، انتقل إلى المفتاح التالي. تجرّب الدالة حتى ثلاثة مفاتيح قبل أن تستسلم.
تميّز هذه الاستراتيجية بين نوعين من الأخطاء:
- أخطاء دائمة مثل
ERROR_WRONG_USER_KEY— تُعطّل المفتاح فوراً. - أخطاء مؤقتة مثل انقطاع الشبكة — يُعاد فيها المحاولة بمفتاح آخر دون تعطيل.
Python
def solve_with_failover(sitekey, page_url, max_attempts=3):
for attempt in range(max_attempts):
api_key = rotator.get_key()
try:
resp = requests.post(SUBMIT_URL, data={
"key": api_key,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"json": "1",
}, timeout=15)
data = resp.json()
if data["status"] != 1:
rotator.report_failure(api_key, data["request"])
continue
rotator.report_success(api_key)
return data["request"], api_key
except requests.RequestException:
rotator.report_failure(api_key, "NETWORK_ERROR")
continue
raise Exception(f"All {max_attempts} keys failed")
JavaScript
const axios = require('axios');
class KeyRotator {
constructor(keys) {
this.keys = keys.map(k => ({ key: k, disabled: false, failures: 0 }));
this.index = 0;
}
getKey() {
const available = this.keys.filter(k => !k.disabled);
if (available.length === 0) throw new Error('No API keys available');
const entry = available[this.index % available.length];
this.index++;
return entry.key;
}
disable(key, reason) {
const entry = this.keys.find(k => k.key === key);
if (entry) {
entry.disabled = true;
console.log(`[rotator] Disabled ${key.substring(0, 8)}...: ${reason}`);
}
}
}
const rotator = new KeyRotator(['KEY_1', 'KEY_2', 'KEY_3']);
async function solveWithFailover(sitekey, pageurl, maxAttempts = 3) {
for (let i = 0; i < maxAttempts; i++) {
const apiKey = rotator.getKey();
try {
const resp = await axios.post('https://ocr.captchaai.com/in.php', null, {
params: { key: apiKey, method: 'userrecaptcha', googlekey: sitekey, pageurl, json: 1 }
});
if (resp.data.status !== 1) {
rotator.disable(apiKey, resp.data.request);
continue;
}
return { taskId: resp.data.request, apiKey };
} catch (err) {
rotator.disable(apiKey, 'NETWORK_ERROR');
}
}
throw new Error('All keys failed');
}
تحميل المفاتيح من متغيرات البيئة
لا تضَع المفاتيح داخل الشيفرة مطلقاً؛ حمّلها من متغيرات البيئة كي لا تتسرّب إلى نظام التحكم بالإصدارات، مفصولةً بفواصل.
طبّق هذه القواعد لحماية بيانات الاعتماد:
- احفظ المفاتيح في متغيّر بيئة واحد مفصولة بفواصل.
- استبعد ملفات البيئة من نظام التحكم بالإصدارات.
- امنح كل بيئة تشغيل مجموعة مفاتيحها الخاصة.
import os
API_KEYS = os.environ["CAPTCHAAI_KEYS"].split(",")
# Set: CAPTCHAAI_KEYS=key1,key2,key3
rotator = KeyRotator(API_KEYS)
const API_KEYS = process.env.CAPTCHAAI_KEYS.split(',');
const rotator = new KeyRotator(API_KEYS);
تحديث الأرصدة دوريًا في العمليات طويلة الأمد
في العمليات الطويلة تتغيّر الأرصدة باستمرار. شغّل خيط معالجة في الخلفية يحدّث الأرصدة كل بضع دقائق، لتُعاد المفاتيح المُعاد شحنها إلى الخدمة تلقائياً.
اضبط الفاصل الزمني بحسب وتيرة إعادة الشحن لديك؛ فالفاصل الأقصر يكتشف عودة الرصيد أسرع لكنه يضيف طلبات getbalance إضافية:
import threading
def periodic_refresh(rotator, interval=300):
def refresh():
while True:
rotator.refresh_balances()
for key, info in rotator.keys.items():
print(f" {key[:8]}...: ${info['balance']:.2f} "
f"{'(disabled)' if info['disabled'] else '(active)'}")
threading.Event().wait(interval)
t = threading.Thread(target=refresh, daemon=True)
t.start()
periodic_refresh(rotator, interval=300) # every 5 minutes
مراقبة صحة المفاتيح أثناء التشغيل
لا يكفي أن يعمل التدوير؛ تحتاج إلى رؤية واضحة لحالة كل مفتاح كي تتصرّف قبل أن تنفد السعة. راقب هذه المؤشّرات باستمرار:
- عدد المفاتيح النشطة مقابل المعطّلة في كل لحظة.
- معدّل الأخطاء لكل مفتاح للكشف عن حساب مضطرب.
- الرصيد المتبقّي لتوقّع موعد إعادة الشحن.
عند هبوط عدد المفاتيح النشطة إلى مفتاح واحد، أرسل تنبيهاً فورياً بدل انتظار التوقّف الكامل.
أفضل الممارسات لتدوير المفاتيح
تجعل هذه الممارسات التدوير أكثر استقراراً في بيئة الإنتاج:
- عطّل المفتاح عند الأخطاء الدائمة فقط، لا عند كل خطأ عابر.
- أمّن الحالة المشتركة بقفل (lock) عند تعدّد مسارات التنفيذ.
- سجّل كل تعطيل وإعادة تفعيل لتتبّع سلوك المفاتيح.
- وزّع المفاتيح على حسابات مستقلة كي لا يؤثّر عطل حساب في البقية.
استكشاف الأخطاء وإصلاحها
تعالج القائمة التالية أكثر المشكلات شيوعاً عند تشغيل التدوير:
| المشكلة | السبب المحتمل | الحل |
|---|---|---|
| جميع المفاتيح معطّلة | الرصيد صفر في كل الحسابات | أعد الشحن وتحقّق من ERROR_ZERO_BALANCE |
| يُستخدَم المفتاح نفسه دائماً | مؤشّر التدوير لا يتقدّم | أمّن مسارات التنفيذ بقفل (lock) |
| تعطيل مفتاح دون مبرّر | معاملة خطأ مؤقت كأنه دائم | عطّل فقط عند ERROR_WRONG_USER_KEY وERROR_ZERO_BALANCE وERROR_IP_NOT_ALLOWED |
الأسئلة الشائعة
هل يزيد تدوير عدة مفاتيح من تكلفة حل الكابتشا؟
لا. تعتمد فوترة CaptchaAI على عدد المسارات المتزامنة (threads) في كل خطة لا على عدد عمليات الحل، ولكل مفتاح حسابه وخطته. فتوزيع الطلبات على عدة مفاتيح يجمع مساراتها دون رسوم إضافية.
متى أختار التدوير المرجّح بدل الدائري؟
استخدم الدائري حين تتقارب أرصدة الحسابات، وانتقل إلى المرجّح حسب الرصيد عندما تتفاوت الأرصدة كي لا يُستنزَف مفتاح قبل غيره.
كيف أتعامل مع خطأ ERROR_ZERO_BALANCE أثناء التشغيل؟
عطّل المفتاح فور ظهور الخطأ، ثم أعد المحاولة بمفتاح آخر. وعند إعادة شحن الحساب، يكتشف التحديث الدوري عودة الرصيد ويعيد تفعيل المفتاح.
هل التدوير آمن مع تعدّد مسارات التنفيذ؟
نعم، شرط حماية الحالة المشتركة بقفل (lock) كما في KeyRotator أعلاه. فمن دون القفل قد يقرأ أكثر من مسار المؤشر نفسه فيستخدم مفتاحاً واحداً باستمرار.
كم عدد المفاتيح المناسب لبدء التدوير؟
يكفي مفتاحان لتأمين تجاوز الفشل الأساسي، وتتيح ثلاثة مفاتيح أو أكثر توزيعاً حقيقياً للحمل. زد العدد تدريجياً مع نمو حجم التشغيل ومراقبة معدّل الأخطاء.
وسّع نطاق حل الكابتشا عبر تدوير عدّة مفاتيح
أنشئ حسابك واحصل على مفتاح API من captchaai.com، ثم طبّق التدوير الأنسب لحجم تشغيلك.