Ứng dụng web tiến bộ đưa ra những thách thức CAPTCHA độc đáo. Họ sử dụng kết xuất phía máy khách, Service Workers và điều hướng một trang — nghĩa là CAPTCHA tải động thay vì tải HTML ban đầu.CaptchaAIxử lý việc giải quyết nhưng bạn cần có chiến lược phát hiện phù hợp để bắt CAPTCHA được đưa vào DOM sau khi tải trang.
Hướng dẫn này bao gồm việc phát hiện, trích xuất và giải CAPTCHA trong ngữ cảnh PWA với Playwright và CaptchaAI.
Tại sao PWA lại khác nhau
Các trang web truyền thống phân phát CAPTCHA trong phản hồi HTML ban đầu. PWA khác nhau ở một số điểm chính:
| Khía cạnh | Trang web truyền thống | PWA |
|---|---|---|
| Đang tải CAPTCHA | Trong HTML ban đầu | Được hiển thị bằng JavaScript sau khi tải trang |
| Điều hướng trang | Tải lại toàn bộ trang | Định tuyến phía máy khách (không tải lại) |
| Nhân viên phục vụ | Không có mặt | Lưu trữ tài nguyên, có thể chặn các yêu cầu |
| Tính khả dụng của DOM | ngay lập tức | Sau khi khung kết xuất |
| Yêu cầu mạng | trực tiếp | Có thể bị chặn bởi Service Worker |
Bước 1: Đợi kết xuất CAPTCHA động
Sai lầm lớn nhất là cố gắng trích xuất khóa trang web trước khi khung PWA hiển thị tiện ích CAPTCHA. Sử dụng trình quan sát đột biến hoặc tín hiệu dành riêng cho khung:
// pwa_captcha_detector.js — Playwright script
const { chromium } = require('playwright');
const axios = require('axios');
const API_KEY = 'YOUR_API_KEY';
async function detectCaptchaInPWA(page) {
// Wait for the PWA app shell to render
await page.waitForLoadState('networkidle');
// Use MutationObserver to detect dynamically loaded CAPTCHAs
const captchaInfo = await page.evaluate(() => {
return new Promise((resolve) => {
// Check if CAPTCHA is already present
const existing = document.querySelector('.g-recaptcha, .cf-turnstile');
if (existing) {
resolve({
type: existing.classList.contains('g-recaptcha')
? 'recaptcha_v2' : 'turnstile',
sitekey: existing.getAttribute('data-sitekey'),
pageurl: window.location.href,
});
return;
}
// Watch for CAPTCHA elements added dynamically
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.nodeType !== 1) continue;
const captcha = node.matches?.('.g-recaptcha, .cf-turnstile')
? node
: node.querySelector?.('.g-recaptcha, .cf-turnstile');
if (captcha) {
observer.disconnect();
resolve({
type: captcha.classList.contains('g-recaptcha')
? 'recaptcha_v2' : 'turnstile',
sitekey: captcha.getAttribute('data-sitekey'),
pageurl: window.location.href,
});
return;
}
}
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
// Timeout after 15 seconds
setTimeout(() => {
observer.disconnect();
resolve(null);
}, 15000);
});
});
return captchaInfo;
}
async function main() {
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://example-pwa.com/login');
const captcha = await detectCaptchaInPWA(page);
if (!captcha) {
console.log('No CAPTCHA detected');
await browser.close();
return;
}
console.log(`Detected ${captcha.type}: ${captcha.sitekey}`);
// Solve with CaptchaAI
const token = await solveCaptcha(captcha);
console.log(`Token: ${token.substring(0, 50)}...`);
// Inject token
await injectToken(page, captcha.type, token);
// Submit form
await page.click('button[type="submit"]');
await page.waitForNavigation({ waitUntil: 'networkidle' });
console.log('Form submitted');
await browser.close();
}
async function solveCaptcha(captcha) {
const params = {
key: API_KEY,
pageurl: captcha.pageurl,
json: '1',
};
if (captcha.type === 'recaptcha_v2') {
params.method = 'userrecaptcha';
params.googlekey = captcha.sitekey;
} else {
params.method = 'turnstile';
params.sitekey = captcha.sitekey;
}
const submit = await axios.get(
'https://ocr.captchaai.com/in.php', { params }
);
if (submit.data.status !== 1) throw new Error(submit.data.request);
const taskId = submit.data.request;
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: API_KEY, action: 'get', id: taskId, json: '1' },
});
if (poll.data.status === 1) return poll.data.request;
if (poll.data.request !== 'CAPCHA_NOT_READY') {
throw new Error(poll.data.request);
}
}
throw new Error('Timeout');
}
async function injectToken(page, type, token) {
if (type === 'recaptcha_v2') {
await page.evaluate((t) => {
document.getElementById('g-recaptcha-response').value = t;
try {
const clients = ___grecaptcha_cfg.clients;
Object.keys(clients).forEach((k) => {
Object.keys(clients[k]).forEach((j) => {
if (clients[k][j]?.callback) clients[k][j].callback(t);
});
});
} catch (e) {}
}, token);
} else {
await page.evaluate((t) => {
const input = document.querySelector('[name="cf-turnstile-response"]');
if (input) input.value = t;
const cb = document.querySelector('.cf-turnstile')
?.getAttribute('data-callback');
if (cb && typeof window[cb] === 'function') window[cb](t);
}, token);
}
}
main().catch(console.error);
Bước 2: Xử lý bộ nhớ đệm của nhân viên dịch vụ
Nhân viên dịch vụ có thể lưu các tập lệnh CAPTCHA vào bộ nhớ đệm, dẫn đến các tiện ích cũ. Bỏ qua bộ đệm khi cần:
// Intercept and bypass Service Worker cache for CAPTCHA scripts
await page.route('**/recaptcha/**', (route) => {
route.continue({ headers: { ...route.request().headers(), 'Cache-Control': 'no-cache' } });
});
await page.route('**/turnstile/**', (route) => {
route.continue({ headers: { ...route.request().headers(), 'Cache-Control': 'no-cache' } });
});
Bước 3: Xử lý điều hướng phía máy khách
PWA sử dụng định tuyến phía máy khách - việc điều hướng đến tuyến được bảo vệ bằng CAPTCHA sẽ không kích hoạt tải trang.
// Monitor PWA route changes for new CAPTCHAs
await page.evaluate(() => {
const originalPushState = history.pushState;
history.pushState = function() {
originalPushState.apply(this, arguments);
window.dispatchEvent(new Event('pwa-route-change'));
};
});
page.on('console', async (msg) => {
// React to route changes if needed
});
// Or wait for specific route
await page.waitForURL('**/checkout', { waitUntil: 'networkidle' });
// Then detect CAPTCHA on the new route
const captcha = await detectCaptchaInPWA(page);
Bước 4: Thay thế Python bằng Selenium
# pwa_captcha_selenium.py
import time
import requests
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
API_KEY = "YOUR_API_KEY"
driver = webdriver.Chrome()
driver.get("https://example-pwa.com/login")
# Wait for PWA to render CAPTCHA
wait = WebDriverWait(driver, 20)
captcha_el = wait.until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".g-recaptcha, .cf-turnstile"))
)
sitekey = captcha_el.get_attribute("data-sitekey")
pageurl = driver.current_url
is_turnstile = "cf-turnstile" in captcha_el.get_attribute("class")
# Submit to CaptchaAI
params = {"key": API_KEY, "pageurl": pageurl, "json": "1"}
if is_turnstile:
params["method"] = "turnstile"
params["sitekey"] = sitekey
else:
params["method"] = "userrecaptcha"
params["googlekey"] = sitekey
resp = requests.get("https://ocr.captchaai.com/in.php", params=params)
task_id = resp.json()["request"]
# Poll
for _ in range(30):
time.sleep(5)
poll = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id, "json": "1",
})
if poll.json().get("status") == 1:
token = poll.json()["request"]
break
else:
raise TimeoutError("CAPTCHA not solved")
# Inject token
driver.execute_script(f"""
document.getElementById('g-recaptcha-response').value = '{token}';
""")
driver.find_element(By.CSS_SELECTOR, 'button[type="submit"]').click()
print("Form submitted")
driver.quit()
Kiểm tra tính vô hiệu của mã thông báo
- Vô hiệu hóa mã thông báo CAPTCHA được lưu trong bộ nhớ đệm khi tuyến thay đổi, trạng thái xác thực thay đổi hoặc nhân viên dịch vụ làm mới làm thay đổi ngữ cảnh trang.
- Chỉ lưu trữ trạng thái tối thiểu cần thiết để kết nối lại mã thông báo đã giải quyết với chế độ xem đang hoạt động.
- Xác minh độ mới của mã thông báo ngay trước khi gửi thay vì giả sử giải pháp nền vẫn hợp lệ.
Khắc phục sự cố
| Vấn đề | Nguyên nhân | Cách xử lý |
|---|---|---|
| Phần tử CAPTCHA không bao giờ xuất hiện | PWA chưa hiển thị lộ trình | Sử dụng waitForSelector với thời gian chờ kéo dài; đảm bảo hoàn tất việc định tuyến phía máy khách |
| Khóa trang web cũ sau khi điều hướng | Nhân viên dịch vụ phục vụ HTML được lưu trong bộ nhớ đệm | Bỏ qua các tiêu đề bộ đệm cho các tài nguyên liên quan đến CAPTCHA |
| Không tìm thấy cuộc gọi lại tiêm mã thông báo | Khung PWA quản lý trạng thái khác nhau | Kiểm tra quản lý trạng thái React/Vue/Angular; cập nhật trạng thái biểu mẫu kích hoạt |
| Gửi biểu mẫu không gửi mã thông báo | Trình xử lý biểu mẫu SPA đọc từ trạng thái thành phần, không phải DOM | Đồng thời cập nhật trạng thái của khung (ví dụ: React ref, thuộc tính phản ứng Vue) |
Câu hỏi thường gặp
PWA có sử dụng các loại CAPTCHA khác với các trang web thông thường không?
Không. PWA sử dụng cùng tiện ích reCAPTCHA, Turnstile và các tiện ích CAPTCHA khác. Sự khác biệt là thời gian - chúng được tải động.
Nhân viên dịch vụ có thể chặn việc giải CAPTCHA không?
Nhân viên dịch vụ có thể lưu các tập lệnh CAPTCHA vào bộ nhớ đệm nhưng bạn có thể bỏ qua điều này bằng các tiêu đề chặn truy xuất bộ nhớ đệm hoặc vô hiệu hóa Nhân viên dịch vụ trong quá trình tự động hóa.
CaptchaAI có xử lý các mã thông báo dành riêng cho PWA theo cách khác không?
Không. Các mã thông báo giống hệt nhau cho dù CAPTCHA được cung cấp trong PWA hay trang truyền thống. CaptchaAI sử dụng khóa trang web và URL - cả hai đều giống nhau.
bài viết liên quan
- Cách giải quyết cuộc gọi lại Recaptcha V2 bằng Api
- So sánh Geetest và Cloudflare Turnstile
- Cloudflare Turnstile 403 Sau khi sửa mã thông báo
Các bước tiếp theo
Bắt đầu giải CAPTCHA trong PWAs —lấy khóa API CaptchaAI của bạnvà tích hợp tính năng phát hiện động vào quá trình tự động hóa của bạn.
Hướng dẫn liên quan: