الدروس التطبيقية

التخزين المؤقت لرموز CAPTCHA لإعادة استخدامها

حل اختبار CAPTCHA يكلف الوقت والمال. إذا كان من الممكن إعادة استخدام الرمز المميز نفسه ضمن نافذة الصلاحية الخاصة به، فإن التخزين المؤقت يلغي استدعاءات واجهة برمجة التطبيقات المتكررة. يغطي هذا الدليل الرموز المميزة القابلة للتخزين المؤقت، ومدة استمرارها، وكيفية تنفيذ التخزين المؤقت بأمان.


عمر الرمز المميز حسب نوع CAPTCHA

نوع التحقق عمر الرمز قابلة للتخزين المؤقت؟ ملاحظات
reCAPTCHA v2 ~120 ثانية محدودة الاستخدام لمرة واحدة في معظم المواقع
reCAPTCHA v3 ~120 ثانية محدودة قد تختلف النتيجة لكل طلب
reCAPTCHA المؤسسة ~120 ثانية لا عمل محدد، استخدام واحد
Cloudflare Turnstile ~300 ثانية نعم، داخل النافذة رمز قابل لإعادة الاستخدام حتى انتهاء الصلاحية
Cloudflare Challenge cf_clearance ~ 15-30 دقيقة نعم ملف تعريف الارتباط قابل لإعادة الاستخدام للجلسة
** التعرف الضوئي على الحروف للصورة ** N/A (نتيجة نصية) نعم النتيجة لا تنتهي أبدا
GeeTest v3 ~60 ثانية لا خاص بالتحدي

الرؤية الرئيسية: Cloudflare Challenge (cf_clearance) وImage OCR هما الأكثر قابلية للتخزين المؤقت. تحتوي رموز reCAPTCHA المميزة على نوافذ قصيرة وغالبًا ما تكون للاستخدام مرة واحدة.


عندما يعمل التخزين المؤقت

يكون التخزين المؤقت فعالاً عندما:

  1. نفس الصفحة، طلبات متعددة - على سبيل المثال، إرسال نفس النموذج عدة مرات
  2. Cloudflare cf_clearance — حل واحد يفتح الجلسة بأكملها
  3. التعرف الضوئي على الحروف بكميات كبيرة - تظهر الصورة نفسها بشكل متكرر (على سبيل المثال، اختبار CAPTCHA الثابت)
  4. الحل المسبق - حل الرموز المميزة قبل الحاجة إليها

التخزين المؤقت لا يعمل عندما:

  • يقوم الموقع بالتحقق من صحة كل رمز مميز مرة واحدة فقط
  • يرتبط الرمز المميز بإجراء أو جلسة محددة
  • لقد انتهت صلاحية الرمز المميز بالفعل

بايثون - ذاكرة التخزين المؤقت في الذاكرة

import time
import hashlib
from typing import Optional
import requests

SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"


class TokenCache:
    def __init__(self):
        self.cache = {}

    def _key(self, method: str, params: dict) -> str:
        # Cache key from method + stable params
        stable = {k: v for k, v in sorted(params.items())
                  if k not in ("key", "json")}
        raw = f"{method}:{stable}"
        return hashlib.sha256(raw.encode()).hexdigest()[:16]

    def get(self, method: str, params: dict) -> Optional[str]:
        key = self._key(method, params)
        entry = self.cache.get(key)
        if entry and entry["expires_at"] > time.time():
            print(f"Cache HIT: {key}")
            return entry["token"]
        if entry:
            del self.cache[key]
        return None

    def set(self, method: str, params: dict, token: str, ttl: int):
        key = self._key(method, params)
        self.cache[key] = {
            "token": token,
            "expires_at": time.time() + ttl,
        }
        print(f"Cached: {key} (TTL: {ttl}s)")

    def invalidate(self, method: str, params: dict):
        key = self._key(method, params)
        self.cache.pop(key, None)

    def cleanup(self):
        now = time.time()
        expired = [k for k, v in self.cache.items() if v["expires_at"] <= now]
        for k in expired:
            del self.cache[k]


# TTL per CAPTCHA type
TTL_MAP = {
    "userrecaptcha": 100,       # 120s lifetime, 20s safety margin
    "turnstile": 240,           # 300s lifetime, 60s margin
    "cloudflare_challenge": 900,# 15min lifetime, 5min margin
    "base64": 86400,            # OCR result never expires — cache 24h
}


