استكشاف الأخطاء

أخطاء وإصلاحات reCAPTCHA Invisible الشائعة

حين يفشل reCAPTCHA غير المرئي في الأتمتة، يكون الرمز في الغالب صحيحًا ومحلولًا بالفعل؛ العطل أنه لا يصل إلى النموذج عبر رد النداء وضمن مهلة الصلاحية. فالنسخة غير المرئية تُلغي مربع الاختيار وتنقل التحقق بالكامل إلى طبقة JavaScript ورد النداء، ومن هذه النقطة تحديدًا تبدأ أغلب الأعطال.

في الممارسة العملية، تتكرر أربعة أعطال أكثر من غيرها:

  1. الأداة غير المرئية لا تُكتشف أصلًا على الصفحة.
  2. رد النداء (callback) لا يُستدعى بعد حقن الرمز.
  3. الرمز يُرسَل بعد انتهاء صلاحيته.
  4. الطلب يُرسَل بمعاملات v2 القياسية دون invisible=1.

يشرح هذا الدليل كل نمط من هذه الأخطاء مع الإصلاح الدقيق له. وإذا كنت بحاجة إلى فهم آلية عمل النسخة غير المرئية أولًا، فابدأ من كيف يعمل reCAPTCHA غير المرئي وطريقة حله.

ابدأ من هنا: الرمز الصحيح وحده لا يكفي؛ يجب أن يصل إلى النموذج عبر رد النداء وخلال 120 ثانية. ومعظم الإصلاحات التالية تدور حول هاتين النقطتين.


مرجع سريع لأكواد الخطأ

تبدأ عملية التشخيص من رسالة الخطأ نفسها. يلخّص الجدول التالي أكثر الأكواد ظهورًا مع سببها وإصلاحه السريع، ثم نفصّل كل حالة في الأقسام اللاحقة:

الكود أو العرض السبب المباشر الإصلاح السريع
ERROR_WRONG_GOOGLEKEY مفتاح موقع خاطئ أو منسوخ من نطاق مختلف استخرج مفتاح الموقع من div الأداة غير المرئية أو من استدعاء grecaptcha.render()
ERROR_PAGEURL عدم تطابق العنوان — أُرسل عنوان الصفحة الأم بدل عنوان الـ iframe استخدم العنوان الدقيق الذي تُحمَّل عنده الأداة غير المرئية
ERROR_CAPTCHA_UNSOLVABLE وسمت Google المهمة بأنها غير قابلة للحل أعد المحاولة بوكيل وملفات ارتباط جديدة، وتحقق مما إذا كان الموقع قد انتقل إلى v3
ERROR_BAD_TOKEN_OR_PAGEURL رفض الموقع المستهدف الرمز تأكد من تطابق pageurl تمامًا، واحقن الرمز عبر رد النداء لا الحقل المخفي
CAPCHA_NOT_READY المهمة ما زالت قيد المعالجة تابع الاستطلاع الدوري كل 5 ثوانٍ؛ يستغرق الحل غير المرئي من 10 إلى 30 ثانية
ERROR_KEY_DOES_NOT_EXIST مفتاح CaptchaAI API غير صالح تحقق من المفتاح في captchaai.com/account
قُبِل الرمز لكن فشل النموذج لم يُنفَّذ رد النداء بعد حقن الرمز ابحث عن دالة data-callback واستدعِها بالرمز

لماذا يفشل reCAPTCHA غير المرئي بطريقة مختلفة عن v2

في مربع الاختيار v2، يكفي عادةً حقن الرمز في الحقل المخفي g-recaptcha-response ليقبله النموذج. أما النسخة غير المرئية فتنقل التحقق بالكامل إلى طبقة برمجية، ويتضح الفرق في نقطتين:

  • الإطلاق: v2 القياسي ينتظر تفاعل المستخدم، بينما تُطلَق النسخة غير المرئية برمجيًا عبر grecaptcha.execute() دون أي عنصر ظاهر.
  • التسليم: v2 يقبل الرمز في الحقل المخفي، بينما تشترط النسخة غير المرئية تنفيذ رد النداء بعد حقن الرمز.

