Tích Hợp

ChromeDriver không bị phát hiện có tích hợp CaptchaAI

Khi script Selenium dừng lại trước một ô reCAPTCHA v2, undetected-chromedriver không giải hộ bạn — nó chỉ làm CAPTCHA xuất hiện ít hơn. Phần còn lại thuộc về CaptchaAI: gửi sitekey lên in.php, polling res.php cho tới khi có token, rồi đặt token vào form và submit. Bài này ghép hai mảnh đó thành một luồng Python chạy được cho reCAPTCHA v2 và Cloudflare Turnstile — hai loại CaptchaAI hỗ trợ chính thức. Nền tảng REST nằm ở hướng dẫn giải reCAPTCHA v2 qua API.


undetected-chromedriver làm gì và không làm gì

undetected-chromedriver (viết tắt là UC) là thư viện Python vá lại ChromeDriver của Selenium: tự khớp phiên bản Chrome đang cài, dọn các cờ mà driver mặc định để lộ, chỉnh vài đặc trưng trình duyệt mà website hay kiểm tra. Kết quả thực tế là số lần gặp thử thách CAPTCHA giảm, chứ không biến mất.

Ranh giới trách nhiệm:

  • UC lo phiên trình duyệt — Chrome thật, driver đã vá, cookie và session giữ nguyên giữa các bước.
  • CaptchaAI lo thử thách CAPTCHA — nhận sitekey, trả về token hợp lệ cho trang.
  • Code của bạn lo phần nối — đọc sitekey, chờ token, ghi token vào đúng field, bấm submit.

Nhầm ranh giới này là lý do phổ biến nhất khiến script chạy ngon trên máy dev nhưng hỏng khi lên server.


Chuẩn bị môi trường

Yêu cầu Chi tiết
API key CaptchaAI captchaai.com
Python 3.8+
Chrome Đã cài trên máy hoặc server chạy job

Cài hai package:

pip install undetected-chromedriver requests

Về chi phí: CaptchaAI tính tiền theo thread, không theo từng lượt giải, nên một job crawl chạy qua đêm không làm hóa đơn nhảy vọt. Gói nhỏ nhất là BASIC ($15/tháng, 5 thread — 5 CAPTCHA giải đồng thời, số lượt giải không giới hạn). Một team QA ở TP.HCM chạy 4–6 worker Selenium trên staging thường đủ với STANDARD ($30/tháng, 15 thread); pipeline theo dõi giá Shopee hay Lazada chạy nhiều tiến trình hơn thì nhìn sang ADVANCE ($90/tháng, 50 thread). Giá niêm yết bằng USD.


Khởi tạo trình duyệt

Hàm dưới trả về một instance Chrome do UC quản lý. --no-sandbox cần cho container Linux; kích thước cửa sổ cố định giúp layout ổn định giữa máy dev và server.

import undetected_chromedriver as uc
import requests
import time

def create_stealth_browser():
    """Create an undetected Chrome browser instance."""
    options = uc.ChromeOptions()
    options.add_argument("--no-sandbox")
    options.add_argument("--window-size=1920,1080")

    driver = uc.Chrome(options=options)
    return driver

Trên server không có màn hình, thêm options.add_argument("--headless=new") cho chế độ headless mới của Chrome; ưu nhược điểm nằm trong so sánh Chrome headless và có giao diện khi giải CAPTCHA.


Giải reCAPTCHA v2 trong ba bước

Trình tự luôn giống nhau: lấy sitekey → gửi task và chờ token → ghi token vào trang.

Bước 1: đọc sitekey từ DOM

Sitekey thường nằm ở data-sitekey; khi widget render trong iframe thì nằm ở tham số k= của src. Hàm dưới thử cả hai đường.

API_KEY = "YOUR_API_KEY"

def extract_recaptcha_sitekey(driver):
    """Extract reCAPTCHA v2 sitekey from the page."""
    try:
        element = driver.find_element("css selector", "[data-sitekey]")
        return element.get_attribute("data-sitekey")
    except Exception:
        # Try finding in iframe src
        iframes = driver.find_elements("css selector", "iframe[src*='recaptcha']")
        for iframe in iframes:
            src = iframe.get_attribute("src")
            if "k=" in src:
                return src.split("k=")[1].split("&")[0]
    return None