class CachedSolver:
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.cache = TokenCache()

    def solve(self, method: str, params: dict) -> str:
        # Check cache first
        cached = self.cache.get(method, params)
        if cached:
            return cached

        # Solve via API
        token = self._api_solve(method, params)
        ttl = TTL_MAP.get(method, 60)
        self.cache.set(method, params, token, ttl)
        return token

    def _api_solve(self, method: str, params: dict) -> str:
        data = {
            "key": self.api_key,
            "method": method,
            "json": 1,
            **params
        }

        resp = requests.post(SUBMIT_URL, data=data, timeout=15)
        result = resp.json()

        if result.get("status") != 1:
            raise Exception(result.get("error_text", result.get("request")))

        task_id = result["request"]
        return self._poll(task_id)

    def _poll(self, task_id: str, max_wait: int = 120) -> str:
        elapsed = 0
        while elapsed < max_wait:
            time.sleep(5)
            elapsed += 5

            resp = requests.get(RESULT_URL, params={
                "key": self.api_key,
                "action": "get",
                "id": task_id,
                "json": 1
            }, timeout=10)
            result = resp.json()

            if result.get("status") == 1:
                return result["request"]
            if result.get("request") == "CAPCHA_NOT_READY":
                continue

            raise Exception(result.get("error_text", result.get("request")))

        raise Exception(f"Timeout: {task_id}")


# Usage
solver = CachedSolver(api_key="YOUR_API_KEY")

# First call — hits API
token1 = solver.solve("turnstile", {
    "sitekey": "0x4AAAA-SITEKEY",
    "pageurl": "https://example.com"
})
print(f"Token 1: {token1[:40]}...")

# Second call within TTL — cache hit, no API call
token2 = solver.solve("turnstile", {
    "sitekey": "0x4AAAA-SITEKEY",
    "pageurl": "https://example.com"
})
print(f"Token 2: {token2[:40]}...")
print(f"Same token: {token1 == token2}")  # True

Node.js - ذاكرة التخزين المؤقت في الذاكرة

const axios = require("axios");
const crypto = require("crypto");

const SUBMIT_URL = "https://ocr.captchaai.com/in.php";
const RESULT_URL = "https://ocr.captchaai.com/res.php";

const TTL_MAP = {
  userrecaptcha: 100,
  turnstile: 240,
  cloudflare_challenge: 900,
  base64: 86400,
};

class TokenCache {
  constructor() {
    this.cache = new Map();
  }

  _key(method, params) {
    const stable = Object.entries(params)
      .filter(([k]) => k !== "key" && k !== "json")
      .sort(([a], [b]) => a.localeCompare(b))
      .map(([k, v]) => `${k}=${v}`)
      .join("&");
    return crypto.createHash("sha256").update(`${method}:${stable}`).digest("hex").slice(0, 16);
  }

  get(method, params) {
    const key = this._key(method, params);
    const entry = this.cache.get(key);
    if (entry && entry.expiresAt > Date.now()) {
      console.log(`Cache HIT: ${key}`);
      return entry.token;
    }
    if (entry) this.cache.delete(key);
    return null;
  }

  set(method, params, token, ttlMs) {
    const key = this._key(method, params);
    this.cache.set(key, { token, expiresAt: Date.now() + ttlMs });
    console.log(`Cached: ${key} (TTL: ${ttlMs / 1000}s)`);
  }
}

class CachedSolver {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.cache = new TokenCache();
  }

  async solve(method, params) {
    const cached = this.cache.get(method, params);
    if (cached) return cached;

    const token = await this._apiSolve(method, params);
    const ttl = (TTL_MAP[method] || 60) * 1000;
    this.cache.set(method, params, token, ttl);
    return token;
  }

  async _apiSolve(method, params) {
    const resp = await axios.post(SUBMIT_URL, null, {
      params: { key: this.apiKey, method, json: 1, ...params },
      timeout: 15000,
    });

    if (resp.data.status !== 1) {
      throw new Error(resp.data.error_text || resp.data.request);
    }

    return this._poll(resp.data.request);
  }

  async _poll(taskId, maxWait = 120000) {
    let elapsed = 0;
    while (elapsed < maxWait) {
      await new Promise((r) => setTimeout(r, 5000));
      elapsed += 5000;

      const resp = await axios.get(RESULT_URL, {
        params: { key: this.apiKey, action: "get", id: taskId, json: 1 },
        timeout: 10000,
      });

      if (resp.data.status === 1) return resp.data.request;
      if (resp.data.request === "CAPCHA_NOT_READY") continue;

      throw new Error(resp.data.error_text || resp.data.request);
    }
    throw new Error("Timeout");
  }
}

