Hướng Dẫn Thực Hành

Trích xuất CAPTCHA iframe: Giải CAPTCHA trong các khung lồng nhau

Bạn gọi switch_to.frame() đúng cú pháp nhưng vẫn không thấy sitekey đâu, vì iframe thanh toán còn lồng thêm một iframe reCAPTCHA bên trong nó? Rất phổ biến với form checkout và đăng nhập nhúng CAPTCHA qua nhiều lớp iframe. Quy trình gọn trong bốn bước:

  • Bước 1: đi đúng cây iframe bằng switch_to.frame() (Selenium) hoặc contentFrame() (Puppeteer).
  • Bước 2: đọc sitekey từ thuộc tính src hoặc data-sitekey của iframe render tiện ích.
  • Bước 3: gửi task tới CaptchaAI qua in.php, polling res.php để lấy token.
  • Bước 4: tiêm token trở lại đúng frame chứa .g-recaptcha, không phải frame gốc.

Cấu trúc iframe CAPTCHA: sitekey nằm ở lớp nào?

Một cấu trúc lồng nhau điển hình trông như sau:

Main page
  └── iframe#payment-frame (cross-origin)
        └── iframe[src*="recaptcha/api2/anchor"] (Google-hosted)
              └── reCAPTCHA widget

Sitekey không nằm ở iframe trong cùng — nó nằm ở iframe render tiện ích. Khung nội tuyến sâu nhất do Google host chỉ là thử thách trực quan, không cần tương tác trực tiếp. Bạn cần thuộc tính data-sitekey hoặc tham số k= trong src của iframe render tiện ích.

Ví dụ thực tế: một đội QA thuê ngoài tại TP.HCM kiểm thử luồng thanh toán dùng cổng thanh toán nhúng qua hai lớp iframe — sitekey nằm ở iframe thứ hai, không phải iframe ngoài cùng. Lấy nhầm sitekey ở iframe cha hoặc tiêm token sai frame, nút "Thanh toán" luôn báo lỗi CAPTCHA dù token CaptchaAI trả về hoàn toàn hợp lệ.


Trước khi viết code: 3 điều cần xác nhận trong DevTools

Kiểm tra nhanh trước khi động vào code, đỡ mất thời gian debug sai hướng:

  1. Mở tab Network hoặc Elements trong DevTools, tìm iframe[src*="recaptcha"] hoặc iframe[src*="turnstile"] — ghi lại id hoặc class để làm selector.
  2. Xác nhận iframe đó có phải cross-origin không (domain của src khác domain trang chính). Nếu có, bạn không thể execute_script trực tiếp bên trong nó.
  3. Ghi lại URL hiển thị trong thanh địa chỉ của chính iframe (mở src ở tab mới) — đây thường là giá trị đúng cho pageurl, không phải URL trang checkout.

Bỏ qua bước này là nguyên nhân phổ biến nhất khiến dev debug nhầm ở tầng CaptchaAI trong khi lỗi thực ra nằm ở việc chọn sai iframe hoặc sai pageurl.


Giải CAPTCHA trong iframe bằng Selenium (Python)

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import requests
import re
import time

API_KEY = "YOUR_API_KEY"
driver = webdriver.Chrome()
driver.get("https://example.com/checkout")

# Step 1: Switch into the outer iframe
wait = WebDriverWait(driver, 15)
outer_iframe = wait.until(
    EC.presence_of_element_located((By.CSS_SELECTOR, "iframe#payment-frame"))
)
driver.switch_to.frame(outer_iframe)

# Step 2: Find the reCAPTCHA iframe and extract sitekey
recaptcha_iframe = wait.until(
    EC.presence_of_element_located(
        (By.CSS_SELECTOR, 'iframe[src*="recaptcha/api2/anchor"]')
    )
)
src = recaptcha_iframe.get_attribute("src")
sitekey = re.search(r"k=([A-Za-z0-9_-]+)", src).group(1)
page_url = driver.current_url
print(f"Sitekey: {sitekey}")
print(f"Page URL: {page_url}")