Nếu cả hai đều trả về None, khả năng cao trang chưa render xong: thêm WebDriverWait chờ [data-sitekey] trước khi đọc.

Bước 2: gửi task tới CaptchaAI rồi polling kết quả

in.php nhận task và trả về ID task; res.php là nơi bạn hỏi kết quả định kỳ (polling). Chờ 20 giây trước lần hỏi đầu, sau đó mỗi 5 giây.

def solve_recaptcha_v2(sitekey, pageurl):
    """Submit reCAPTCHA v2 to CaptchaAI and return the token."""
    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": pageurl,
        "json": 1
    }).json()

    if submit.get("status") != 1:
        raise RuntimeError(f"Submit error: {submit.get('request')}")

    task_id = submit["request"]

    time.sleep(20)
    for _ in range(30):
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY, "action": "get", "id": task_id, "json": 1
        }).json()

        if result.get("status") == 1:
            return result["request"]
        if result.get("request") != "CAPCHA_NOT_READY":
            raise RuntimeError(f"Solve error: {result['request']}")
        time.sleep(5)

    raise TimeoutError("Solve timed out")

Khi đọc log: CAPCHA_NOT_READY nghĩa là đang giải, cứ chờ tiếp; mọi giá trị khác trong request là mã lỗi thật và nên dừng vòng lặp. Đừng rút khoảng chờ xuống 1 giây — bạn chỉ tự tạo rate limit cho mình.

Bước 3: đặt token vào trang

Token đi vào textarea g-recaptcha-response. Nhiều form còn khai báo callback, nên script thứ hai gọi luôn callback đó.

def inject_recaptcha_token(driver, token):
    """Inject the solved token into the page and submit."""
    driver.execute_script(f'''
        document.getElementById("g-recaptcha-response").innerHTML = "{token}";
        document.getElementById("g-recaptcha-response").style.display = "block";
    ''')

    # If there's a callback function, trigger it
    driver.execute_script(f'''
        if (typeof ___grecaptcha_cfg !== 'undefined') {{
            var clients = ___grecaptcha_cfg.clients;
            for (var key in clients) {{
                var client = clients[key];
                if (client && client.callback) {{
                    client.callback("{token}");
                }}
            }}
        }}
    ''')

Sau bước này form đã sẵn sàng; phần còn lại chỉ là .click() vào nút submit.


Luồng hoàn chỉnh: đăng nhập trên staging

Ví dụ dưới ghép cả ba bước vào một kịch bản QA quen thuộc: đăng nhập vào trang staging do team bạn vận hành, có bật reCAPTCHA v2 để giống production.

import undetected_chromedriver as uc
import requests
import time

API_KEY = "YOUR_API_KEY"

def solve_recaptcha(sitekey, pageurl):
    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY, "method": "userrecaptcha",
        "googlekey": sitekey, "pageurl": pageurl, "json": 1
    }).json()

    if submit.get("status") != 1:
        raise RuntimeError(f"Submit error: {submit.get('request')}")

    task_id = submit["request"]
    time.sleep(20)

    for _ in range(30):
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY, "action": "get", "id": task_id, "json": 1
        }).json()
        if result.get("status") == 1:
            return result["request"]
        if result.get("request") != "CAPCHA_NOT_READY":
            raise RuntimeError(f"Solve error: {result['request']}")
        time.sleep(5)
    raise TimeoutError("Solve timed out")

def main():
    driver = uc.Chrome()

    try:
        # Navigate to target page
        driver.get("https://staging.example.com/qa-login")
        time.sleep(3)

        # Fill in form fields
        driver.find_element("id", "username").send_keys("user")
        driver.find_element("id", "password").send_keys("pass")

        # Extract sitekey
        element = driver.find_element("css selector", "[data-sitekey]")
        sitekey = element.get_attribute("data-sitekey")
        pageurl = driver.current_url
        print(f"Sitekey: {sitekey}")

        # Solve CAPTCHA
        token = solve_recaptcha(sitekey, pageurl)
        print(f"Token: {token[:50]}...")

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

        # Submit form
        driver.find_element("id", "submit-btn").click()
        time.sleep(3)

        print(f"Current URL: {driver.current_url}")
    finally:
        driver.quit()

if __name__ == "__main__":
    main()

Chú ý: pageurl lấy từ driver.current_url chứ không hardcode. Nếu trang redirect trước khi hiện form, URL hardcode sẽ khiến token bị từ chối vì không khớp domain.


Cloudflare Turnstile: khác method, khác field

Turnstile dùng method=turnstile, tham số sitekey (không phải googlekey), token đi vào input cf-turnstile-response, và chờ ngắn hơn trước lần polling đầu.

def solve_turnstile(sitekey, pageurl):
    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY, "method": "turnstile",
        "sitekey": sitekey, "pageurl": pageurl, "json": 1
    }).json()

    if submit.get("status") != 1:
        raise RuntimeError(f"Submit error: {submit.get('request')}")

    task_id = submit["request"]
    time.sleep(10)

    for _ in range(30):
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY, "action": "get", "id": task_id, "json": 1
        }).json()
        if result.get("status") == 1:
            return result["request"]
        if result.get("request") != "CAPCHA_NOT_READY":
            raise RuntimeError(f"Solve error: {result['request']}")
        time.sleep(5)
    raise TimeoutError("Solve timed out")

# Inject Turnstile token
def inject_turnstile_token(driver, token):
    driver.execute_script(f'''
        var input = document.querySelector('[name="cf-turnstile-response"]');
        if (input) input.value = "{token}";
    ''')

Chi tiết tham số nằm trong hướng dẫn giải Cloudflare Turnstile qua API; bản Puppeteer của luồng này ở bài tích hợp Puppeteer với CaptchaAI.


Bốn lỗi hay gặp khi lên production

Vấn đề Nguyên nhân Cách xử lý
Chrome không khớp phiên bản UC không tìm được driver tương ứng Cập nhật Chrome hoặc truyền version_main vào uc.Chrome()
CAPTCHA vẫn xuất hiện Trang kiểm tra sâu hơn mức UC vá được Bình thường — để CaptchaAI giải
Ghi token không có tác dụng Sai ID phần tử hoặc form dùng callback Kiểm tra lại cách trang nhúng reCAPTCHA
WebDriverException Chrome crash trong container Thêm --no-sandbox--disable-dev-shm-usage

Nên log lại ID task, thời gian giải và mã lỗi cho từng lượt. Khi cần đối soát số dư hoặc rà soát việc xử lý dữ liệu thu thập theo Nghị định 13/2023/NĐ-CP, log đó là thứ duy nhất trả lời được job nào đã chạy lúc nào.


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

Không dùng UC, chỉ dùng Selenium thường có được không?

Được. CaptchaAI chỉ cần sitekeypageurl, không quan tâm driver nào gửi request. UC chỉ giảm số lần CAPTCHA xuất hiện.

Chạy --headless=new trên server thì luồng có đổi không?

Code không đổi dòng nào. Chỉ tần suất thay đổi: chế độ headless thường làm CAPTCHA xuất hiện nhiều hơn, nên tính dư ngân sách thread.

5 thread của gói BASIC đủ cho bao nhiêu worker Selenium?

Một thread là một CAPTCHA đang được giải, không phải một worker. Vì worker chỉ chiếm thread lúc chờ token, 5 thread thường phục vụ được nhiều hơn 5 worker; khi log xuất hiện lỗi vượt giới hạn thì mới cần lên gói cao hơn.

CaptchaAI có giải hCaptcha trong luồng này không?

Không. CaptchaAI chưa hỗ trợ hCaptcha và FunCaptcha (Arkose Labs); GeeTest v4 mới ở trạng thái sắp ra mắt. Các loại dùng được gồm reCAPTCHA v2/v3, Cloudflare Turnstile và Cloudflare Challenge, GeeTest v3, CAPTCHA ảnh/OCR, grid và BLS; CaptchaFox, Friendly Captcha và Lemin đang ở bản beta.

Dùng chung với Selenium Grid được không?

Được, nhưng UC cần vá binary driver ở máy cục bộ; với cụm phân tán, hãy vá sẵn driver trên từng node trước khi chạy job.


Bắt đầu với API key của bạn

Đăng ký tại captchaai.com, lấy API key, dán vào API_KEY rồi chạy lại ví dụ Python ở trên.


Đọc thêm

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