قاعدة عملية: إذا كان الرمز يُقبل من CaptchaAI لكن النموذج لا يتقدّم، فالخلل في تسليم الرمز (رد النداء) لا في الحل نفسه.

مثال من الواقع: عند أتمتة اختبارات الجودة لبوابة حجوزات إقليمية تعمل بالعربية، قد تظهر صفحة تسجيل الدخول سليمة بصريًا دون أي مربع اختيار، فيُرفَض الدخول بصمت لأن السكربت لم يكتشف الأداة غير المرئية ولم يستدعِ رد النداء. والأقسام التالية مرتّبة حسب النقطة التي يتعطّل عندها سير العمل.


الخطأ 1: الصفحة تستخدم reCAPTCHA غير مرئي دون أن يكتشفه السكربت

النسخة غير المرئية بلا مربع اختيار ظاهر، لذلك قد يمرّ السكربت على الصفحة دون أن يلاحظ وجود تحقق أصلًا، فتفشل الطلبات المحمية بصمت عبر أخطاء إرسال النموذج أو عمليات إعادة توجيه غير متوقعة. علامات تدل على وجود أداة غير مرئية لم تُكتشَف:

  • النموذج يُرسَل لكن لا شيء يحدث بعده.
  • إعادة توجيه متكررة إلى الصفحة نفسها بعد الإرسال.
  • استجابة الخادم تشير إلى فشل التحقق دون رسالة واضحة.

كيف تكتشف reCAPTCHA غير المرئي

ابحث عن هذه الأنماط في HTML الصفحة:

<!-- Pattern 1: div with data-size="invisible" -->
<div class="g-recaptcha" data-sitekey="6LdKlZEU..."
     data-size="invisible"
     data-callback="onSubmit"></div>

<!-- Pattern 2: button with data-sitekey and invisible size -->
<button class="g-recaptcha"
        data-sitekey="6LdKlZEU..."
        data-callback="onSubmit"
        data-action="submit">Submit</button>

<!-- Pattern 3: programmatic render with size: invisible -->
<script>
  grecaptcha.render('submit-btn', {
    sitekey: '6LdKlZEU...',
    callback: onSubmit,
    size: 'invisible'
  });
</script>

سكربت الكشف (Python):

import requests
from bs4 import BeautifulSoup
import re

def detect_invisible_recaptcha(url):
    resp = requests.get(url)
    soup = BeautifulSoup(resp.text, "html.parser")

    # Check for data-size="invisible"
    widget = soup.find("div", {"data-size": "invisible", "class": "g-recaptcha"})
    if widget:
        return {
            "type": "invisible",
            "sitekey": widget.get("data-sitekey"),
            "callback": widget.get("data-callback")
        }

    # Check for programmatic render with invisible
    scripts = soup.find_all("script")
    for script in scripts:
        if script.string and "size" in str(script.string) and "invisible" in str(script.string):
            key_match = re.search(r"sitekey['\"]?\s*[:=]\s*['\"]([^'\"]+)", script.string)
            if key_match:
                return {
                    "type": "invisible-programmatic",
                    "sitekey": key_match.group(1),
                    "callback": "check grecaptcha.render() call"
                }

    return None

سكربت الكشف (Node.js):

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

async function detectInvisibleRecaptcha(url) {
  const { data } = await axios.get(url);
  const $ = cheerio.load(data);

  // Check for data-size="invisible"
  const widget = $(".g-recaptcha[data-size='invisible']");
  if (widget.length) {
    return {
      type: "invisible",
      sitekey: widget.attr("data-sitekey"),
      callback: widget.attr("data-callback"),
    };
  }

  // Check script tags for programmatic invisible render
  const scriptContent = $("script")
    .map((_, el) => $(el).html())
    .get()
    .join("\n");
  if (scriptContent.includes("invisible")) {
    const keyMatch = scriptContent.match(/sitekey['"]?\s*[:=]\s*['"]([^'"]+)/);
    if (keyMatch) {
      return {
        type: "invisible-programmatic",
        sitekey: keyMatch[1],
        callback: "check grecaptcha.render() call",
      };
    }
  }

  return null;
}