// Usage
(async () => {
  const solver = new CachedSolver("YOUR_API_KEY");

  const token1 = await solver.solve("turnstile", {
    sitekey: "0x4AAAA-SITEKEY",
    pageurl: "https://example.com",
  });
  console.log(`Token 1: ${token1.slice(0, 40)}...`);

  const token2 = await solver.solve("turnstile", {
    sitekey: "0x4AAAA-SITEKEY",
    pageurl: "https://example.com",
  });
  console.log(`Token 2: ${token2.slice(0, 40)}...`);
  console.log(`Same token: ${token1 === token2}`);
})();

Redis ذاكرة التخزين المؤقت للأنظمة الموزعة

بالنسبة للإعدادات متعددة العمال، استخدم Redis بدلاً من ذاكرة التخزين المؤقت في الذاكرة:

import redis
import json

r = redis.Redis(host="localhost", port=6379, db=0)

def cache_token(method, params, token, ttl):
    key = f"captcha:{method}:{hash(frozenset(params.items()))}"
    r.setex(key, ttl, token)

def get_cached_token(method, params):
    key = f"captcha:{method}:{hash(frozenset(params.items()))}"
    return r.get(key)

يعالج Redis تلقائيًا انتهاء صلاحية TTL ويعمل عبر عمليات متعددة.


نمط ما قبل الحل

حل الرموز قبل الحاجة إليها. احتفظ بمخزن مؤقت من الرموز الجاهزة:

from collections import deque
from threading import Thread

token_buffer = deque(maxlen=5)

def pre_solve_worker(solver, method, params):
    while True:
        if len(token_buffer) < 3:
            try:
                token = solver._api_solve(method, params)
                ttl = TTL_MAP.get(method, 60)
                token_buffer.append({
                    "token": token,
                    "expires_at": time.time() + ttl
                })
            except Exception as e:
                print(f"Pre-solve failed: {e}")
        time.sleep(2)

# Start pre-solver in background
thread = Thread(
    target=pre_solve_worker,
    args=(solver, "turnstile", {"sitekey": "0x4AAAA-KEY", "pageurl": "https://example.com"}),
    daemon=True
)
thread.start()

# Consume pre-solved tokens
def get_presolved():
    while token_buffer:
        entry = token_buffer.popleft()
        if entry["expires_at"] > time.time():
            return entry["token"]
    return None

قواعد إبطال ذاكرة التخزين المؤقت

المحفّز العمل
تم رفض الرمز المميز بواسطة الموقع المستهدف إبطال وإعادة الحل
انتهت صلاحية TTL تتم إزالتها تلقائيًا من ذاكرة التخزين المؤقت
تم تغيير الوكيل إبطال رموز Cloudflare المميزة (المرتبطة بـ IP)
تم تحديث إعدادات CAPTCHA للموقع مسح جميع الرموز المميزة المخزنة مؤقتًا لهذا الموقع

استكشاف الأخطاء وإصلاحها

مشكلة السبب إصلاح
تم رفض الرمز المميز المخبأ انتهت صلاحية الرمز المميز أو تم استخدامه مرة واحدة قم بتقليل TTL أو تعطيل التخزين المؤقت لهذا النوع
ذاكرة التخزين المؤقت لا تصل أبدا تختلف المعلمات بين المكالمات تطبيع المعلمات قبل التجزئة
الرموز القديمة في Redis TTL طويل جدًا انخفاض TTL مع هامش أمان
نمو الذاكرة لا تنظيف اتصل بـ cleanup() بشكل دوري أو استخدم Redis مع TTL

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

هل يمكنني تخزين الرموز المميزة reCAPTCHA v2؟

في بعض الأحيان. تقبل العديد من المواقع الرمز المميز مرة واحدة فقط. قم بالاختبار عن طريق إرسال نفس الرمز المميز مرتين - إذا نجح الإرسال الثاني، فسيعمل التخزين المؤقت لهذا الموقع.

كم يمكن أن يوفر التخزين المؤقت؟

بالنسبة إلى Cloudflare Challenge، يمكن أن يغطي الحل الواحد جلسة كاملة مدتها 15-30 دقيقة. يمكن أن يؤدي ذلك إلى تقليل التكاليف بنسبة تزيد عن 90% للتجريف عالي التردد على نفس النطاق.

هل الحل المسبق يستحق العناء؟

نعم، إذا كان خط الأنابيب الخاص بك يحتوي على طلب يمكن التنبؤ به. يؤدي الحل المسبق إلى التخلص من وقت الانتظار على حساب هدر الرمز المميز المحتمل في حالة انخفاض الطلب.


قم بتحسين تكاليف اختبار CAPTCHA باستخدام CaptchaAI

ابدأ في تخزين الرموز المميزة فيcaptchaai.com.


أدلة ذات صلة

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