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 và --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 sitekey và pageurl, 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.