# Step 3: Solve with CaptchaAI
resp = requests.post("https://ocr.captchaai.com/in.php", data={
    "key": API_KEY,
    "method": "userrecaptcha",
    "googlekey": sitekey,
    "pageurl": page_url,
    "json": "1"
})
task_id = resp.json()["request"]

token = None
for _ in range(24):
    time.sleep(5)
    poll = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": API_KEY, "action": "get", "id": task_id, "json": "1"
    }).json()
    if poll["status"] == 1:
        token = poll["request"]
        break
    if poll["request"] != "CAPCHA_NOT_READY":
        raise Exception(poll["request"])

print(f"Token: {token[:50]}...")

# Step 4: Inject token (still inside the outer iframe)
driver.execute_script("""
    document.querySelector('textarea[name="g-recaptcha-response"]').value = arguments[0];
    if (typeof grecaptcha !== 'undefined') {
        grecaptcha.getResponse = function() { return arguments[0]; };
    }
""", token)

# Step 5: Switch back to main page
driver.switch_to.default_content()
print("Token injected, switched back to main frame")

Kết quả mong đợi:

Sitekey: 6Le-SITEKEY-abc123
Page URL: https://example.com/checkout
Token: 03AGdBq26ZfPxL...
Token injected, switched back to main frame

Dễ bỏ sót nhất: quên gọi switch_to.default_content() sau khi tiêm token khiến các thao tác tiếp theo trên trang chính (bấm submit, đọc phần tử ngoài iframe) báo lỗi, vì Selenium vẫn "đứng" trong iframe thanh toán.


Giải CAPTCHA trong iframe bằng Puppeteer (Node.js)

Puppeteer quản lý frame như đối tượng hạng nhất, không cần switch thủ công như Selenium — chỉ cần lấy contentFrame() của handle iframe rồi thao tác trực tiếp trên đó.

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

const API_KEY = 'YOUR_API_KEY';

(async () => {
  const browser = await puppeteer.launch({ headless: false });
  const page = await browser.newPage();
  await page.goto('https://example.com/checkout', {
    waitUntil: 'networkidle2'
  });

  // Step 1: Find the payment iframe
  const paymentFrameHandle = await page.waitForSelector('iframe#payment-frame');
  const paymentFrame = await paymentFrameHandle.contentFrame();

  // Step 2: Find reCAPTCHA iframe inside the payment frame
  const recaptchaFrameHandle = await paymentFrame.waitForSelector(
    'iframe[src*="recaptcha/api2/anchor"]'
  );
  const src = await paymentFrame.evaluate(
    el => el.getAttribute('src'), recaptchaFrameHandle
  );

  const sitekey = src.match(/k=([A-Za-z0-9_-]+)/)[1];
  const pageUrl = page.url();
  console.log(`Sitekey: ${sitekey}`);

  // Step 3: Solve with CaptchaAI
  const submitResp = await axios.post('https://ocr.captchaai.com/in.php', null, {
    params: {
      key: API_KEY,
      method: 'userrecaptcha',
      googlekey: sitekey,
      pageurl: pageUrl,
      json: 1
    }
  });
  const taskId = submitResp.data.request;

  let token = null;
  for (let i = 0; i < 24; i++) {
    await new Promise(r => setTimeout(r, 5000));
    const pollResp = await axios.get('https://ocr.captchaai.com/res.php', {
      params: { key: API_KEY, action: 'get', id: taskId, json: 1 }
    });
    if (pollResp.data.status === 1) {
      token = pollResp.data.request;
      break;
    }
    if (pollResp.data.request !== 'CAPCHA_NOT_READY') {
      throw new Error(pollResp.data.request);
    }
  }

  console.log(`Token: ${token.substring(0, 50)}...`);

  // Step 4: Inject token into the payment frame
  await paymentFrame.evaluate((tkn) => {
    const textarea = document.querySelector(
      'textarea[name="g-recaptcha-response"]'
    );
    textarea.value = tkn;

    const callback = document.querySelector('.g-recaptcha')
      ?.getAttribute('data-callback');
    if (callback && typeof window[callback] === 'function') {
      window[callback](tkn);
    }
  }, token);

  console.log('Token injected into payment iframe');
})();

