لتشغيل حل CAPTCHA على Azure دون إدارة أي خادم تحتاج إلى ثلاثة مكوّنات فقط:
- دالة Azure Function تستقبل الطلب وتُعيد الرمز المحلول.
- مفتاح CaptchaAI API محفوظ بأمان في Key Vault، لا في الكود.
- قائمة انتظار Queue Storage توزّع المهام عند تشغيل دفعات كبيرة.
يجمع هذا الدليل هذه القطع في مسار إنتاجي واحد: تدفع الكود، فيتولّى Azure توفير الموارد والتوسّع والمراقبة عبر Application Insights تلقائيًا، وتدفع فقط مقابل مدة التنفيذ الفعلية.
هذا النمط مناسب لأحمال العمل غير المنتظمة — اختبارات QA ليلية، أو معالجة نماذج محمية بـ reCAPTCHA على دفعات — حيث لا يبقى خادم مشغّلًا في أوقات الخمول. في الأقسام التالية نبني الدالة، ونؤمّن المفتاح، ونضيف معالجة الدُفعات، ثم ننشر ونراقب.
دالة مُفعّلة عبر HTTP لحل CAPTCHA
نقطة الدخول الأبسط هي دالة تُفعّل عبر HTTP: يرسل عميلك طلب POST يحمل نوع الـ CAPTCHA ومعاملاته، وتُرجع الدالة الرمز المحلول في استجابة JSON. الدالة أدناه تقرأ حمولة الطلب، وتقرأ المفتاح من متغيّرات البيئة، ثم تستدعي دالة solve التي تُرسل المهمة إلى CaptchaAI عبر in.php وتستطلع النتيجة من res.php حتى تجهز أو تنتهي المهلة.
لاحظ فصل منطق الحل في دالة solve مستقلة: هذا يجعلها قابلة لإعادة الاستخدام بين المُشغّل عبر HTTP والمُشغّل عبر قائمة الانتظار الذي نضيفه لاحقًا. المهلة الداخلية مضبوطة على 90 ثانية، وهي تتّسع لأوقات حل معظم الأنواع المدعومة مثل reCAPTCHA v2/v3 وCloudflare Turnstile.
# function_app.py
import json
import time
import os
import logging
import urllib.request
import urllib.parse
import azure.functions as func
app = func.FunctionApp()
@app.route(route="solve", methods=["POST"])
def solve_captcha(req: func.HttpRequest) -> func.HttpResponse:
"""HTTP trigger for CAPTCHA solving."""
try:
body = req.get_json()
except ValueError:
return func.HttpResponse(
json.dumps({"error": "JSON body required"}),
status_code=400,
mimetype="application/json",
)
method = body.get("method", "userrecaptcha")
params = body.get("params", {})
api_key = os.environ["CAPTCHAAI_KEY"]
try:
token = solve(api_key, method, params)
return func.HttpResponse(
json.dumps({"token": token}),
mimetype="application/json",
)
except Exception as e:
logging.error(f"Solve failed: {e}")
return func.HttpResponse(
json.dumps({"error": str(e)}),
status_code=500,
mimetype="application/json",
)
def solve(api_key, method, params, timeout=90):
"""Solve CAPTCHA via CaptchaAI API."""
submit_data = urllib.parse.urlencode({
"key": api_key,
"method": method,
"json": 1,
**params,
}).encode()
req = urllib.request.Request(
"https://ocr.captchaai.com/in.php",
data=submit_data,
)
with urllib.request.urlopen(req, timeout=30) as resp:
result = json.loads(resp.read())
if result.get("status") != 1:
raise RuntimeError(f"Submit error: {result.get('request')}")
task_id = result["request"]
start = time.time()
while time.time() - start < timeout:
time.sleep(5)
poll_url = (
f"https://ocr.captchaai.com/res.php"
f"?key={api_key}&action=get&id={task_id}&json=1"
)
with urllib.request.urlopen(poll_url, timeout=15) as resp:
data = json.loads(resp.read())
if data["request"] != "CAPCHA_NOT_READY":
if data.get("status") == 1:
return data["request"]
raise RuntimeError(f"Solve error: {data['request']}")
raise TimeoutError("Solve timeout")
حفظ مفتاح الـ API في Azure Key Vault
لا تكتب مفتاح CaptchaAI مباشرة في الكود أو في إعدادات نصية مكشوفة. احفظه في Azure Key Vault، ثم امنح الدالة هوية مُدارة (managed identity) تقرأ السر عند التشغيل فقط. بهذا يبقى المفتاح خارج المستودع وخارج سجلّات النشر، ويمكنك تدويره دون إعادة نشر الكود.
الخطوات التالية تُنشئ الخزنة، وتخزّن المفتاح، وتمنح الدالة صلاحية القراءة:
# Create Key Vault
az keyvault create \
--name captchaai-vault \
--resource-group myResourceGroup
# Store secret
az keyvault secret set \
--vault-name captchaai-vault \
--name CaptchaAIKey \
--value "YOUR_API_KEY"
# Grant function access
az webapp identity assign \
--name my-captcha-function \
--resource-group myResourceGroup
az keyvault set-policy \
--name captchaai-vault \
--object-id <principal-id> \
--secret-permissions get
بعد ذلك أشِر إلى السر من إعدادات التطبيق باستخدام مرجع Key Vault، فيصبح متاحًا للدالة عبر متغيّر البيئة CAPTCHAAI_KEY نفسه الذي يقرأه الكود:
CAPTCHAAI_KEY=@Microsoft.KeyVault(SecretUri=https://captchaai-vault.vault.azure.net/secrets/CaptchaAIKey/)
معالجة الدُفعات عبر Queue Storage
عندما يزيد الحجم، لا تُبقِ العميل منتظرًا استجابة HTTP لكل مهمة. بدلًا من ذلك ادفع المهام إلى قائمة انتظار Queue Storage، ودَع دالة مُفعّلة بالطابور تسحب كل رسالة وتحلّها بشكل غير متزامن. هذا يفصل الاستقبال عن المعالجة، ويجعل النظام يمتصّ ذروات الطلب دون فقدان أي مهمة.
الدالة أدناه تقرأ المهمة من الرسالة، وتستدعي دالة solve نفسها، ثم تخزّن النتيجة. عند حدوث خطأ معروف سجّله وأعد النتيجة بدل ترك الاستثناء يتصاعد، حتى لا تعيد Azure محاولة الرسالة إلى ما لا نهاية:
@app.queue_trigger(
arg_name="msg",
queue_name="captcha-tasks",
connection="AzureWebJobsStorage",
)
def process_queue_task(msg: func.QueueMessage):
"""Process CAPTCHA task from queue."""
task = json.loads(msg.get_body().decode())
api_key = os.environ["CAPTCHAAI_KEY"]
try:
token = solve(api_key, task["method"], task["params"])
logging.info(f"Task {task['id']} solved")
# Store result in Table Storage or return queue
_store_result(task["id"], "success", token)
except Exception as e:
logging.error(f"Task {task['id']} failed: {e}")
_store_result(task["id"], "error", str(e))
def _store_result(task_id, status, value):
"""Store result (simplified — use Table Storage in production)."""
logging.info(f"Result: {task_id} = {status}")
في الإنتاج استبدل التخزين المبسّط بكتابة النتيجة إلى Table Storage أو إعادتها إلى طابور نتائج، ليتمكّن باقي النظام من قراءتها.
بنية مشروع الدالة
نموذج البرمجة بملف واحد في Python يبقي المشروع بسيطًا: كل الدوال في function_app.py، مع ملفات تهيئة قليلة بجانبه.
captcha-function/
├── function_app.py
├── requirements.txt
├── host.json
└── local.settings.json
requirements.txt:
azure-functions
host.json:
{
"version": "2.0",
"functionTimeout": "00:02:00",
"logging": {
"logLevel": {
"default": "Information"
}
}
}
local.settings.json:
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "python",
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"CAPTCHAAI_KEY": "YOUR_API_KEY_FOR_LOCAL_DEV"
}
}
قيمة functionTimeout هنا مضبوطة على دقيقتين لتتّسع لدورة الاستطلاع كاملة؛ أما local.settings.json فيُستخدم في التطوير المحلي فقط ولا يُنشر مع الدالة.
نشر الدالة على Azure
أنشئ تطبيق الدالة على خطة Consumption، ثم انشر الكود، ثم اختبر نقطة النهاية بطلب POST يحمل نوع reCAPTCHA v2 ومعاملاته:
# Create function app
az functionapp create \
--resource-group myResourceGroup \
--consumption-plan-location westus2 \
--runtime python \
--runtime-version 3.11 \
--functions-version 4 \
--name my-captcha-solver \
--storage-account mystorageaccount
# Deploy
func azure functionapp publish my-captcha-solver
# Test
curl -X POST https://my-captcha-solver.azurewebsites.net/api/solve \
-H "Content-Type: application/json" \
-d '{
"method": "userrecaptcha",
"params": {
"googlekey": "SITE_KEY",
"pageurl": "https://example.com"
}
}'
استبدل SITE_KEY بمفتاح الموقع الحقيقي وعنوان الصفحة المستهدفة. نجاح الطلب يُعيد الرمز داخل حقل token، وهو جاهز لإدراجه في نموذجك.
جدولة دفعة مهام في قائمة الانتظار
لتغذية الدالة المُفعّلة بالطابور، أرسل رسائل JSON إلى قائمة الانتظار من أي سكربت. المثال أدناه يدفع عشر مهام دفعة واحدة، ويتكفّل Azure بتوزيعها على المثيلات المتاحة:
from azure.storage.queue import QueueClient
import json
queue = QueueClient.from_connection_string(
conn_str="YOUR_STORAGE_CONNECTION_STRING",
queue_name="captcha-tasks",
)
# Submit batch
for i in range(10):
task = {
"id": f"task-{i}",
"method": "userrecaptcha",
"params": {
"googlekey": "SITE_KEY",
"pageurl": f"https://example.com/page{i}",
},
}
queue.send_message(json.dumps(task))
print(f"Queued task-{i}")
المراقبة والاعتمادية في الإنتاج
بمجرد أن يصبح المسار حيًّا، تصبح الرؤية أهم من الكود. يجمع Application Insights تلقائيًا زمن كل استدعاء ومعدّل الأخطاء وأثر التتبّع الكامل، فتستطيع رصد ارتفاع أوقات الحل أو تكرار خطأ CAPCHA_NOT_READY قبل أن يؤثّر في العملاء. اجعل رسائل السجل تحمل معرّف المهمة كما في الأمثلة، ليسهل ربط الفشل بمهمة بعينها.
للاعتمادية في الإنتاج، التزم بثلاث قواعد:
- عالِج الأخطاء المتوقّعة صراحةً بدل ترك الاستثناء يتصاعد؛ فالاستثناء غير المُعالَج في مسار الطابور يُعيد الرسالة مرارًا.
- سجّل الخطأ وأعد النتيجة، واعتمد على قائمة الرسائل الميتة (dead-letter queue) لعزل ما يفشل بشكل متكرّر.
- اضبط عدد المهام المتزامنة بما يوافق عدد الـ threads في خطتك، لأن CaptchaAI يعمل بنموذج thread-based ولا فائدة من إرسال أكثر مما تسمح به الخطة.
متى تختار Azure Functions لحل CAPTCHA
النمط بدون خوادم يتفوّق تحديدًا في الحالات التالية:
- أحمال متقطّعة أو ذروات ليلية — تدفع مقابل زمن التنفيذ فقط، لا مقابل خادم خامل.
- دفعات كبيرة غير متزامنة — يمتصّ Queue Storage الطلبات ويوزّعها على المثيلات.
- فرق صغيرة بلا عبء تشغيلي — لا خوادم تُدار ولا تصحيحات نظام تشغيل.
تخيّل فريقًا في القاهرة أو الرياض يشغّل اختبارات QA ليلية على نماذج تسجيل محمية بـ reCAPTCHA: يبدأ التشغيل الساعة الثانية صباحًا، يعالج بضعة آلاف من الطلبات خلال ساعة، ثم يخمد. على خادم دائم تدفع مقابل 24 ساعة لتشغيل فعلي مدته ساعة؛ أما على Azure Functions فتدفع مقابل زمن التنفيذ فقط.
على جانب CaptchaAI، ما يحدّد الإنتاجية هو عدد الـ threads لا عدد الحلول، فكل الخطط تشمل حلولًا غير محدودة لكل thread. خطة مثل ADVANCE ($90/شهريًا، 50 thread) تسمح بخمسين مهمة CAPTCHA متزامنة، وهو ما يوازي عدد المثيلات التي قد يوفّرها Azure عند الذروة. طابِق الرقمين: لا فائدة من 200 استدعاء متزامن على Azure إذا كانت خطتك تسمح بخمسين thread فقط.
استكشاف الأخطاء وإصلاحها
| المشكلة | السبب | الإجراء |
|---|---|---|
| تنتهي مهلة الدالة عند 5 دقائق | المهلة الافتراضية | اضبط functionTimeout في host.json |
| مرجع Key Vault يُرجع قيمة فارغة | الهوية المُدارة أو السياسة مفقودة | فعّل الهوية المُدارة وأضِف سياسة القراءة في Key Vault |
| رسائل الطابور تُعاد محاولتها بلا توقّف | الدالة تطرح استثناءً غير مُعالَج | عالِج الأخطاء المعروفة، وسجّلها، ثم أعد النتيجة |
| البدء البارد يتجاوز 10 ثوانٍ | تهيئة وقت تشغيل Python | استخدم خطة Premium أو اضبط FUNCTIONS_WORKER_PROCESS_COUNT |
الأسئلة الشائعة
كيف أطابق عدد الـ threads في خطة CaptchaAI مع تزامن Azure Functions؟
اجعل الحد الأقصى للمهام المتزامنة مساويًا لعدد الـ threads في خطتك أو أقل. مثلًا خطة ADVANCE ($90/شهريًا) تتيح 50 thread، فلا معنى لتشغيل مئة مثيل Azure متزامن يرسل أكثر مما تسمح به الخطة. اضبط تزامن الطابور عبر host.json ليوافق الرقمين.
ما الفرق بين المُشغّل عبر HTTP والمُشغّل عبر الطابور؟
مُشغّل HTTP متزامن ومناسب للطلب الواحد الذي ينتظر ردًّا فوريًا. مُشغّل الطابور غير متزامن ومناسب للدُفعات وأحمال الذروة، إذ يمتصّ Queue Storage الطلبات ويوزّعها دون فقدان أي مهمة عند الضغط.
كيف أقلّل البدء البارد الذي يبطئ حل CAPTCHA؟
البدء البارد ينتج عن تهيئة وقت تشغيل Python بعد الخمول. للأحمال الثابتة استخدم خطة Premium التي تُبقي المثيلات دافئة، أو اضبط FUNCTIONS_WORKER_PROCESS_COUNT. أما للأحمال المتقطّعة فخطة Consumption تبقى الأوفر رغم زمن الإحماء.
هل تحل CaptchaAI كل أنواع CAPTCHA من داخل Azure Functions؟
تحل CaptchaAI أنواع reCAPTCHA v2/v3، وCloudflare Turnstile وChallenge، وGeeTest v3، وصور OCR والشبكات وBLS، مع CaptchaFox وFriendly Captcha وLemin في مرحلة beta. أما hCaptcha وFunCaptcha (Arkose Labs) فغير مدعومة حاليًا، وGeeTest v4 قيد الإعداد ولم يُتَح بعد.
أين أرصد فشل الحل بعد النشر؟
في Application Insights المرتبط تلقائيًا بتطبيق الدالة. يعرض زمن كل استدعاء ومعدّل الأخطاء وسلاسل التتبّع؛ ضمّن معرّف المهمة في رسائل السجل لتربط كل فشل بمهمته وتحدّد الأنماط المتكرّرة بسرعة.
أدلة ذات صلة
جاهز للانطلاق على Azure؟ افتح حساب CaptchaAI وابدأ اليوم.