الخطأ 2: مفتاح موقع خاطئ — ERROR_WRONG_GOOGLEKEY

يظهر هذا الكود عندما يكون مفتاح الموقع (sitekey) المُرسَل غير مطابق للأداة غير المرئية في الصفحة المستهدفة. من أكثر الأسباب تكرارًا:

السبب ما الذي حدث فعليًا
نُسخ المفتاح من مربع اختيار v2 المفتاح يخص أداة أخرى في صفحة مختلفة
مفتاح من العنوان الأساسي لإصدار مختلف لا يطابق الأداة غير المرئية المستهدفة
وجود أدوات reCAPTCHA متعددة التُقط مفتاح الأداة الخطأ

الإصلاح: استخرج مفتاح الموقع غير المرئي الصحيح

استهدف الأداة غير المرئية تحديدًا عند استخراج المفتاح، لا أول عنصر g-recaptcha تجده على الصفحة:

import requests
from bs4 import BeautifulSoup

def get_invisible_sitekey(url):
    resp = requests.get(url)
    soup = BeautifulSoup(resp.text, "html.parser")

    # Priority 1: invisible widget
    widget = soup.find(attrs={"data-size": "invisible", "class": "g-recaptcha"})
    if widget:
        return widget["data-sitekey"]

    # Priority 2: any g-recaptcha div (may be invisible without data-size)
    widget = soup.find(class_="g-recaptcha")
    if widget and widget.get("data-sitekey"):
        return widget["data-sitekey"]

    return None

sitekey = get_invisible_sitekey("https://example.com/login")
print(f"Sitekey: {sitekey}")

الخطأ 3: رد النداء لا يُستدعى — النموذج يُرسَل دون نتيجة

تنبيه: هذا هو العطل الأول الذي يغفله المطورون في reCAPTCHA غير المرئي. فخلافًا لمربع الاختيار v2 حيث يكفي حقن الرمز في g-recaptcha-response، تعتمد النسخة غير المرئية دائمًا تقريبًا على دالة رد نداء بلغة JavaScript؛ فإن حقنت الرمز دون استدعاء رد النداء لن يُعالَج النموذج إطلاقًا.

كيف يعمل تدفق رد النداء

  1. grecaptcha.execute() يُطلق التحدي غير المرئي
  2. بعد الحل، يستدعي Google الدالة المحددة في data-callback
  3. تتولى دالة رد النداء إرسال النموذج أو تنفيذ استدعاء API

الإصلاح: ابحث عن رد النداء ونفّذه

الخطوة 1 — حدّد اسم رد النداء:

# From HTML: data-callback="onSubmit"
# From JS: callback: onSubmit
# From grecaptcha.render: second argument with callback property

الخطوة 2 — احقن الرمز واستدعِ رد النداء (Selenium):

from selenium import webdriver
import requests
import time

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

# Get sitekey
sitekey = driver.find_element("css selector", ".g-recaptcha").get_attribute("data-sitekey")
callback_name = driver.find_element("css selector", ".g-recaptcha").get_attribute("data-callback")

# Solve with CaptchaAI
task_id = requests.get("https://ocr.captchaai.com/in.php", params={
    "key": "YOUR_API_KEY",
    "method": "userrecaptcha",
    "googlekey": sitekey,
    "pageurl": driver.current_url,
    "invisible": 1
}).text.split("|")[1]

# Poll for result
token = None
for _ in range(60):
    time.sleep(5)
    resp = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": "YOUR_API_KEY",
        "action": "get",
        "id": task_id
    }).text
    if resp.startswith("OK|"):
        token = resp.split("|")[1]
        break

# Inject token into the response field
driver.execute_script(
    f'document.getElementById("g-recaptcha-response").value = "{token}";'
)

# CRITICAL: Call the callback function
driver.execute_script(f'{callback_name}("{token}");')

الخطوة 2 — احقن الرمز واستدعِ رد النداء (Puppeteer):

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

