Khắc Phục Sự Cố

Lỗi reCAPTCHA vô hình thường gặp và cách khắc phục

reCAPTCHA vô hình thường "im lặng" khi hỏng: bạn bấm gửi, biểu mẫu không đi đâu cả và cũng chẳng có thông báo nào. Bỏ đi checkbox không có nghĩa là bớt lỗi — nó chỉ đổi sang một nhóm lỗi khó soi hơn. Trong phần lớn trường hợp, nguyên nhân rơi đúng vào bốn chỗ:

  • scraper không nhận ra widget vô hình trên trang;
  • quên gọi hàm callback sau khi chèn token;
  • gửi token đã hết hạn quá 120 giây;
  • thiếu tham số invisible=1 khi gọi CaptchaAI.

Bài viết đi qua từng mẫu lỗi kèm cách khắc phục chính xác và code chạy được ngay. Nếu bạn muốn nắm cơ chế trước khi gỡ lỗi, hãy đọc reCAPTCHA vô hình hoạt động thế nào và cách giải rồi quay lại đây. Còn nếu đang cần sửa gấp, hãy bắt đầu từ bảng tra và bảng chẩn đoán theo triệu chứng ngay bên dưới.

Bảng tra lỗi nhanh

Lỗi Nguyên nhân Cách khắc phục
ERROR_WRONG_GOOGLEKEY Sai sitekey, hoặc sitekey lấy từ một tên miền khác Lấy sitekey từ div widget vô hình hoặc lời gọi grecaptcha.render()
ERROR_PAGEURL URL không khớp — gửi URL trang cha thay vì URL iframe Dùng đúng URL nơi widget vô hình được tải
ERROR_CAPTCHA_UNSOLVABLE Google đánh dấu task là không giải được Thử lại với proxy và cookie mới; kiểm tra xem trang đã chuyển sang v3 chưa
ERROR_BAD_TOKEN_OR_PAGEURL Token bị trang đích từ chối Kiểm tra pageurl khớp chính xác; chèn qua callback, không phải trường ẩn
CAPCHA_NOT_READY Task vẫn đang xử lý Tiếp tục polling mỗi 5 giây; giải vô hình mất 10–30 giây
ERROR_KEY_DOES_NOT_EXIST API key CaptchaAI không hợp lệ Kiểm tra key tại captchaai.com/account
Token được chấp nhận nhưng biểu mẫu vẫn lỗi Chưa gọi callback sau khi chèn token Tìm và gọi hàm trong data-callback với token

Chẩn đoán nhanh theo triệu chứng

Trước khi lật từng mã lỗi, hãy đối chiếu triệu chứng bạn đang thấy với nguyên nhân hay gặp nhất. Bảng dưới giúp bạn nhảy thẳng tới phần cần đọc thay vì dò tuần tự:

Triệu chứng bạn thấy Khả năng cao nhất Phần nên đọc
Không thấy CAPTCHA nào nhưng request vẫn bị chặn scraper không nhận ra widget vô hình "Bỏ sót widget reCAPTCHA vô hình"
Token nhận được nhưng bấm gửi thì trang đứng im chưa gọi callback "Callback không được gọi"
CaptchaAI trả ERROR_CAPTCHA_UNSOLVABLE liên tục thiếu invisible=1 "Thiếu tham số invisible=1"
Hoạt động lúc test, hỏng khi chạy thật token hết hạn hoặc IP lệch "Token hết hạn" và "Token bị từ chối"
siteverify trả timeout-or-duplicate token hết hạn hoặc bị dùng lại "Token hết hạn trước khi kịp gửi"

Checklist khắc phục khi giải thất bại

Chạy lần lượt qua checklist này mỗi khi một ca giải reCAPTCHA vô hình thất bại:

Bước Cần kiểm tra Lệnh/Thao tác
1 Xác nhận đúng là vô hình, không phải v2 tiêu chuẩn Tìm data-size="invisible" hoặc size: 'invisible' trong lời gọi render
2 Kiểm tra sitekey có đúng không So sánh với data-sitekey của riêng widget vô hình
3 Chắc chắn có invisible=1 trong request API Soi lại tham số in.php
4 pageurl khớp chính xác Dùng URL từ DevTools trình duyệt, không phải URL sau chuyển hướng
5 Tìm tên hàm callback Tìm thuộc tính data-callback hoặc callback trong grecaptcha.render()
6 Đã chèn token VÀ gọi callback chưa Cả hai bước đều bắt buộc — chỉ mình token là chưa đủ
7 Kiểm tra độ mới của token Token phải được dùng trong vòng 120 giây
8 Thử với proxy nếu nghi vấn IP Thêm tham số proxyproxytype

