المرجع

حل اختبار CAPTCHA من الأساسيات إلى الإنتاج

حلّ اختبار CAPTCHA برمجياً يتلخّص في حلقة من أربع خطوات ثابتة: استخرج معلمات التحدي من الصفحة، أرسلها إلى خدمة الحل، استطلع النتيجة، ثم احقن الرمز في النموذج. يأخذك هذا الدليل من أول عملية حل ناجحة إلى خط أنابيب جاهز للإنتاج باستخدام CaptchaAI وPython، مع التركيز على ما يهمّ فعلاً عند التشغيل الحقيقي: معالجة الأخطاء، ومراقبة الرصيد، والتوسّع تحت الحِمل.


أولاً: كيف تعمل اختبارات CAPTCHA ولماذا تنتشر

تعريف موجز لاختبار CAPTCHA

اختبار CAPTCHA — واختصاره يعني "اختبار تورينج العام الآلي بالكامل للتمييز بين الحاسوب والإنسان" — هو تحدٍّ يُدرَج في الصفحة لمنع الوصول الآلي غير المرغوب مع السماح للمستخدم البشري بالمرور. من وجهة نظر المطوّر، هو حاجز يجب اجتيازه برمجياً قبل إكمال طلب مشروع.

لماذا تعتمد المواقع على هذه الاختبارات

فهم سبب وجود الاختبار يساعدك على توقّع نوعه وسلوكه:

  • منع إنشاء الحسابات آلياً بكميات كبيرة
  • الحدّ من استخراج البيانات وجمعها دون إذن
  • إيقاف الرسائل غير المرغوبة في النماذج والتعليقات
  • تحديد معدل الوصول إلى الـ API

أنواع اختبارات CAPTCHA التي ستقابلها

تنقسم الاختبارات إلى أربع عائلات، لكلٍّ منها أسلوب استخراج وحلّ مختلف:

النوع أمثلة التحدي
نص/Image الحروف المشوهة، والتعابير الرياضية اكتب ما تراه
خانة الاختيار reCAPTCHA v2 انقر فوق خانة الاختيار، وربما حل شبكة الصور
غير مرئية reCAPTCHA v3، Turnstile لا يوجد تفاعل من قبل المستخدم - التسجيل السلوكي
تفاعلية شريحة GeeTest، شبكة BLS اسحب العناصر أو انقر عليها أو رتّبها

يغطّي CaptchaAI عائلات reCAPTCHA (v2 وv3 وEnterprise) وCloudflare Turnstile وChallenge وGeeTest v3 والصور وBLS. في المقابل، لا يدعم حالياً hCaptcha أو FunCaptcha (Arkose Labs)، ودعم GeeTest v4 معلن أنه قادم قريباً وليس متاحاً بعد.


آلية عمل خدمات حل اختبار CAPTCHA

مسار الحل من طرفك إلى الخدمة

الفكرة أنك لا تحلّ التحدي بنفسك، بل تفوّضه إلى الخدمة وتنتظر الرمز الناتج:

Your Code  →  Submit CAPTCHA to API  →  Solving Service  →  Return Token/Text  →  Your Code Injects Result

الخطوات الخمس بالتفصيل

تتكرّر الحلقة نفسها مهما اختلف نوع الاختبار، فتكفي طبقة تجريد واحدة:

  1. استخرج معلمات CAPTCHA من الصفحة المستهدفة (مفتاح الموقع، التحدي، الصورة)
  2. أرسل المعلمات إلى الـ API للحل
  3. استطلع النتيجة دورياً (رمز أو نص)
  4. احقن النتيجة مرة أخرى في الصفحة
  5. أرسل النموذج

إعداد CaptchaAI في مشروعك

تثبيت المكتبات المطلوبة

مكتبة requests وحدها تكفي للبدء:

pip install requests

فئة الحل الأساسية

الفئة التالية تلخّص الحلقة كاملة: submit لإرسال المهمة، وget_result للاستطلاع الدوري، وsolve كواجهة مختصرة، وbalance لقراءة الرصيد. أعِد استخدامها في كل مشروع:

import time
import requests