Khác với Selenium: gọi paymentFrame.evaluate() thay vì execute_script, và không cần bước "quay lại main frame" tương đương switch_to.default_content().


Xử lý iframe lồng từ ba cấp trở lên

Từ ba lớp iframe trở lên, gọi các lệnh chuyển frame nối tiếp nhau — mỗi lần một cấp, không nhảy cóc:

Selenium

# Main → iframe-1 → iframe-2 → CAPTCHA
driver.switch_to.frame(driver.find_element(By.ID, "iframe-1"))
driver.switch_to.frame(driver.find_element(By.ID, "iframe-2"))
# Now extract sitekey from this context

# To go back up one level:
driver.switch_to.parent_frame()

# To go back to main:
driver.switch_to.default_content()

Puppeteer

const frame1 = await (await page.$('iframe#iframe-1')).contentFrame();
const frame2 = await (await frame1.$('iframe#iframe-2')).contentFrame();
// Extract sitekey from frame2

Lỗi thường gặp khi trích xuất CAPTCHA từ iframe

Dùng URL trang chính làm pageurl

Site từ chối token dù request tới CaptchaAI báo thành công. Fix: dùng URL của đúng frame render tiện ích CAPTCHA, không phải URL top-level của trang checkout.

Không chuyển về frame gốc sau khi tiêm token

Các thao tác tiếp theo trên trang chính (bấm submit, đọc phần tử ngoài iframe) thất bại âm thầm. Fix: gọi switch_to.parent_frame() hoặc switch_to.default_content() ngay sau bước tiêm token.

Lấy sitekey từ sai iframe

Kết quả trả về lỗi ERROR_WRONG_GOOGLEKEY. Fix: sitekey nằm trên iframe chứa .g-recaptcha, không phải iframe thử thách trong cùng do Google host.

JS bị chặn bởi iframe cross-origin

Console báo SecurityError khi cố chạy execute_script. Fix: không thao tác DOM bên trong iframe cross-origin — đọc sitekey trực tiếp từ thuộc tính src, không cần vào bên trong.


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

Iframe cross-origin thì lấy sitekey bằng cách nào?

Đọc thuộc tính src hoặc data-sitekey của phần tử iframe — cả hai đọc được từ frame cha, không cần vào bên trong iframe cross-origin. Không bao giờ cần chạy JavaScript trong iframe cross-origin để lấy sitekey.

Gửi đúng pageurl rồi mà token vẫn bị site từ chối, vì sao?

Thường do pageurl trỏ vào URL trang chính thay vì URL của chính frame render CAPTCHA. Với CAPTCHA nhúng qua iframe, pageurl thường phải là URL riêng của iframe đó — kiểm tra bằng DevTools trước khi gửi task tới in.php.

execute_script báo SecurityError trong iframe, xử lý sao?

Đây là giới hạn same-origin policy của trình duyệt, không phải lỗi CaptchaAI. Không thể chạy JavaScript trong iframe cross-origin từ context frame cha — đọc sitekey từ src/data-sitekey (đọc được từ ngoài), rồi switch/contentFrame vào đúng frame chứa textarea[name="g-recaptcha-response"] để tiêm token.

Cloudflare Turnstile trong iframe có trích xuất theo cách này không?

Có — vẫn đi xuống đúng cây iframe và đọc sitekey tương tự. Khác biệt là tên field: token Turnstile điền vào cf-turnstile-response, không phải g-recaptcha-response, nên bước tiêm token cần đổi selector cho đúng loại CAPTCHA.

Selenium báo NoSuchFrameException dù iframe đã thấy trong DOM, vì sao?

Thường là timing: iframe được render bằng JavaScript sau khi trang load xong nên switch_to.frame() chạy trước khi iframe tồn tại. Dùng WebDriverWait với EC.frame_to_be_available_and_switch_to_it() thay vì switch_to.frame() gọi trực tiếp, và tăng timeout nếu payment gateway load chậm trên mạng di động.


Giải CAPTCHA ở mọi độ sâu iframe bằng CaptchaAI

Nhận khóa API của bạn tại captchaai.com.


Hướng dẫn liên quan

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