(async () => {
  const browser = await puppeteer.launch({ headless: "new" });
  const page = await browser.newPage();
  await page.goto("https://example.com/form");

  // Get sitekey and callback
  const { sitekey, callback } = await page.evaluate(() => {
    const el = document.querySelector(".g-recaptcha[data-size='invisible']");
    return {
      sitekey: el?.getAttribute("data-sitekey"),
      callback: el?.getAttribute("data-callback"),
    };
  });

  // Submit to CaptchaAI
  const submitResp = await axios.get("https://ocr.captchaai.com/in.php", {
    params: {
      key: "YOUR_API_KEY",
      method: "userrecaptcha",
      googlekey: sitekey,
      pageurl: page.url(),
      invisible: 1,
    },
  });
  const taskId = submitResp.data.split("|")[1];

  // Poll for result
  let token;
  for (let i = 0; i < 60; i++) {
    await new Promise((r) => setTimeout(r, 5000));
    const result = await axios.get("https://ocr.captchaai.com/res.php", {
      params: { key: "YOUR_API_KEY", action: "get", id: taskId },
    });
    if (result.data.startsWith("OK|")) {
      token = result.data.split("|")[1];
      break;
    }
  }

  // Inject token and fire callback
  await page.evaluate(
    (tok, cb) => {
      document.getElementById("g-recaptcha-response").value = tok;
      if (cb && typeof window[cb] === "function") {
        window[cb](tok);
      }
    },
    token,
    callback,
  );

  await browser.close();
})();

الخطأ 4: غياب المعامل invisible=1

مهم: عند حل النسخة غير المرئية عبر CaptchaAI يجب تضمين invisible=1 في الطلب. بدونه تُعامَل المهمة كتحدي مربع اختيار v2 قياسي، ما قد يؤدي إلى ERROR_CAPTCHA_UNSOLVABLE أو إلى رموز يرفضها الموقع المستهدف.

الطلب الخاطئ مقابل الطلب الصحيح

# WRONG — missing invisible=1
params = {
    "key": "YOUR_API_KEY",
    "method": "userrecaptcha",
    "googlekey": sitekey,
    "pageurl": page_url
}

# CORRECT — includes invisible=1
params = {
    "key": "YOUR_API_KEY",
    "method": "userrecaptcha",
    "googlekey": sitekey,
    "pageurl": page_url,
    "invisible": 1  # Required for invisible reCAPTCHA
}

response = requests.get("https://ocr.captchaai.com/in.php", params=params)

الخطأ 5: انتهاء صلاحية الرمز قبل الإرسال

ملاحظة: تنتهي صلاحية رموز reCAPTCHA خلال 120 ثانية، تمامًا كما في v2 القياسي. لكن سير العمل غير المرئي غالبًا ما يضيف خطوات معالجة بين لحظة الحل ولحظة الإرسال، ما يجعل انتهاء الصلاحية أكثر احتمالًا.

الأعراض التي تدل على انتهاء الصلاحية:

  • النموذج يُرجع خطأ عامًا بعد حقن الرمز.
  • تحقق الخادم عبر siteverify يُرجع timeout-or-duplicate.
  • الرمز كان صالحًا لكنه استغرق وقتًا طويلًا حتى الوصول إلى خطوة الإرسال.

الإصلاح: احلّ في الوقت المناسب

اطلب الحل فقط عندما تكون جاهزًا للإرسال الفوري:

import requests
import time

def solve_invisible_recaptcha(api_key, sitekey, page_url):
    # Submit task
    resp = requests.get("https://ocr.captchaai.com/in.php", params={
        "key": api_key,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": page_url,
        "invisible": 1
    })
    if not resp.text.startswith("OK|"):
        raise Exception(f"Submit failed: {resp.text}")
    task_id = resp.text.split("|")[1]

    # Poll for result
    for _ in range(60):
        time.sleep(5)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": api_key,
            "action": "get",
            "id": task_id
        })
        if result.text.startswith("OK|"):
            return result.text.split("|")[1]
        if result.text != "CAPCHA_NOT_READY":
            raise Exception(f"Solve failed: {result.text}")

    raise Exception("Solve timed out after 5 minutes")

# Usage: solve JUST before you need to submit
# 1. Navigate to page and prepare form data first
# 2. THEN solve the captcha
# 3. Inject token and submit immediately
token = solve_invisible_recaptcha("YOUR_API_KEY", sitekey, page_url)
# Submit within 120 seconds of receiving the token