class CaptchaAI:
    BASE = "https://ocr.captchaai.com"

    def __init__(self, api_key):
        self.api_key = api_key

    def submit(self, params):
        params["key"] = self.api_key
        params["json"] = 1
        resp = requests.post(f"{self.BASE}/in.php", data=params)
        data = resp.json()
        if data["status"] != 1:
            raise Exception(f"Submit failed: {data['request']}")
        return data["request"]

    def get_result(self, task_id, timeout=300, interval=5, initial_wait=10):
        time.sleep(initial_wait)
        deadline = time.time() + timeout
        while time.time() < deadline:
            resp = requests.get(
                f"{self.BASE}/res.php",
                params={
                    "key": self.api_key,
                    "action": "get",
                    "id": task_id,
                    "json": 1,
                },
            ).json()
            if resp["request"] == "CAPCHA_NOT_READY":
                time.sleep(interval)
                continue
            if resp["status"] == 1:
                return resp["request"]
            raise Exception(f"Solve failed: {resp['request']}")
        raise TimeoutError("Solve timed out")

    def solve(self, params, **kwargs):
        task_id = self.submit(params)
        return self.get_result(task_id, **kwargs)

    def balance(self):
        resp = requests.get(
            f"{self.BASE}/res.php",
            params={"key": self.api_key, "action": "getbalance"},
        )
        return float(resp.text)

حل كل نوع من أنواع CAPTCHA عبر API

مع وجود الفئة السابقة، يصبح الفرق بين الأنواع مجرد اختلاف في حقول params، وحقل method هو ما يحدّد نوع المعالجة.

reCAPTCHA v2

يكفي مفتاح الموقع (googlekey) وعنوان الصفحة، دون الحاجة إلى متصفح:

solver = CaptchaAI("YOUR_API_KEY")
token = solver.solve({
    "method": "userrecaptcha",
    "googlekey": "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
    "pageurl": "https://example.com/login",
})

reCAPTCHA v3

النسخة السلوكية تحتاج إلى version وaction، وغالباً إلى مهلة انتظار أولية أطول:

token = solver.solve({
    "method": "userrecaptcha",
    "googlekey": "SITE_KEY",
    "pageurl": "https://example.com",
    "version": "v3",
    "action": "submit",
}, initial_wait=20)

Cloudflare Turnstile

token = solver.solve({
    "method": "turnstile",
    "sitekey": "0x4AAAAAAAC3a...",
    "pageurl": "https://example.com",
})

GeeTest v3

يتطلّب هذا النوع قيمتَي gt وchallenge اللتين تُستخرجان من الصفحة قبل الإرسال:

result = solver.solve({
    "method": "geetest",
    "gt": "GT_VALUE",
    "challenge": "CHALLENGE_VALUE",
    "pageurl": "https://example.com",
})

الصور والتعرّف الضوئي (Image/OCR)

هنا ترسل بيانات الصورة مُرمّزة بصيغة base64 وتصف خصائص النص المتوقّع:

import base64

with open("captcha.png", "rb") as f:
    img_b64 = base64.b64encode(f.read()).decode()

text = solver.solve({
    "method": "base64",
    "body": img_b64,
    "numeric": "1",
    "minLen": "4",
    "maxLen": "6",
})

استخراج معلمات التحدي من الصفحة

في الواقع عليك استخراج مفتاح الموقع من الصفحة أولاً، وغالباً باستخدام Selenium.

مفتاح موقع reCAPTCHA

عادةً يكون المفتاح في سمة data-sitekey، وإن غاب فيمكن قراءته من رابط الـ iframe:

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://example.com/login")

# Method 1: From div attribute
sitekey = driver.find_element(
    By.CSS_SELECTOR, "[data-sitekey]"
).get_attribute("data-sitekey")

# Method 2: From iframe URL
import re
iframe = driver.find_element(By.CSS_SELECTOR, "iframe[src*='recaptcha']")
src = iframe.get_attribute("src")
sitekey = re.search(r"k=([^&]+)", src).group(1)

مفتاح موقع Turnstile

sitekey = driver.find_element(
    By.CSS_SELECTOR, "[data-sitekey], .cf-turnstile"
).get_attribute("data-sitekey")

معلمات GeeTest

نقرأ القيم مباشرة عبر JavaScript لأنها قد لا تظهر في سمات مباشرة:

import json

gt_data = driver.execute_script("""
    return {
        gt: document.querySelector('[data-gt]')?.getAttribute('data-gt'),
        challenge: document.querySelector('[data-challenge]')?.getAttribute('data-challenge')
    };
""")

حقن الحل داخل الصفحة

بعد استلام الرمز، أدخِله في الحقل المخفي الذي يتوقّعه الموقع ثم أكمِل الإرسال.

الحقن المعتمد على الرمز (reCAPTCHA وTurnstile)

driver.execute_script(f"""
    document.querySelector('[name="g-recaptcha-response"]').value = '{token}';
    document.querySelector('[name="cf-turnstile-response"]').value = '{token}';
""")