Bỏ sót widget reCAPTCHA vô hình trên trang

reCAPTCHA vô hình không hiển thị checkbox nào. Nếu scraper của bạn không nhận ra nó, các request bị bảo vệ sẽ thất bại lặng lẽ. Các dấu hiệu thường thấy:

  • biểu mẫu gửi đi nhưng bị trả về trang cũ, không kèm thông báo;
  • server đáp lỗi chung chung hoặc bị chuyển hướng sang trang đăng nhập;
  • không có ô CAPTCHA nào hiển thị nên dễ tưởng nhầm là trang không hề bảo vệ.

Cách nhận diện trong HTML. Tìm một trong ba mẫu sau trong HTML của trang:

<!-- 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>

Script nhận diện (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

Script nhận diện (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;
}

Sai sitekey — ERROR_WRONG_GOOGLEKEY

Lỗi này xuất hiện khi sitekey bạn gửi không khớp với widget reCAPTCHA vô hình trên trang đích. Các nguyên nhân hay gặp:

  • Copy nhầm sitekey từ một widget v2 checkbox ở trang khác
  • Dùng sitekey lấy từ anchor URL của một phiên bản reCAPTCHA khác
  • Trang có nhiều widget reCAPTCHA và bạn lấy nhầm cái

Cách khắc phục: lấy đúng sitekey của widget vô hình

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://staging.example.com/qa-login")
print(f"Sitekey: {sitekey}")

Callback không được gọi — bấm gửi nhưng trang đứng im

Đây là lỗi reCAPTCHA vô hình phổ biến nhất mà dev hay bỏ sót. Điểm khác biệt nằm ở bước cuối cùng:

  • v2 checkbox: chèn token vào g-recaptcha-response là biểu mẫu chạy được;
  • reCAPTCHA vô hình: chèn token xong, bạn còn phải tự gọi hàm callback JavaScript thì biểu mẫu mới xử lý.

Chèn token mà quên callback thì biểu mẫu đứng im mãi mãi.

Luồng callback hoạt động thế nào

  1. grecaptcha.execute() kích hoạt thử thách vô hình
  2. Giải xong, Google gọi hàm được khai báo trong data-callback
  3. Hàm callback đó mới thật sự gửi biểu mẫu hoặc gọi API

Cách khắc phục: tìm và tự gọi callback

Tên callback thường nằm ở một trong ba nơi:

  • thuộc tính data-callback trên thẻ widget;
  • tham số callback trong lời gọi grecaptcha.render();
  • hàm được truyền vào grecaptcha.execute() trong mã trang.

Bước 1 — xác định tên hàm callback:

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

Bước 2 — chèn token VÀ gọi callback (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}");')

Bước 2 — chèn token VÀ gọi callback (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();
})();

Thiếu tham số invisible=1 khi gọi API

Khi giải reCAPTCHA vô hình qua CaptchaAI, bạn bắt buộc thêm invisible=1 vào request. Thiếu tham số này, hệ thống sẽ xử lý task như một v2 checkbox tiêu chuẩn. Hậu quả thường thấy:

  • trả về ERROR_CAPTCHA_UNSOLVABLE vì xử lý sai loại thử thách;
  • token có vẻ hợp lệ nhưng trang đích từ chối khi verify;
  • tỷ lệ giải thành công tụt hẳn dù sitekey vẫn đúng.

So sánh request thiếu và request đủ tham số invisible=1:

# 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)

Token hết hạn trước khi kịp gửi

Token reCAPTCHA vô hình hết hạn sau 120 giây, đúng bằng v2 tiêu chuẩn. Điều khiến bản vô hình dễ dính lỗi hết hạn hơn:

  • luồng vô hình thường chèn thêm bước giữa lúc giải và lúc gửi;
  • callback có thể phải chờ một thao tác khác trên trang rồi mới chạy;
  • khi gom nhiều biểu mẫu, token giải sớm phải nằm chờ khá lâu.

Đây là tình huống hay gặp ở các team QA tại công ty outsourcing ở TP.HCM hay Hà Nội: một job kiểm thử đăng nhập chạy hàng đêm giải cả loạt token cùng lúc rồi mới lần lượt gửi từng biểu mẫu — đến biểu mẫu thứ ba, thứ tư thì token đầu tiên đã quá 120 giây và fail mà log không nói rõ lý do. Cách sửa gọn nhất là giải riêng cho từng lần gửi, ngay sát thời điểm gửi.

Dấu hiệu thường thấy:

  • Biểu mẫu báo lỗi chung chung sau khi chèn token
  • siteverify phía server trả về timeout-or-duplicate
  • Token vốn hợp lệ nhưng mất quá lâu mới tới được bước gửi