الخطأ 6: رفض الرمز — ERROR_BAD_TOKEN_OR_PAGEURL

تحقّق الموقع المستهدف من الرمز لدى Google فحصل على فشل. الأسباب الشائعة وطريقة تمييزها:

السبب كيف تتعرّف عليه الإصلاح
pageurl خاطئ العنوان لا يطابق النطاق المسجَّل لمفتاح الموقع استخدم العنوان الدقيق الذي تُحمَّل عنده الأداة
استُخدم الرمز على نطاق مختلف إعادة استخدام الرمز عبر النطاقات احلّ باستخدام pageurl الخاص بالنطاق الصحيح
الرمز مُستخدَم مسبقًا إرسال الرمز نفسه مرتين اطلب حلًا جديدًا لكل عملية إرسال
عدم تطابق عنوان IP عنوان IP لديك يختلف عن عنوان جلسة الحل أضف معامل proxy لمطابقة عنوان IP للجلسة
غياب علم invisible حُلّ كـ v2 قياسي واستُخدم على صفحة غير مرئية أضف invisible=1 إلى طلب الحل

قائمة تحقق للتشخيص

شغّل عملية حل تشخيصية مع تسجيل مفصّل لعزل السبب:

def debug_invisible_solve(api_key, sitekey, page_url, proxy=None):
    """Run a diagnostic solve with detailed logging."""
    print(f"Sitekey: {sitekey}")
    print(f"Page URL: {page_url}")
    print(f"Proxy: {proxy or 'none'}")

    params = {
        "key": api_key,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": page_url,
        "invisible": 1
    }
    if proxy:
        params["proxy"] = proxy
        params["proxytype"] = "HTTP"

    # Submit
    resp = requests.get("https://ocr.captchaai.com/in.php", params=params)
    print(f"Submit response: {resp.text}")
    if not resp.text.startswith("OK|"):
        return None

    task_id = resp.text.split("|")[1]
    print(f"Task ID: {task_id}")

    # Poll with timing
    start = time.time()
    for _ in range(60):
        time.sleep(5)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": api_key, "action": "get", "id": task_id
        })
        elapsed = time.time() - start
        print(f"  [{elapsed:.0f}s] {result.text[:50]}")
        if result.text.startswith("OK|"):
            token = result.text.split("|")[1]
            print(f"Token received after {elapsed:.0f}s")
            print(f"Token length: {len(token)} characters")
            print(f"Token starts with: {token[:30]}...")
            return token
        if result.text != "CAPCHA_NOT_READY":
            print(f"FAILED: {result.text}")
            return None

    print("TIMEOUT after 5 minutes")
    return None

الخطأ 7: أدوات reCAPTCHA متعددة في الصفحة نفسها

بعض الصفحات تجمع بين مربع اختيار v2 ظاهر وreCAPTCHA غير مرئي في آنٍ واحد.

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

الإصلاح: استهدف الأداة الصحيحة

عدّد جميع أدوات reCAPTCHA على الصفحة أولًا، ثم اختر الأداة ذات size="invisible" تحديدًا:

from bs4 import BeautifulSoup

def find_all_recaptcha_widgets(html):
    soup = BeautifulSoup(html, "html.parser")
    widgets = []

    for el in soup.find_all(class_="g-recaptcha"):
        widgets.append({
            "sitekey": el.get("data-sitekey"),
            "size": el.get("data-size", "normal"),
            "callback": el.get("data-callback"),
            "tag": el.name,
            "id": el.get("id")
        })

    return widgets

# Example output:
# [
#   {"sitekey": "6LdA...", "size": "normal", "callback": None, "tag": "div", "id": "recaptcha-login"},
#   {"sitekey": "6LdB...", "size": "invisible", "callback": "onRegister", "tag": "div", "id": "recaptcha-register"}
# ]
# Use the widget with size="invisible" for the invisible solve

حلّال reCAPTCHA غير المرئي كامل مع معالجة الأخطاء

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

import requests
import time
import logging

logger = logging.getLogger(__name__)