التعامل مع دوال رد النداء (Callbacks)

بعض عمليات التكامل لا تكتفي بتعبئة الحقل، بل تنتظر استدعاء دالة رد النداء لتفعيل الزر:

driver.execute_script(f"""
    if (typeof ___grecaptcha_cfg !== 'undefined') {{
        Object.keys(___grecaptcha_cfg.clients).forEach(function(key) {{
            var client = ___grecaptcha_cfg.clients[key];
            // Find and call the callback
        }});
    }}
""")

معالجة الأخطاء بثبات

الفرق بين سكربت تجريبي وخدمة إنتاجية هو غالباً في تعامله مع الفشل. ميّز بين الأخطاء التي تستحق إعادة المحاولة وتلك التي لا فائدة من تكرارها.

منطق إعادة المحاولة

خطأ UNSOLVABLE قد يُحلّ بمحاولة جديدة، أما ZERO_BALANCE فيعني نفاد الرصيد ولا معنى لإعادة المحاولة معه:

def solve_with_retry(solver, params, max_retries=3):
    for attempt in range(max_retries):
        try:
            return solver.solve(params)
        except Exception as e:
            error = str(e)
            if "ZERO_BALANCE" in error:
                raise  # Don't retry — need funds
            if "UNSOLVABLE" in error:
                print(f"Attempt {attempt + 1} failed, retrying...")
                continue
            raise
    raise Exception(f"Failed after {max_retries} attempts")

مراقبة الرصيد قبل الإرسال

فحص الرصيد قبل بدء دُفعة كبيرة يجنّبك سلسلة إخفاقات مكلفة في منتصف التشغيل:

def check_balance_before_solve(solver, min_balance=0.10):
    balance = solver.balance()
    if balance < min_balance:
        raise Exception(f"Low balance: ${balance:.2f}")
    return balance

أنماط جاهزة للإنتاج

عند الحجم الكبير يصبح تنظيم الاتصالات والتزامن أهمّ من سرعة الحل الفردي.

تجميع الاتصالات (Connection Pooling)

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

def create_session():
    session = requests.Session()
    retry = Retry(total=3, backoff_factor=1, status_forcelist=[500, 502, 503])
    adapter = HTTPAdapter(max_retries=retry, pool_connections=10, pool_maxsize=20)
    session.mount("https://", adapter)
    return session

الحل المتزامن عبر عدة مسارات

عدد العمال المتزامنين هنا يجب أن يتوافق مع عدد مسارات المعالجة (Threads) في خطتك؛ فتجاوزه لن يزيد السرعة:

from concurrent.futures import ThreadPoolExecutor, as_completed

def solve_batch(solver, captcha_list, max_workers=5):
    results = {}
    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        futures = {
            executor.submit(solver.solve, params): url
            for url, params in captcha_list
        }
        for future in as_completed(futures):
            url = futures[future]
            try:
                results[url] = future.result()
            except Exception as e:
                results[url] = f"ERROR: {e}"
    return results

تحديد معدل الطلبات

import threading

class RateLimiter:
    def __init__(self, max_per_second=10):
        self.interval = 1.0 / max_per_second
        self.lock = threading.Lock()
        self.last_call = 0

    def wait(self):
        with self.lock:
            now = time.time()
            wait_time = self.last_call + self.interval - now
            if wait_time > 0:
                time.sleep(wait_time)
            self.last_call = time.time()

مراقبة التشغيل وقياس الأداء

ما لا تقيسه لا تحسّنه. سجّل كل عملية وتتبّع معدل النجاح والزمن لتكتشف أي تراجع مبكراً.

التسجيل (Logging)

import logging

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
logger = logging.getLogger("captcha")

def solve_logged(solver, params):
    start = time.time()
    logger.info(f"Submitting {params.get('method')} CAPTCHA")
    try:
        result = solver.solve(params)
        elapsed = time.time() - start
        logger.info(f"Solved in {elapsed:.1f}s")
        return result
    except Exception as e:
        elapsed = time.time() - start
        logger.error(f"Failed after {elapsed:.1f}s: {e}")
        raise

تتبّع المقاييس

class SolveMetrics:
    def __init__(self):
        self.total = 0
        self.success = 0
        self.failures = 0
        self.total_time = 0.0

    def record(self, success, elapsed):
        self.total += 1
        self.total_time += elapsed
        if success:
            self.success += 1
        else:
            self.failures += 1

    def summary(self):
        rate = (self.success / self.total * 100) if self.total else 0
        avg = (self.total_time / self.total) if self.total else 0
        return {
            "total": self.total,
            "success_rate": f"{rate:.1f}%",
            "avg_time": f"{avg:.1f}s",
        }