Cách khắc phục: giải sát thời điểm gửi (just-in-time)

Chỉ gọi giải khi bạn đã sẵn sàng gửi ngay lập tức:

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

Token bị từ chối — ERROR_BAD_TOKEN_OR_PAGEURL

Trang đích đã gửi token cho Google xác minh và nhận về kết quả thất bại. Các nguyên nhân thường gặp:

Nguyên nhân Cách xác định Cách khắc phục
Sai pageurl URL không khớp tên miền đã đăng ký sitekey Dùng đúng URL nơi widget được tải
Token dùng trên tên miền khác Tái sử dụng token giữa các tên miền Giải lại với pageurl đúng tên miền
Token đã được dùng Gửi cùng một token hai lần Xin token mới cho mỗi lần gửi
IP không khớp IP của bạn khác IP của bên giải Thêm tham số proxy để khớp IP phiên
Thiếu cờ vô hình Giải theo v2 tiêu chuẩn nhưng dùng trên trang vô hình Thêm invisible=1 vào request giải

Script chẩn đoán dưới đây in ra từng bước để khoanh vùng nguyên nhân:

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

Nhiều widget reCAPTCHA trên cùng một trang

Một số trang đặt cả v2 checkbox hiển thị LẪN reCAPTCHA vô hình. Giải nhầm widget thì token vẫn hợp lệ nhưng không khớp với widget đang bảo vệ hành động bạn cần. Dấu hiệu nhận biết trang có nhiều widget:

  • vừa có ô v2 checkbox hiển thị, vừa có nút gửi chạy ngầm;
  • nhiều thẻ g-recaptcha với data-sitekey khác nhau trong cùng HTML;
  • mỗi biểu mẫu (đăng nhập, đăng ký) gắn một widget riêng.

Cách khắc phục: nhắm đúng widget

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

Câu hỏi thường gặp

Vì sao token giải đúng nhưng biểu mẫu vẫn không gửi được?

Gần như chắc chắn bạn chưa gọi hàm callback. reCAPTCHA vô hình không xử lý biểu mẫu chỉ vì trường g-recaptcha-response đã có token — nó chờ hàm khai báo trong data-callback được gọi với token đó. Hãy đọc tên callback từ HTML rồi tự gọi nó ngay sau khi chèn token, như hướng dẫn ở phần callback bên trên.

reCAPTCHA vô hình mất bao lâu để giải qua CaptchaAI?

Thường 10–30 giây. Thử thách vô hình nhìn chung nhanh hơn v2 checkbox vì không cần nhận dạng hình ảnh — nó dựa trên phân tích rủi ro hành vi. Trong lúc chờ, cứ polling res.php mỗi 5 giây cho tới khi nhận về OK|.

Lỗi timeout-or-duplicate khi verify token nghĩa là gì?

Nghĩa là token đã hết hạn (quá 120 giây) hoặc đã bị dùng một lần trước đó. Xin một token mới cho mỗi lần gửi và chỉ giải ngay sát thời điểm gửi để tránh lỗi này; đừng tái sử dụng lại một token cho hai request.

Giải reCAPTCHA vô hình có đắt hơn v2 checkbox không?

Không. CaptchaAI tính tiền theo thread (luồng giải đồng thời), không tính theo từng lần giải hay theo loại CAPTCHA. Gói BASIC ($15/tháng, 5 thread) giải reCAPTCHA vô hình, v2 và các loại được hỗ trợ khác với cùng một mức giá; bạn chỉ nâng gói khi cần thêm thread để chạy song song nhiều hơn.

Bộ giải reCAPTCHA vô hình hoàn chỉnh kèm xử lý lỗi

Gộp mọi cách xử lý lỗi ở trên vào một chỗ, đây là bộ giải sẵn sàng chạy production để bạn mang đi dùng luôn. Lớp wrapper này tự lo:

  • gửi task kèm invisible=1 và polling res.php cho tới khi có kết quả;
  • phân biệt lỗi cấu hình (không thử lại) với lỗi tạm thời (thử lại có backoff);
  • trả về None khi gặp ERROR_CAPTCHA_UNSOLVABLE để bạn xin task mới.

Dùng trực tiếp và quay lại các phần trên khi cần hiểu vì sao mỗi nhánh xử lý lại cần thiết:

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://staging.example.com/qa-login"
)
print(f"Token: {token[:50]}...")
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://staging.example.com/qa-login").then((token) => {
  console.log(`Token: ${token.substring(0, 50)}...`);
});

Đọc tiếp

Bài viết liên quan

Os comentários estão desativados para este artigo.