نادراً ما يظهر تدهور حل CAPTCHA على شكل عطل واضح؛ يظهر بعد أسبوع على هيئة دفعة بيانات ناقصة أو طلب متوقف في منتصف سير العمل. الجواب العملي: سجّل كل محاولة كنقطة قياس تحمل الوقت والنوع والحالة وزمن الاستجابة، ثم اقرأ الاتجاه لا القيمة اللحظية.
يقوم المسار على ثلاث طبقات: قياس داخل سكربت الحل، وتخزين في قاعدة بيانات زمنية، واستعلامات تحوّل الأرقام إلى قرار — بأمثلة Python وJavaScript على Prometheus وInfluxDB.
المقاييس السبعة التي تستحق التخزين
| المقياس | نوع القياس | القرار الذي يدعمه |
|---|---|---|
| معدل الحل الناجح (%) | Gauge | رصد تغيّر جودة الحل بمرور الوقت |
| زمن الحل (بالمللي ثانية) | Histogram | كشف التباطؤ ومراجعة مهلة الطلب |
| معدل الأخطاء حسب رمز الخطأ | Counter | تمييز الأنماط الناشئة مبكراً |
| التكلفة التقديرية للحل الواحد | Gauge | متابعة الميزانية ورصد القفزات |
| عمق قائمة الانتظار | Gauge | تخطيط السعة وعدد الـ Threads |
| الرموز المنتهية قبل الاستخدام | Counter | ضبط مدة صلاحية الرمز |
| رصيد الحساب | Gauge | إعادة الشحن قبل التوقف |
الصف قبل الأخير هو الأهم عملياً: انتهاء صلاحية الرمز قبل إرساله يبدو في السجلات كفشل من طرف الخدمة، بينما هو بطء في الكود الذي يستهلكه.
خط أساس الأداء قبل أي رسم بياني
أرقام السرعة المعلنة لدى CaptchaAI حدود قصوى لا متوسطات: أقل من 10 ثوانٍ لـ Cloudflare Turnstile، وأقل من 12 ثانية لـ GeeTest v3، وأقل من 60 ثانية لـ reCAPTCHA v2، مع معدل نجاح مرتفع على الأنواع المدعومة. إذا بقي المئين الخامس والتسعين لديك فوق هذا السقف، فالسبب غالباً شبكتك أو فترة الاستطلاع الدوري.
وعند تفسير التكلفة تذكّر أن الفوترة تتم على عدد الـ Threads المتزامنة، وأن عمليات الحل داخل كل Thread غير محدودة: BASIC بـ 15 دولاراً و5 Threads، وADVANCE بـ 90 دولاراً و50 Thread، وENTERPRISE بـ 300 دولار و200 Thread.
قياس الأداء من داخل سكربت الحل مع Prometheus
المثال التالي يستخدم Push Gateway لأن سكربتات الأتمتة قصيرة العمر ولا تبقى حيّة لتُستطلع منها المقاييس.
وسم دالة الحل بالعدادات والمدرّجات
import os
import time
import requests
from prometheus_client import CollectorRegistry, Counter, Histogram, Gauge, push_to_gateway
registry = CollectorRegistry()
SOLVE_TOTAL = Counter(
"captcha_solve_total", "Total CAPTCHA solve attempts",
["type", "status"], registry=registry
)
SOLVE_LATENCY = Histogram(
"captcha_solve_latency_seconds", "CAPTCHA solve latency",
["type"], buckets=[5, 10, 15, 20, 30, 45, 60, 90, 120],
registry=registry
)
SOLVE_COST = Counter(
"captcha_solve_cost_dollars", "Total cost of CAPTCHA solves",
["type"], registry=registry
)
API_BALANCE = Gauge(
"captcha_api_balance_dollars", "CaptchaAI account balance",
registry=registry
)
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
PUSHGATEWAY = os.environ.get("PUSHGATEWAY_URL", "localhost:9091")
def solve_with_metrics(sitekey, pageurl, captcha_type="recaptcha_v2"):
start = time.time()
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": pageurl,
"json": 1
})
data = resp.json()
if data.get("status") != 1:
SOLVE_TOTAL.labels(type=captcha_type, status="submit_error").inc()
push_metrics()
return {"error": data.get("request")}
captcha_id = data["request"]
for _ in range(60):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get",
"id": captcha_id, "json": 1
}).json()
if result.get("status") == 1:
elapsed = time.time() - start
SOLVE_TOTAL.labels(type=captcha_type, status="solved").inc()
SOLVE_LATENCY.labels(type=captcha_type).observe(elapsed)
SOLVE_COST.labels(type=captcha_type).inc(0.00299)
push_metrics()
return {"solution": result["request"]}
if result.get("request") != "CAPCHA_NOT_READY":
SOLVE_TOTAL.labels(type=captcha_type, status="error").inc()
push_metrics()
return {"error": result.get("request")}
SOLVE_TOTAL.labels(type=captcha_type, status="timeout").inc()
push_metrics()
return {"error": "TIMEOUT"}
def push_metrics():
try:
push_to_gateway(PUSHGATEWAY, job="captcha_solver", registry=registry)
except Exception:
pass # Don't fail solving because metrics push failed
def update_balance():
resp = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "getbalance"
})
try:
balance = float(resp.text)
API_BALANCE.set(balance)
push_metrics()
except ValueError:
pass
لاحظ أن دفع المقاييس محاط بـ try/except صامت: لا يجوز أن يفشل الحل لأن نقطة التجميع غير متاحة.
استعلامات PromQL الأربعة التي ستستخدمها يومياً
# Success rate over last hour
rate(captcha_solve_total{status="solved"}[1h])
/ rate(captcha_solve_total[1h]) * 100
# P95 solve latency
histogram_quantile(0.95, rate(captcha_solve_latency_seconds_bucket[1h]))
# Error rate by type
rate(captcha_solve_total{status="error"}[1h])
# Hourly cost
increase(captcha_solve_cost_dollars_total[1h])
ابنِ تنبيهاتك على المئين الخامس والتسعين لا على المتوسط الحسابي، لأن المتوسط يخفي الذيل البطيء الذي يعطّل سير العمل فعلياً.
تخزين طويل المدى مع InfluxDB
يصلح Prometheus للمراقبة اللحظية، لكن التحليل الشهري يحتاج تخزيناً أطول وبنية وسوم أغنى: نقطة قياس لكل محاولة مع وسم للنوع والحالة ورمز الخطأ.
كتابة نقطة قياس لكل محاولة حل
from influxdb_client import InfluxDBClient, Point
from influxdb_client.client.write_api import SYNCHRONOUS
INFLUX_URL = os.environ.get("INFLUX_URL", "http://localhost:8086")
INFLUX_TOKEN = os.environ.get("INFLUX_TOKEN", "")
INFLUX_ORG = os.environ.get("INFLUX_ORG", "captcha")
INFLUX_BUCKET = os.environ.get("INFLUX_BUCKET", "captcha_metrics")
influx_client = InfluxDBClient(url=INFLUX_URL, token=INFLUX_TOKEN, org=INFLUX_ORG)
write_api = influx_client.write_api(write_options=SYNCHRONOUS)
def record_solve_metric(captcha_type, status, elapsed_ms, cost=0.0, error=None):
point = (
Point("captcha_solve")
.tag("type", captcha_type)
.tag("status", status)
.field("elapsed_ms", elapsed_ms)
.field("cost", cost)
.field("success", 1 if status == "solved" else 0)
)
if error:
point = point.tag("error_code", error)
write_api.write(bucket=INFLUX_BUCKET, record=point)
def record_balance(balance):
point = Point("captcha_balance").field("balance", balance)
write_api.write(bucket=INFLUX_BUCKET, record=point)
اجعل رمز الخطأ وسماً لا حقلاً. ولا تضف وسوماً عالية التنوع مثل معرّف المهمة أو عنوان الصفحة الكامل — هذه أسرع طريقة لإبطاء الاستعلامات.
استعلامات Flux لاتجاهات اليوم والأسبوع
// Success rate over last 24 hours (1-hour windows)
from(bucket: "captcha_metrics")
|> range(start: -24h)
|> filter(fn: (r) => r._measurement == "captcha_solve" and r._field == "success")
|> aggregateWindow(every: 1h, fn: mean)
|> map(fn: (r) => ({r with _value: r._value * 100.0}))
|> yield(name: "success_rate")
// Average solve time by type
from(bucket: "captcha_metrics")
|> range(start: -24h)
|> filter(fn: (r) => r._measurement == "captcha_solve" and r._field == "elapsed_ms" and r.status == "solved")
|> group(columns: ["type"])
|> aggregateWindow(every: 1h, fn: mean)
|> yield(name: "avg_latency")
// Cumulative cost
from(bucket: "captcha_metrics")
|> range(start: -24h)
|> filter(fn: (r) => r._measurement == "captcha_solve" and r._field == "cost")
|> cumulativeSum()
|> yield(name: "cumulative_cost")
النافذة aggregateWindow(every: 1h) هي ما يحوّل الضجيج إلى اتجاه: اقرأها على 24 ساعة يومياً، وعلى 7 أيام قبل أي قرار يتعلق بعدد الـ Threads.
نفس القياس من Node.js
مع Puppeteer أو Playwright ينتقل المنطق ذاته إلى JavaScript بفارق واحد: الخدمة طويلة العمر، فتُعرَض المقاييس عبر /metrics بدل دفعها إلى Push Gateway.
const client = require("prom-client");
const axios = require("axios");
const register = new client.Registry();
const API_KEY = process.env.CAPTCHAAI_API_KEY;
const solveTotal = new client.Counter({
name: "captcha_solve_total",
help: "Total CAPTCHA solve attempts",
labelNames: ["type", "status"],
registers: [register],
});
const solveLatency = new client.Histogram({
name: "captcha_solve_latency_seconds",
help: "CAPTCHA solve latency",
labelNames: ["type"],
buckets: [5, 10, 15, 20, 30, 45, 60, 90, 120],
registers: [register],
});
async function solveWithMetrics(sitekey, pageurl, type = "recaptcha_v2") {
const start = Date.now();
const submit = await axios.post("https://ocr.captchaai.com/in.php", null, {
params: { key: API_KEY, method: "userrecaptcha", googlekey: sitekey, pageurl, json: 1 },
});
if (submit.data.status !== 1) {
solveTotal.inc({ type, status: "submit_error" });
return { error: submit.data.request };
}
const captchaId = submit.data.request;
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 5000));
const poll = await axios.get("https://ocr.captchaai.com/res.php", {
params: { key: API_KEY, action: "get", id: captchaId, json: 1 },
});
if (poll.data.status === 1) {
const elapsed = (Date.now() - start) / 1000;
solveTotal.inc({ type, status: "solved" });
solveLatency.observe({ type }, elapsed);
return { solution: poll.data.request };
}
if (poll.data.request !== "CAPCHA_NOT_READY") {
solveTotal.inc({ type, status: "error" });
return { error: poll.data.request };
}
}
solveTotal.inc({ type, status: "timeout" });
return { error: "TIMEOUT" };
}
// Expose metrics endpoint
const express = require("express");
const app = express();
app.get("/metrics", async (req, res) => {
res.set("Content-Type", register.contentType);
res.end(await register.metrics());
});
app.listen(9090);
أي قاعدة بيانات زمنية تناسب فريقك
| المعيار | Prometheus | InfluxDB | TimescaleDB |
|---|---|---|---|
| الاستخدام الأنسب | المراقبة التشغيلية اللحظية | مخزن مقاييس مستقل عالي التنوع | تحليلات مبنية على SQL |
| لغة الاستعلام | PromQL | Flux | SQL |
| الاحتفاظ بالبيانات | يُضبط في الإعدادات | سياسات احتفاظ لكل bucket | إدارة الاحتفاظ عبر PostgreSQL |
| التكامل مع Grafana | مدمج | مدمج | مدمج |
| منحنى التعلم | منخفض | متوسط | منخفض لمن يعرف SQL |
| التشغيل الذاتي | نعم | نعم | نعم — كامتداد لـ PostgreSQL |
القاعدة العملية: إن كان Prometheus يراقب خوادمك أصلاً فأضف مقاييس CAPTCHA إليه ولا تُدخل مكوناً جديداً.
سيناريو تشغيلي من السوق العربي
فريق بيانات في القاهرة يشغّل مهمة ليلية لجمع أسعار من بوابات إقليمية عبر reCAPTCHA v2 وTurnstile. بعد أسبوعين بدأت الدفعة تتأخر ساعة كاملة دون أي رسالة خطأ.
ما كشفته السلاسل الزمنية: معدل النجاح لم يتغير، لكن المئين الخامس والتسعين لزمن الحل ارتفع بين 1:00 و3:00 صباحاً بتوقيت القاهرة. السبب أن المهمة كانت تفتح طلبات متزامنة تفوق عدد الـ Threads في الخطة، فانتظرت الزائدة في قائمة الانتظار. الحل: توزيع الدفعة على نافذتين، ثم الانتقال من BASIC بـ 15 دولاراً و5 Threads إلى ADVANCE بـ 90 دولاراً و50 Thread عندما تضاعف عدد البوابات.
تفصيلتان محليتان عند بناء اللوحة:
- المنطقة الزمنية: خزّن الطوابع بتوقيت UTC واعرضها بـ
Africa/CairoأوAsia/Riyadh؛ مقارنة نافذة ليلية بتوقيت خاطئ تعطي استنتاجاً معكوساً. - أسبوع العمل: المقارنة «أسبوع مقابل أسبوع» يجب أن تحاذي أيام الأحد إلى الخميس حيث ينطبق ذلك، لا نموذج الأسبوع الافتراضي في القوالب الجاهزة.
الاحتفاظ بالبيانات وضبط التنبيهات
احتفظ بالبيانات عالية الدقة 7 أيام، وبتجميع بالساعة 90 يوماً، وبملخص يومي بلا حد زمني. واربط التنبيهات بمتوسطات متحركة لا بقيم لحظية:
- انخفاض معدل النجاح خلال ساعة عن خط الأساس بأكثر من 10 نقاط مئوية.
- بقاء المئين الخامس والتسعين فوق السقف المعلن للنوع المستخدم 15 دقيقة متصلة.
- رصيد يكفي أقل من 48 ساعة من الاستهلاك الحالي.
أخطاء شائعة في القياس وكيفية معالجتها
| المشكلة | السبب المرجّح | الإجراء |
|---|---|---|
| فجوات في الرسم البياني رغم استمرار العمل | Push Gateway لا يستقبل الدفعات | تحقق من الشبكة بين السكربت ونقطة التجميع وسجّل فشل الدفع |
| المئينات تبدو غير منطقية | حدود المدرّج لا تناسب الحمل | استخدم حدوداً مناسبة مثل [5, 10, 15, 20, 30, 45, 60, 90, 120] |
| أرقام التكلفة لا تطابق الفاتورة | القيمة تقديرية لكل حل بينما الفوترة على الـ Threads | احسبها شهرياً بقسمة الاشتراك على عدد العمليات الناجحة |
| الاستعلامات تتباطأ مع الوقت | تنوّع مفرط في الوسوم | اقصر الوسوم على النوع والحالة ورمز الخطأ |
| الرمز صالح لكن الموقع يرفضه | تأخر استهلاكه أو اختلاف سياق الجلسة | قِس الزمن بين استلام الرمز وإرساله واستخدمه داخل الجلسة نفسها |
الأسئلة الشائعة
هل يبطئ جمع المقاييس عملية الحل نفسها؟
لا إذا فصلت الدفع عن المسار الحرج. زيادة العدّادات تتم داخل الذاكرة، والتكلفة الوحيدة هي طلب الدفع إلى نقطة التجميع — اجعله غير معطِّل.
كيف أربط مقياس التكلفة بخطة CaptchaAI وهي مفوترة على الـ Threads؟
اعتبر التكلفة لكل حل رقماً مشتقاً لا سعراً حقيقياً: اقسم قيمة الاشتراك الشهري — BASIC بـ 15 دولاراً أو ADVANCE بـ 90 دولاراً — على عدد العمليات الناجحة. الرقم ينخفض كلما زاد الاستخدام لأن عمليات الحل ضمن الاشتراك غير محدودة.
ما الفرق بين زمن الحل المقاس من تطبيقي وزمن الخدمة؟
قياسك يشمل زمن الشبكة ودورة الاستطلاع؛ باستطلاع كل 5 ثوانٍ قد تسجّل حتى 5 ثوانٍ زائدة بعد جاهزية النتيجة. سجّل زمن الإرسال وزمن أول استجابة ناجحة كحقلين منفصلين.
هل أحتاج إلى بنية مراقبة كاملة للبدء؟
لا. ابدأ بجدول واحد يسجّل الوقت والنوع والحالة وزمن الاستجابة. طبقة Prometheus وGrafana تستحق العناء عند بلوغ بضعة آلاف عملية حل شهرياً.
الخطوات التالية
ابدأ بوسم دالة حل واحدة، وشغّلها يوماً كاملاً، ثم اقرأ الاتجاه:
- ابدأ من هنا: أول عملية حل عبر CaptchaAI في خمس دقائق
- حل reCAPTCHA v2 عبر الـ API خطوة بخطوة
- حل Cloudflare Turnstile عبر الـ API
- حل GeeTest v3 عبر الـ API