CAPTCHA thanh trượt GeeTest v3 chỉ giải được khi có đủ hai tham số: gt (định danh trang web, cố định) và challenge (token phiên, đổi mỗi lần tải). Thiếu một trong hai, CaptchaAI trả về ERROR_BAD_PARAMETERS ngay lập tức. Bài này đi thẳng vào ba cách trích xuất — chặn API đăng ký, đọc mã nguồn trang, hook initGeetest — rồi gửi kết quả tới CaptchaAI để giải.
Quy trình tổng quan gồm ba bước:
- Trích xuất
gtvàchallengebằng một trong ba cách ở dưới. - Gửi cặp tham số tới
in.php, pollingres.phpcho đến khi CaptchaAI trả kết quả. - Chèn
challenge,validate,seccodetừ kết quả vào form và submit.
Bắt đầu giải GeeTest v3 với CaptchaAI
Lấy API key tại captchaai.com trước khi làm theo các bước bên dưới — bạn sẽ cần nó ở phần gửi tham số tới CaptchaAI. Gói BASIC ($15/tháng, 5 thread) là điểm khởi đầu hợp lý để thử nghiệm trước khi scale.
Hai tham số GeeTest v3 bắt buộc: gt và challenge
gt— định danh trang web, chuỗi hex 32 ký tự, tĩnh trên mỗi site. Ví dụ:019924a82c70bb123aae90d483b6a0ed.challenge— token phiên, chuỗi hex 32 ký tự, đổi mỗi lần tải trang. Ví dụ:a3f5c8d2e1b04a6789012345678abcdf.api_server— tên miền phụ của GeeTest (tùy chọn), tĩnh trên mỗi site. Ví dụ:api-na.geetest.com.
gt không đổi giữa các lần gửi request, nhưng challenge hết hạn theo phiên — mỗi lần CAPTCHA tải lại, bạn phải trích xuất giá trị challenge mới. Gửi một challenge cũ là nguyên nhân phổ biến nhất khiến CaptchaAI hoặc chính trang web từ chối kết quả (xem phần khắc phục sự cố bên dưới).
Ở nhiều đội QA và scraping tại các công ty outsourcing IT hoặc thương mại điện tử tại TP.HCM, Hà Nội, GeeTest v3 dạng thanh trượt xuất hiện phổ biến trên trang đăng nhập của các nền tảng Đông Nam Á. Cả ba cách dưới đây đều dùng được khi kiểm thử luồng nội bộ hoặc theo dõi catalog công khai của chính doanh nghiệp.
Cách 1: Chặn API đăng ký (register)
Đây là cách phổ biến nhất và nên thử trước tiên.
Phần lớn triển khai GeeTest v3 lấy gt và challenge từ một endpoint đăng ký (URL thường chứa register, captcha hoặc geetest) ngay khi trang tải xong. Phản hồi JSON trông như sau:
{
"success": 1,
"challenge": "a3f5c8d2e1b04a6789012345678abcdf",
"gt": "019924a82c70bb123aae90d483b6a0ed",
"new_captcha": true
}
Python (requests)
import requests
import re
session = requests.Session()
html = session.get("https://staging.example.com/qa-login").text
# Find the register endpoint
register_url = re.search(
r'(https?://[^"\']+(?:register|captcha|geetest)[^"\']*)',
html
)
if register_url:
resp = session.get(register_url.group(1)).json()
gt = resp["gt"]
challenge = resp["challenge"]
print(f"gt: {gt}")
print(f"challenge: {challenge}")
JavaScript (chặn response bằng Puppeteer)
const puppeteer = require('puppeteer');
const browser = await puppeteer.launch();
const page = await browser.newPage();
let geetestParams = {};
page.on('response', async (response) => {
const url = response.url();
if (url.includes('register') || url.includes('captcha')) {
try {
const json = await response.json();
if (json.gt && json.challenge) {
geetestParams = {
gt: json.gt,
challenge: json.challenge,
};
console.log('Captured GeeTest params:', geetestParams);
}
} catch (e) {}
}
});
await page.goto('https://staging.example.com/qa-login', { waitUntil: 'networkidle2' });
console.log('Final params:', geetestParams);
Cách 2: Đọc tham số từ mã nguồn trang
- Không phải trang nào cũng gọi API riêng cho
gt/challenge— tham số có thể nằm trong data attribute của thẻ HTML (data-gt,data-challenge). - Hoặc được gán trực tiếp cho biến JavaScript ngay trong script nội tuyến.
Cả hai trường hợp đều chỉ cần regex trên HTML đã tải, không cần chặn network:
import re
# From data attributes
gt_match = re.search(r'data-gt=["\']([a-f0-9]{32})', html)
challenge_match = re.search(r'data-challenge=["\']([a-f0-9]{32})', html)
# From JavaScript variables
if not gt_match:
gt_match = re.search(r'gt\s*[=:]\s*["\']([a-f0-9]{32})', html)
if not challenge_match:
challenge_match = re.search(r'challenge\s*[=:]\s*["\']([a-f0-9]{32})', html)
gt = gt_match.group(1) if gt_match else None
challenge = challenge_match.group(1) if challenge_match else None
print(f"gt={gt}, challenge={challenge}")
Cách 3: Chặn hàm initGeetest
Khi Cách 1 và Cách 2 đều thất bại (tham số sinh hoàn toàn phía client), chặn thẳng hàm khởi tạo initGeetest() trước khi script trang chạy.
GeeTest v3 luôn gọi hàm này với gt/challenge nằm trong object cấu hình đầu tiên:
// Puppeteer: inject before page scripts
await page.evaluateOnNewDocument(() => {
window.__geetestConfig = null;
const origInit = window.initGeetest;
Object.defineProperty(window, 'initGeetest', {
set(fn) {
this._initGeetest = function(config, callback) {
window.__geetestConfig = config;
console.log('GeeTest config:', JSON.stringify(config));
return fn(config, callback);
};
},
get() { return this._initGeetest; }
});
});
await page.goto('https://staging.example.com/qa-login', { waitUntil: 'networkidle2' });
const config = await page.evaluate(() => window.__geetestConfig);
// config = { gt: "019924a...", challenge: "a3f5c8d...", product: "bind", ... }
Chọn cách theo tình huống:
- Endpoint đăng ký lộ trong tab network → Cách 1.
- Tham số nằm sẵn trong HTML tĩnh → Cách 2.
- Tham số sinh phía client → Cách 3.
Gửi tham số tới CaptchaAI để giải
Có gt và challenge rồi thì gửi task tới in.php với method=geetest, sau đó polling res.php cho đến khi có kết quả. CaptchaAI thường giải GeeTest v3 trong dưới 12 giây với tỷ lệ giải thành công cao trên các loại được hỗ trợ.
Python
import requests
import time
API_KEY = "YOUR_API_KEY"
# Submit
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "geetest",
"gt": gt,
"challenge": challenge,
"pageurl": "https://staging.example.com/qa-login",
"json": "1",
}).json()
if resp["status"] != 1:
raise Exception(f"Submit error: {resp['request']}")
task_id = resp["request"]
# Poll
for _ in range(30):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id, "json": "1"
}).json()
if result["status"] == 1:
solution = result["request"]
print(f"Solution: {solution}")
break
if result["request"] != "CAPCHA_NOT_READY":
raise Exception(f"Error: {result['request']}")
JavaScript
const submit = await axios.post('https://ocr.captchaai.com/in.php', null, {
params: {
key: 'YOUR_API_KEY',
method: 'geetest',
gt: geetestParams.gt,
challenge: geetestParams.challenge,
pageurl: 'https://staging.example.com/qa-login',
json: 1,
}
});
const taskId = submit.data.request;
let solution = null;
for (let i = 0; i < 30; i++) {
await new Promise(r => setTimeout(r, 5000));
const poll = await axios.get('https://ocr.captchaai.com/res.php', {
params: { key: 'YOUR_API_KEY', action: 'get', id: taskId, json: 1 }
});
if (poll.data.status === 1) {
solution = poll.data.request;
break;
}
}
console.log('Solution:', solution);
CaptchaAI tự nhận diện
method=geetestvà trả kết quả qua cùng luồng polling như các loại CAPTCHA khác — không cần cấu hình gì thêm ngoài API key.
Chèn kết quả GeeTest v3 vào form
Kết quả CaptchaAI trả về cho GeeTest v3 gồm ba trường: challenge, validate và seccode.
Parse chuỗi kết quả rồi ghi cả ba giá trị vào các input ẩn của form trước khi submit:
# solution is a pipe-delimited string or JSON object
# Parse and inject into the form
import json
sol = json.loads(solution) if isinstance(solution, str) else solution
driver.execute_script("""
const form = document.querySelector('form');
function addHidden(name, value) {
let input = form.querySelector(`input[name="${name}"]`);
if (!input) {
input = document.createElement('input');
input.type = 'hidden';
input.name = name;
form.appendChild(input);
}
input.value = value;
}
addHidden('geetest_challenge', arguments[0]);
addHidden('geetest_validate', arguments[1]);
addHidden('geetest_seccode', arguments[2]);
""", sol["challenge"], sol["validate"], sol["seccode"])
Câu hỏi thường gặp
Challenge GeeTest v3 có hiệu lực trong bao lâu?
Thường 60–120 giây. Trích xuất challenge và gửi tới CaptchaAI ngay, đừng để trong hàng đợi chờ xử lý.
CaptchaAI có hỗ trợ GeeTest v4 không?
Chưa. GeeTest v4 chỉ ở trạng thái sắp ra mắt trên CaptchaAI. Nếu trang mục tiêu đã dùng GeeTest v4 (captcha_id thay vì gt/challenge), hướng dẫn này chưa áp dụng được.
Vì sao regex trên HTML không lấy được challenge?
Nhiều trang tải challenge bằng API bất đồng bộ sau khi DOM đã render — regex trên HTML tĩnh sẽ không thấy giá trị này. Chuyển sang Cách 1 hoặc Cách 3 trong trường hợp đó.
Giải GeeTest v3 bằng CaptchaAI tốn bao nhiêu?
Giá theo thread, không theo lần giải. Gói BASIC ($15/tháng, 5 thread) đủ cho hầu hết đội QA hoặc scraping nhỏ; số thread quyết định số CAPTCHA giải song song, không giới hạn số lần giải trong tháng.
Có thể trích xuất tham số mà không cần trình duyệt headless không?
Tùy vào cách trang triển khai GeeTest v3:
- Có — nếu trang expose tham số qua endpoint đăng ký công khai (Cách 1) hoặc nhúng thẳng vào HTML (Cách 2), chỉ cần
requests/axioslà đủ. - Không — nếu tham số được tạo hoàn toàn bằng JavaScript phía client, bắt buộc trình duyệt headless như Puppeteer (Cách 3).
Lỗi thường gặp khi trích xuất tham số
Những lỗi dưới đây chiếm phần lớn các ticket hỗ trợ liên quan đến GeeTest v3:
ERROR_BAD_PARAMETERS— thiếugthoặcchallengetrong request gửi CaptchaAI. Cả hai đều bắt buộc; trích xuất lại trước khi gửi.- Challenge đã cũ — token
challengehết hạn trước khi gửi. Trích xuấtchallengemới ngay trước khi submit, không cache lại giá trị cũ. - Giá trị
gtsai — copy nhầmgttừ một trang khác.gtgắn với từng site; luôn trích xuất từ đúng trang mục tiêu. - Trang web từ chối kết quả đã giải — challenge hết hạn trong lúc CaptchaAI đang giải. Rút ngắn thời gian giữa lúc trích xuất và lúc gửi request.
- Trích xuất được
gtnhưng thiếuchallenge— trang dùng lazy-load, script GeeTest chưa chạy xong khi bạn đọc DOM. Đợi network rảnh (waitUntil: 'networkidle2') trước khi đọc response hoặc DOM.
Đa số lỗi ở trên đều xuất phát từ việc challenge hết hạn — luôn trích xuất ngay trước khi gửi, đừng tái sử dụng giá trị cũ giữa các lần retry.
Ghi log thời điểm trích xuất và thời điểm gửi task nếu lỗi lặp lại — chênh lệch quá lớn giữa hai mốc này là dấu hiệu rõ nhất của challenge hết hạn.