class InvisibleRecaptchaSolver:
    def __init__(self, api_key, max_retries=3):
        self.api_key = api_key
        self.max_retries = max_retries
        self.base_url = "https://ocr.captchaai.com"

    def solve(self, sitekey, page_url, proxy=None):
        """Solve invisible reCAPTCHA with automatic retry on transient errors."""
        for attempt in range(1, self.max_retries + 1):
            try:
                token = self._attempt_solve(sitekey, page_url, proxy)
                if token:
                    return token
            except Exception as e:
                logger.warning(f"Attempt {attempt} failed: {e}")
                if attempt < self.max_retries:
                    time.sleep(2 ** attempt)
        raise Exception(f"Failed to solve after {self.max_retries} attempts")

    def _attempt_solve(self, sitekey, page_url, proxy):
        params = {
            "key": self.api_key,
            "method": "userrecaptcha",
            "googlekey": sitekey,
            "pageurl": page_url,
            "invisible": 1
        }
        if proxy:
            params["proxy"] = proxy
            params["proxytype"] = "HTTP"

        # Submit task
        resp = requests.get(f"{self.base_url}/in.php", params=params)

        if "ERROR" in resp.text:
            error = resp.text.strip()
            if error in ("ERROR_WRONG_GOOGLEKEY", "ERROR_KEY_DOES_NOT_EXIST"):
                raise Exception(f"Configuration error (do not retry): {error}")
            if error == "ERROR_ZERO_BALANCE":
                raise Exception("Account balance is zero — add funds")
            raise Exception(f"Submit error: {error}")

        if not resp.text.startswith("OK|"):
            raise Exception(f"Unexpected submit response: {resp.text}")

        task_id = resp.text.split("|")[1]

        # Poll for result
        for _ in range(60):
            time.sleep(5)
            result = requests.get(f"{self.base_url}/res.php", params={
                "key": self.api_key,
                "action": "get",
                "id": task_id
            })

            if result.text.startswith("OK|"):
                return result.text.split("|")[1]

            if result.text == "CAPCHA_NOT_READY":
                continue

            if result.text == "ERROR_CAPTCHA_UNSOLVABLE":
                logger.warning("Captcha unsolvable — will retry with new task")
                return None

            raise Exception(f"Poll error: {result.text}")

        raise Exception("Solve timed out after 5 minutes")


# Usage
solver = InvisibleRecaptchaSolver("YOUR_API_KEY")
token = solver.solve(
    sitekey="6LdKlZEU...",
    page_url="https://example.com/login"
)
print(f"Token: {token[:50]}...")

والنسخة نفسها بلغة Node.js:

const axios = require("axios");

class InvisibleRecaptchaSolver {
  constructor(apiKey, maxRetries = 3) {
    this.apiKey = apiKey;
    this.maxRetries = maxRetries;
    this.baseUrl = "https://ocr.captchaai.com";
  }

  async solve(sitekey, pageUrl, proxy) {
    for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
      try {
        const token = await this._attemptSolve(sitekey, pageUrl, proxy);
        if (token) return token;
      } catch (err) {
        console.warn(`Attempt ${attempt} failed: ${err.message}`);
        if (attempt < this.maxRetries) {
          await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));
        }
      }
    }
    throw new Error(`Failed to solve after ${this.maxRetries} attempts`);
  }

  async _attemptSolve(sitekey, pageUrl, proxy) {
    const params = {
      key: this.apiKey,
      method: "userrecaptcha",
      googlekey: sitekey,
      pageurl: pageUrl,
      invisible: 1,
    };
    if (proxy) {
      params.proxy = proxy;
      params.proxytype = "HTTP";
    }

    // Submit task
    const submitResp = await axios.get(`${this.baseUrl}/in.php`, { params });
    if (submitResp.data.includes("ERROR")) {
      const error = submitResp.data.trim();
      if (["ERROR_WRONG_GOOGLEKEY", "ERROR_KEY_DOES_NOT_EXIST"].includes(error)) {
        throw new Error(`Configuration error (do not retry): ${error}`);
      }
      throw new Error(`Submit error: ${error}`);
    }

    const taskId = submitResp.data.split("|")[1];

    // Poll for result
    for (let i = 0; i < 60; i++) {
      await new Promise((r) => setTimeout(r, 5000));
      const result = await axios.get(`${this.baseUrl}/res.php`, {
        params: { key: this.apiKey, action: "get", id: taskId },
      });

      if (result.data.startsWith("OK|")) {
        return result.data.split("|")[1];
      }
      if (result.data === "CAPCHA_NOT_READY") continue;
      if (result.data === "ERROR_CAPTCHA_UNSOLVABLE") return null;
      throw new Error(`Poll error: ${result.data}`);
    }
    throw new Error("Solve timed out after 5 minutes");
  }
}