مثال تطبيقي: أتمتة اختبار الجودة لبوابة حجوزات

تخيّل فريقاً في شركة سفر أو تجارة إلكترونية عربية يريد اختبار مسار تسجيل الدخول وإتمام الشراء آلياً كل ليلة. الصفحات محمية بـ reCAPTCHA v2 على تسجيل الدخول وبـ Turnstile على خطوة الدفع. باستخدام الفئة أعلاه، يمرّ كل سيناريو بالحلقة نفسها: استخراج مفتاح الموقع، الإرسال بالطريقة المناسبة، حقن الرمز، ثم متابعة التدفق.

السؤال العملي هو حجم التزامن. يعتمد تسعير CaptchaAI على عدد مسارات المعالجة المتزامنة (Threads) مع حلول غير محدودة لكل مسار شهرياً، لا على عدد العمليات. فتكفي خطة BASIC ($15 شهرياً، 5 مسارات) لاختبارات ليلية صغيرة، بينما تناسب ADVANCE ($90 شهرياً، 50 مساراً) خطوط الأنابيب الأكبر. الأسعار بالدولار الأمريكي والتكلفة ثابتة ومتوقّعة.


قائمة تحقّق قبل الإطلاق

خطوة المهمة
1 ثبّت requests واحصل على مفتاح API
2 حدّد نوع CAPTCHA في الصفحة المستهدفة
3 استخرج مفتاح الموقع والمعلمات
4 أرسل إلى CaptchaAI بالطريقة الصحيحة
5 استطلع النتيجة بالتوقيت المناسب
6 احقن الرمز وأرسل النموذج
7 أضف منطق إعادة المحاولة للإنتاج
8 راقب معدل النجاح والتكاليف
9 وسّع عبر تجميع الاتصالات والتزامن

الأسئلة الشائعة

هل استخدام خدمة حل CAPTCHA قانوني ومسموح؟

يعتمد الأمر على الغرض. حلّ الاختبارات لأغراض مشروعة مثل اختبار الجودة وأتمتة سير العمل واختبار التكامل على مواقعك أو مواقع تملك إذناً بالوصول إليها هو استخدام معتمد. تجنّب أي استخدام يتعارض مع شروط الموقع أو يمسّ بيانات لا تملكها.

هل يدعم CaptchaAI جميع أنواع اختبارات CAPTCHA؟

لا. يدعم CaptchaAI عائلات reCAPTCHA وCloudflare Turnstile وChallenge وGeeTest v3 والصور وBLS، لكنه لا يدعم حالياً hCaptcha أو FunCaptcha (Arkose Labs)، بينما دعم GeeTest v4 معلن أنه قادم قريباً. تحقّق من النوع المستخدم في صفحتك قبل بناء التكامل.

كم عدد مسارات المعالجة (Threads) التي أحتاجها لمشروعي؟

يتحدّد العدد بذروة التزامن لديك، لا بإجمالي الحلول. إذا كنت تحلّ خمسة اختبارات في آنٍ واحد كحد أقصى فخطة بخمسة مسارات كافية. وإذا بدأت الطلبات تصطف في قائمة الانتظار، فتلك إشارة إلى الحاجة لمسارات أكثر.

لماذا تظهر أخطاء مثل UNSOLVABLE وكيف أعالجها؟

يعني UNSOLVABLE أن الخدمة لم تحلّ هذه المحاولة تحديداً، وغالباً ما تنجح إعادة المحاولة. تأكّد من صحة مفتاح الموقع وعنوان الصفحة، واعتمد منطق إعادة محاولة محدوداً مع تمييز الأخطاء غير القابلة للإصلاح مثل ZERO_BALANCE.

كيف أتفادى انتهاء صلاحية الرمز قبل استخدامه؟

احلّ الاختبار قبل الحاجة إليه مباشرةً. تنتهي صلاحية رموز reCAPTCHA خلال 120 ثانية تقريباً، وTurnstile خلال 300 ثانية. لا تحلّ مسبقاً بكميات كبيرة، بل اربط توقيت الحل بلحظة الإرسال الفعلي.


أدلة ذات صلة


من الأساسيات إلى الإنتاج في دليل واحد —ابدأ بـ CaptchaAI.

التعليقات غير مفعّلة لهذا المقال.