Neste artigo procurámos responder a todas as questões importantes. Para começar a resolver o problema, o primeiro passo é identificar que sistema de proteção está a ser utilizado. Para isso, pode consultar a lista de captchas e sistemas de proteção antibot mais populares, onde encontrará exemplos visuais e sinais principais que ajudam a perceber rapidamente com o que está a lidar.
Se verificar que o seu site utiliza ComplexImage, o passo seguinte é analisar mais detalhadamente as suas características e o seu funcionamento. Neste mesmo artigo também pode consultar o guia de integração de ComplexImage, para compreender totalmente como o sistema funciona no seu site. Isto permitirá não só entender a proteção atual, como também planear corretamente a sua manutenção.
Trabalhar com o CapMonster Cloud através da API normalmente envolve os passos seguintes:
type - ComplexImageTask
class - recognition
imagesBase64 - array de imagens em formato base64. Exemplo: [“/9j/4AAQSkZJRgABAQEAAAAAAAD…”];
Task (dentro de metadata) - nome da tarefa (por exemplo dli).
https://api.capmonster.cloud/createTask{
"clientKey": "API_KEY",
"task": {
"type": "ComplexImageTask",
"class": "recognition",
"imagesBase64": [
"base64"
],
"metadata": {
"Task": "dli" // substitua pela tarefa necessária, a lista de módulos disponíveis pode ser encontrada em https://docs.capmonster.cloud/docs/captchas/ComplexImageTask-Recognition/
}
}
}
{
"errorId":0,
"taskId":407533072
}https://api.capmonster.cloud/getTaskResult{
"clientKey":"API_KEY",
"taskId": 407533072
}
{
"solution":
{
"answer": "1",
"metadata": {
"AnswerType": "Text"
}
},
"cost": 0.0003,
"status": "ready",
"errorId": 0,
"errorCode": null,
"errorDescription": null
}
// npm install playwright @zennolab_com/capmonstercloud-client
// npx playwright install chromium
import { chromium } from 'playwright';
import { CapMonsterCloudClientFactory, ClientOptions, ComplexImageTaskRecognitionRequest } from '@zennolab_com/capmonstercloud-client';
const API_KEY = "YOUR_API_KEY";
const TARGET_URL = "https://example.com/captcha-page";
async function solveComplexImageTaskPlaywright() {
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(TARGET_URL);
// Localize a imagem do CAPTCHA
const captchaHandle = await page.$('#captcha'); // substitua pelo seletor real
const captchaBase64 = await captchaHandle.evaluate(img => {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
return canvas.toDataURL('image/png').split(',')[1];
});
console.log("Captcha base64:", captchaBase64.substring(0, 50) + "...");
const cmcClient = CapMonsterCloudClientFactory.Create(
new ClientOptions({ clientKey: API_KEY })
);
// Envie o CAPTCHA para reconhecimento
const citRecognitionRequest = new ComplexImageTaskRecognitionRequest({
imagesBase64: [captchaBase64],
metaData: { Task: 'oocl_rotate' } // substitua pelo seu tipo de CAPTCHA
});
const result = await cmcClient.Solve(citRecognitionRequest);
console.log("Solution received:", result);
// Processamento da solução
const solution = result.solution;
if (!solution) {
console.error("No solution received");
return;
}
if (solution.metadata?.AnswerType === "Coordinate") {
// CAPTCHA com coordenadas
const box = await captchaHandle.boundingBox();
for (const point of solution.answer) {
const clickX = box.x + point.X;
const clickY = box.y + point.Y;
console.log(`Clicking at: (${clickX}, ${clickY})`);
await page.mouse.click(clickX, clickY);
}
} else if (solution.metadata?.AnswerType === "Grid") {
// CAPTCHA Grid (array true/false)
const box = await captchaHandle.boundingBox();
const gridItems = await page.$$('#captcha_grid div'); // substitua pelos seletores dos elementos da grade
const answers = solution.answer;
for (let i = 0; i < answers.length; i++) {
if (answers[i] && gridItems[i]) {
const itemBox = await gridItems[i].boundingBox();
const clickX = itemBox.x + itemBox.width / 2;
const clickY = itemBox.y + itemBox.height / 2;
console.log(`Clicking grid item ${i} at: (${clickX}, ${clickY})`);
await page.mouse.click(clickX, clickY);
}
}
} else {
console.warn("Unknown captcha solution type:", solution.metadata?.AnswerType);
}
// Clique no botão de confirmação (se houver)
await page.click('#submit_button'); // substitua pelo seletor real do botão
console.log("Captcha solved.");
}
solveComplexImageTaskPlaywright().catch(console.error);
1. Geração do CAPTCHA no servidor.
2. Envio do CAPTCHA ao cliente
<!--CAPTCHA com grade de imagens-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Grid CAPTCHA Demo</title>
<style>
#captchaGrid {
display: grid;
grid-template-columns: repeat(3, 100px);
grid-gap: 10px;
margin-bottom: 10px;
}
.grid-item {
width: 100px;
height: 100px;
background-color: #eee;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border: 2px solid transparent;
}
.grid-item.selected {
border-color: blue;
}
</style>
</head>
<body>
<h1>Grid CAPTCHA Demo</h1>
<div id="captchaGrid"></div>
<button id="submitBtn">Submit</button>
<button id="refreshBtn">Refresh CAPTCHA</button>
<p id="result"></p>
<script>
let captchaId;
let answers = []; // array true/false para cliques
async function loadCaptcha() {
const res = await fetch('/captcha'); // o servidor deve retornar JSON com captchaId e array de imagens base64
const data = await res.json();
captchaId = data.captchaId;
answers = new Array(data.images.length).fill(false);
const grid = document.getElementById('captchaGrid');
grid.innerHTML = '';
data.images.forEach((imgBase64, i) => {
const div = document.createElement('div');
div.className = 'grid-item';
div.style.backgroundImage = `url('data:image/png;base64,${imgBase64}')`;
div.style.backgroundSize = 'cover';
div.addEventListener('click', () => {
answers[i] = !answers[i];
div.classList.toggle('selected', answers[i]);
});
grid.appendChild(div);
});
document.getElementById('result').textContent = '';
}
document.getElementById('refreshBtn').addEventListener('click', loadCaptcha);
document.getElementById('submitBtn').addEventListener('click', async () => {
const res = await fetch('/captcha/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ captchaId, answer: answers })
});
const data = await res.json();
document.getElementById('result').textContent = data.success ? 'Captcha passed!' : 'Captcha failed, try again.';
if (!data.success) loadCaptcha();
});
// carregamento do CAPTCHA ao iniciar
loadCaptcha();
</script>
</body>
</html>
3. Entrada de resposta pelo usuário
4. Envio da resposta ao servidor:
5. Verificação no servidor
<?php
session_start();
// TTL do CAPTCHA em segundos
define('CAPTCHA_TTL', 300); // 5 minutos
// Gerar imagem aleatória
function generateCaptchaImage($text = null) {
$width = 100;
$height = 100;
if (!$text) {
$text = substr(str_shuffle('ABCDEFGHJKLMNPQRSTUVWXYZ23456789'), 0, 2);
}
$image = imagecreatetruecolor($width, $height);
// fundo
$bgColor = imagecolorallocate($image, rand(180, 255), rand(180, 255), rand(180, 255));
imagefilledrectangle($image, 0, 0, $width, $height, $bgColor);
// texto
$textColor = imagecolorallocate($image, 0, 0, 0);
$fontSize = 15;
$fontFile = __DIR__ . '/Arial.ttf'; // caminho para fonte TTF
if (file_exists($fontFile)) {
imagettftext($image, $fontSize, rand(-20,20), 10, 50, $textColor, $fontFile, $text);
} else {
imagestring($image, 5, 10, 40, $text, $textColor);
}
ob_start();
imagepng($image);
$imgData = ob_get_clean();
imagedestroy($image);
return base64_encode($imgData);
}
// Criar CAPTCHA Grid
function generateGridCaptcha() {
$numImages = 9; // 3x3
$images = [];
$solution = [];
for ($i = 0; $i < $numImages; $i++) {
// Resolver aleatoriamente, imagem correta ou não (exemplo)
$isCorrect = rand(0,1) === 1;
$solution[] = $isCorrect;
// Gerar imagem (para CAPTCHA real, podem ser adicionados objetos)
$text = $isCorrect ? 'OK' : 'NO';
$images[] = generateCaptchaImage($text);
}
return ['images' => $images, 'solution' => $solution];
}
// Endpoint de geração do CAPTCHA
if ($_SERVER['REQUEST_METHOD'] === 'GET' && $_SERVER['REQUEST_URI'] === '/captcha') {
$captchaId = uniqid('captcha_', true);
$gridCaptcha = generateGridCaptcha();
$_SESSION['captchas'][$captchaId] = [
'solution' => $gridCaptcha['solution'],
'timestamp' => time()
];
header('Content-Type: application/json');
echo json_encode([
'captchaId' => $captchaId,
'images' => $gridCaptcha['images']
]);
exit;
}
// Endpoint de verificação do CAPTCHA
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_SERVER['REQUEST_URI'] === '/captcha/verify') {
$data = json_decode(file_get_contents('php://input'), true);
$captchaId = $data['captchaId'] ?? '';
$answer = $data['answer'] ?? [];
if (!isset($_SESSION['captchas'][$captchaId])) {
echo json_encode(['success' => false, 'message' => 'Captcha expired or not found']);
exit;
}
$captcha = $_SESSION['captchas'][$captchaId];
// verificação TTL
if (time() - $captcha['timestamp'] > CAPTCHA_TTL) {
unset($_SESSION['captchas'][$captchaId]);
echo json_encode(['success' => false, 'message' => 'Captcha expired']);
exit;
}
// verificação array true/false
$success = $captcha['solution'] === $answer;
// remover CAPTCHA após verificação
unset($_SESSION['captchas'][$captchaId]);
echo json_encode(['success' => $success]);
exit;
}
// 404
http_response_code(404);
echo 'Not found';
6. Próximos passos
Adicional
Realize teste de carga (ex: k6 ou JMeter) — com muitas requisições:
Se você encontrou um site com CAPTCHA ou outro sistema de proteção já instalado e não tem acesso ao código — não há problema! É relativamente fácil identificar a tecnologia. Para verificar o funcionamento correto, você pode usar o serviço CapMonster Cloud em ambiente de teste isolado para garantir que o processamento de tokens e a lógica de verificação funcionam corretamente.
No caso do CAPTCHA com imagens, basta reconhecer o sistema, estudar seu comportamento e garantir que a proteção funciona. Neste artigo mostramos como identificar CAPTCHA com imagens (ComplexImage) e como integrá-la ou reconfigurá-la para manter a proteção e controlar seu funcionamento.