En este artículo hemos intentado responder a todas las preguntas clave. Para empezar a resolver la tarea, primero hay que determinar qué sistema de protección se está utilizando. Para ello, puedes consultar la lista de captchas y sistemas de protección antibot más populares, donde se muestran ejemplos visuales y señales clave que te ayudarán a identificar rápidamente con qué estás tratando.
Si descubres que tu sitio web utiliza ComplexImage, el siguiente paso será analizar con más detalle sus propiedades y su funcionamiento. En este mismo artículo también puedes consultar la guía de integración de ComplexImage para comprender por completo cómo funciona en tu sitio. Esto te permitirá no solo entender la protección actual, sino también planificar correctamente su mantenimiento.
Trabajar con CapMonster Cloud mediante la API suele implicar los siguientes pasos:
type - ComplexImageTask
class - recognition
imagesBase64 - un array de imágenes en formato Base64. Ejemplo: [“/9j/4AAQSkZJRgABAQEAAAAAAAD…”];
Task (dentro de metadata) - Nombre de la tarea (por ejemplo, dli).
https://api.capmonster.cloud/createTask{
"clientKey": "API_KEY",
"task": {
"type": "ComplexImageTask",
"class": "recognition",
"imagesBase64": [
"base64"
],
"metadata": {
"Task": "dli" // reemplace con la tarea deseada, la lista de módulos disponibles se encuentra en 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);
// Localizar la imagen del CAPTCHA
const captchaHandle = await page.$('#captcha'); // reemplace con el selector 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 })
);
// Enviar CAPTCHA para reconocimiento
const citRecognitionRequest = new ComplexImageTaskRecognitionRequest({
imagesBase64: [captchaBase64],
metaData: { Task: 'oocl_rotate' } // reemplace con su tipo de CAPTCHA
});
const result = await cmcClient.Solve(citRecognitionRequest);
console.log("Solution received:", result);
// Procesar la solución
const solution = result.solution;
if (!solution) {
console.error("No solution received");
return;
}
if (solution.metadata?.AnswerType === "Coordinate") {
// CAPTCHA con 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 de cuadrícula (array true/false)
const box = await captchaHandle.boundingBox();
const gridItems = await page.$$('#captcha_grid div'); // reemplace con los selectores de los elementos de la cuadrícula
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);
}
// Hacer clic en el botón de confirmación (si lo hay)
await page.click('#submit_button'); // reemplace con el selector real del botón
console.log("Captcha solved.");
}
solveComplexImageTaskPlaywright().catch(console.error);
1. Generación de CAPTCHA en el servidor.
2. Enviar CAPTCHA al cliente
<!--CAPTCHA de cuadrícula de imágenes-->
<!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 clics
async function loadCaptcha() {
const res = await fetch('/captcha'); // El servidor devuelve JSON con captchaId y array de imágenes 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();
});
// Cargar CAPTCHA al iniciar
loadCaptcha();
</script>
</body>
</html>
3. Ingreso de la respuesta por el usuario
4. Enviar respuesta al servidor:
5. Verificación en el servidor
<?php
session_start();
// TTL de CAPTCHA en segundos
define('CAPTCHA_TTL', 300); // 5 minutos
// Generar imagen aleatoria
function generateCaptchaImage($text = null) {
$width = 100;
$height = 100;
if (!$text) {
$text = substr(str_shuffle('ABCDEFGHJKLMNPQRSTUVWXYZ23456789'), 0, 2);
}
$image = imagecreatetruecolor($width, $height);
// Fondo
$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'; // Ruta a fuente 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);
}
// Crear CAPTCHA de cuadrícula
function generateGridCaptcha() {
$numImages = 9; // 3x3
$images = [];
$solution = [];
for ($i = 0; $i < $numImages; $i++) {
// Solución aleatoria, imagen correcta o no (ejemplo)
$isCorrect = rand(0,1) === 1;
$solution[] = $isCorrect;
// Generar imagen (se pueden agregar diferentes objetos para CAPTCHA real)
$text = $isCorrect ? 'OK' : 'NO';
$images[] = generateCaptchaImage($text);
}
return ['images' => $images, 'solution' => $solution];
}
// Endpoint de generación de 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 verificación de 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];
// Verificar TTL
if (time() - $captcha['timestamp'] > CAPTCHA_TTL) {
unset($_SESSION['captchas'][$captchaId]);
echo json_encode(['success' => false, 'message' => 'Captcha expired']);
exit;
}
// Verificar array true/false
$success = $captcha['solution'] === $answer;
// Eliminar CAPTCHA después de la verificación
unset($_SESSION['captchas'][$captchaId]);
echo json_encode(['success' => $success]);
exit;
}
// 404
http_response_code(404);
echo 'Not found';
6. Pasos siguientes
Adicionalmente
Realice pruebas de carga (p. ej., con k6 o JMeter) — con muchas solicitudes:
Si encuentra un sitio con CAPTCHA ya instalado u otro sistema de protección y no tiene acceso al código — ¡no hay problema! Es fácil identificar la tecnología utilizada. Para verificar el funcionamiento, puede usar CapMonster Cloud en un entorno de prueba aislado y asegurarse de que el procesamiento de tokens y la lógica funcionen correctamente.
Con CAPTCHA de imágenes basta con identificar el sistema, estudiar su comportamiento y asegurarse de que la protección funcione. En este artículo mostramos cómo reconocer CAPTCHA de imágenes (ComplexImage) y cómo integrarla o reconfigurarla para mantener la protección y controlarla de manera confiable.