// Usage
const solver = new InvisibleRecaptchaSolver("YOUR_API_KEY");
solver.solve("6LdKlZEU...", "https://example.com/login").then((token) => {
  console.log(`Token: ${token.substring(0, 50)}...`);
});

قائمة التحقق النهائية عند فشل الحل

مرّ على هذه الخطوات بالترتيب عند تعطّل حل reCAPTCHA غير المرئي؛ كل خطوة تعزل سببًا محتملًا:

الخطوة التحقق الأمر أو الإجراء
1 تأكد أنه غير مرئي وليس v2 قياسيًا ابحث عن data-size="invisible" أو size: 'invisible' في استدعاء العرض
2 تحقق من صحة مفتاح الموقع قارنه بقيمة data-sitekey على الأداة غير المرئية تحديدًا
3 أكّد وجود invisible=1 في طلب الـ API راجع معاملات in.php
4 تحقق من تطابق pageurl تمامًا استخدم عنوان أدوات مطوّري المتصفح، لا عنوان إعادة التوجيه
5 جد اسم دالة رد النداء ابحث عن سمة data-callback أو callback داخل grecaptcha.render()
6 تحقق من حقن الرمز واستدعاء رد النداء معًا الخطوتان مطلوبتان؛ الرمز وحده لا يكفي
7 تحقق من حداثة الرمز يجب استخدام الرمز خلال 120 ثانية
8 اختبر بوكيل إذا كان عنوان IP مهمًا أضف معاملي proxy وproxytype

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

كيف أميّز reCAPTCHA غير المرئي عن v3 على الصفحة؟

  • غير المرئي: يعرض شارة reCAPTCHA في زاوية الصفحة، ويعتمد على data-size="invisible" أو على grecaptcha.execute() المربوط بزر.
  • v3: يعمل في الخلفية على كل صفحة ويعيد درجة (score) دون أي تفاعل، وغالبًا بلا شارة ظاهرة.
  • للتمييز بينهما: افحص HTML بحثًا عن data-size أو عن استدعاء render صريح.

لماذا يتكرر ERROR_CAPTCHA_UNSOLVABLE رغم صحة المفتاح؟

غالبًا لأن الطلب أُرسل دون invisible=1، فيُعامَل التحدي كـ v2 قياسي ويتعذّر حله. تحقق أيضًا من سمعة عنوان IP وملفات تعريف الارتباط، وأعد المحاولة بوكيل نظيف. وإذا استمر الخطأ رغم ذلك، فقد يكون الموقع قد انتقل إلى v3.

هل يمكنني إعادة استخدام الرمز نفسه لأكثر من عملية إرسال؟

لا. كل رمز صالح لعملية إرسال واحدة، وتنتهي صلاحيته خلال 120 ثانية. إعادة استخدام الرمز نفسه تؤدي إلى timeout-or-duplicate أو ERROR_BAD_TOKEN_OR_PAGEURL. اطلب حلًا جديدًا لكل محاولة إرسال.

متى أحتاج إلى بروكسي لحل النسخة غير المرئية؟

ليس دائمًا. لكن إذا كان الموقع يقارن عنوان IP الذي حُلّ عبره الرمز بعنوان جلستك، فأضف معاملي proxy وproxytype لمطابقة عنوان IP للجلسة. هذا يفسّر غالبًا نجاح الرمز في بيئة الاختبار وفشله في الإنتاج.

هل يلزم متصفح فعلي لحل reCAPTCHA غير المرئي؟

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


الخطوات التالية

أدلة ذات صلة

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