Pour résoudre un CAPTCHA depuis un script shell, deux outils déjà présents sur votre serveur suffisent : cURL pour appeler l'API CaptchaAI et jq pour lire la réponse JSON. Aucun runtime Python ou Node.js, aucune dépendance lourde.
C'est exactement ce qu'il faut quand le CAPTCHA se dresse au milieu d'un cron ou d'un job CI/CD : vous récupérez le token et le pipeline continue. Ce guide couvre reCAPTCHA v2 et v3, Cloudflare Turnstile et les CAPTCHA image, en pur Bash.
Pourquoi cURL suffit pour résoudre un CAPTCHA en shell
L'API CaptchaAI est une API HTTP classique : tâche envoyée en POST, résultat interrogé en GET. cURL fait les deux nativement.
- Zéro dépendance lourde : cURL et Bash sont déjà présents sur la plupart des environnements Unix et des images Docker de base.
- Léger : pas de runtime applicatif, pas d'étape d'installation.
- Compatible cron : parfait pour les tâches planifiées qui franchissent un défi CAPTCHA sans intervention humaine.
- Prêt pour la CI/CD : fonctionne tel quel dans Docker, GitHub Actions, Jenkins ou GitLab CI.
- Combinable avec les outils shell :
jq,grep,awk, pipes et redirections chaînent la résolution avec le reste du script.
Ce qu'il vous faut avant de commencer
- Bash 4.0+
- cURL (installé par défaut sur Linux et macOS)
jqpour analyser le JSON :apt install jqoubrew install jq- Une clé API CaptchaAI (créez un compte ici)
La facturation CaptchaAI se fait par thread simultané, pas par résolution : un script shell mono-thread reste largement dans les limites du plan d'entrée BASIC ($15/mois, 5 threads), qui inclut des résolutions illimitées dans le mois.
Les deux fonctions au cœur du script : envoi et interrogation
Toute intégration CaptchaAI en shell repose sur deux opérations : envoyer la tâche, puis interroger l'API jusqu'à obtenir le token. Écrivez-les une fois, réutilisez-les partout.
Envoyer la tâche à l'API
L'endpoint in.php reçoit la tâche et renvoie un identifiant, lu via jq dans les champs status et request de la réponse JSON.
#!/bin/bash
CAPTCHAAI_URL="https://ocr.captchaai.com"
submit_task() {
local api_key="$1"
shift
local params=("$@")
local response
response=$(curl -s -X POST "${CAPTCHAAI_URL}/in.php" \
-d "key=${api_key}" \
-d "json=1" \
"${params[@]}")
local status
status=$(echo "$response" | jq -r '.status')
local request
request=$(echo "$response" | jq -r '.request')
if [ "$status" != "1" ]; then
echo "ERROR: Submit failed: $request" >&2
return 1
fi
echo "$request"
}
Interroger le résultat (polling)
La résolution n'est pas instantanée. On interroge res.php tant que la réponse vaut CAPCHA_NOT_READY, avec un délai d'expiration pour ne pas boucler à l'infini. Un intervalle de 5 secondes reste réactif sans marteler l'API.
poll_result() {
local api_key="$1"
local task_id="$2"
local max_wait="${3:-300}"
local interval="${4:-5}"
local elapsed=0
while [ "$elapsed" -lt "$max_wait" ]; do
sleep "$interval"
elapsed=$((elapsed + interval))
local response
response=$(curl -s "${CAPTCHAAI_URL}/res.php?key=${api_key}&action=get&id=${task_id}&json=1")
local status
status=$(echo "$response" | jq -r '.status')
local request
request=$(echo "$response" | jq -r '.request')
if [ "$request" = "CAPCHA_NOT_READY" ]; then
echo "Waiting... (${elapsed}s/${max_wait}s)" >&2
continue
fi
if [ "$status" != "1" ]; then
echo "ERROR: Solve failed: $request" >&2
return 1
fi
echo "$request"
return 0
done
echo "ERROR: Timeout after ${max_wait}s" >&2
return 1
}
Résoudre reCAPTCHA v2 en Bash
reCAPTCHA v2 est le cas le plus fréquent. Fournissez la clé du site (googlekey) et l'URL de la page via la méthode userrecaptcha ; vous récupérez un token attendu dans le champ g-recaptcha-response.
solve_recaptcha_v2() {
local api_key="$1"
local site_url="$2"
local sitekey="$3"
echo "Submitting reCAPTCHA v2..." >&2
local task_id
task_id=$(submit_task "$api_key" \
-d "method=userrecaptcha" \
-d "googlekey=${sitekey}" \
-d "pageurl=${site_url}")
if [ $? -ne 0 ]; then return 1; fi
echo "Task ID: $task_id" >&2
echo "Polling for solution..." >&2
local token
token=$(poll_result "$api_key" "$task_id")
if [ $? -ne 0 ]; then return 1; fi
echo "$token"
}
# Usage
API_KEY="YOUR_API_KEY"
TOKEN=$(solve_recaptcha_v2 "$API_KEY" \
"https://example.com/login" \
"6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-")
echo "Token: ${TOKEN:0:50}..."
Résoudre Cloudflare Turnstile
Turnstile suit la même logique avec la méthode turnstile. Le sitekey se passe dans le paramètre key ; le token se réinjecte dans le champ cf-turnstile-response.
solve_turnstile() {
local api_key="$1"
local site_url="$2"
local sitekey="$3"
local task_id
task_id=$(submit_task "$api_key" \
-d "method=turnstile" \
-d "key=${sitekey}" \
-d "pageurl=${site_url}")
if [ $? -ne 0 ]; then return 1; fi
poll_result "$api_key" "$task_id"
}
# Usage
TOKEN=$(solve_turnstile "$API_KEY" \
"https://example.com/form" \
"0x4AAAAAAAB5...")
Résoudre reCAPTCHA v3 avec son action
reCAPTCHA v3 ne demande aucun clic : il renvoie un score associé à une action. Précisez version=v3 et l'action attendue par la page (login, submit, verify…) pour que le token colle au contexte vérifié côté serveur.
solve_recaptcha_v3() {
local api_key="$1"
local site_url="$2"
local sitekey="$3"
local action="${4:-verify}"
local task_id
task_id=$(submit_task "$api_key" \
-d "method=userrecaptcha" \
-d "googlekey=${sitekey}" \
-d "pageurl=${site_url}" \
-d "version=v3" \
-d "action=${action}" \
if [ $? -ne 0 ]; then return 1; fi
poll_result "$api_key" "$task_id"
}
Résoudre un CAPTCHA image (OCR)
Pour les CAPTCHA image, on encode le fichier en base64 et on l'envoie via la méthode base64 ; le second helper télécharge d'abord l'image depuis une URL.
solve_image_captcha() {
local api_key="$1"
local image_path="$2"
if [ ! -f "$image_path" ]; then
echo "ERROR: File not found: $image_path" >&2
return 1
fi
local base64_data
base64_data=$(base64 -w 0 "$image_path" 2>/dev/null || base64 "$image_path")
local task_id
task_id=$(submit_task "$api_key" \
-d "method=base64" \
--data-urlencode "body=${base64_data}")
if [ $? -ne 0 ]; then return 1; fi
poll_result "$api_key" "$task_id"
}
# From URL
solve_image_from_url() {
local api_key="$1"
local image_url="$2"
local tmp_file
tmp_file=$(mktemp /tmp/captcha_XXXXXX.png)
curl -s -o "$tmp_file" "$image_url"
local result
result=$(solve_image_captcha "$api_key" "$tmp_file")
rm -f "$tmp_file"
echo "$result"
}
# Usage
TEXT=$(solve_image_captcha "$API_KEY" "captcha.png")
echo "CAPTCHA text: $TEXT"
Regrouper le tout dans une bibliothèque shell
Rassemblez ces fonctions dans un fichier captchaai.sh à sourcer : envoi, polling, solde, et un helper par type de CAPTCHA.
#!/bin/bash
# CaptchaAI Solver Library
# Source this file: source ./captchaai.sh
CAPTCHAAI_URL="https://ocr.captchaai.com"
CAPTCHAAI_POLL_INTERVAL=5
CAPTCHAAI_MAX_WAIT=300
captchaai_submit() {
local api_key="$1"; shift
local response
response=$(curl -s -X POST "${CAPTCHAAI_URL}/in.php" \
-d "key=${api_key}" -d "json=1" "$@")
local status=$(echo "$response" | jq -r '.status')
local request=$(echo "$response" | jq -r '.request')
[ "$status" = "1" ] && echo "$request" || { echo "Submit: $request" >&2; return 1; }
}
captchaai_poll() {
local api_key="$1" task_id="$2" elapsed=0
while [ "$elapsed" -lt "$CAPTCHAAI_MAX_WAIT" ]; do
sleep "$CAPTCHAAI_POLL_INTERVAL"
elapsed=$((elapsed + CAPTCHAAI_POLL_INTERVAL))
local resp=$(curl -s "${CAPTCHAAI_URL}/res.php?key=${api_key}&action=get&id=${task_id}&json=1")
local req=$(echo "$resp" | jq -r '.request')
local st=$(echo "$resp" | jq -r '.status')
[ "$req" = "CAPCHA_NOT_READY" ] && continue
[ "$st" = "1" ] && { echo "$req"; return 0; }
echo "Solve: $req" >&2; return 1
done
echo "Timeout" >&2; return 1
}
captchaai_balance() {
local api_key="$1"
curl -s "${CAPTCHAAI_URL}/res.php?key=${api_key}&action=getbalance&json=1" | jq -r '.request'
}
captchaai_recaptcha_v2() {
local key="$1" url="$2" sk="$3"
local tid=$(captchaai_submit "$key" -d "method=userrecaptcha" -d "googlekey=$sk" -d "pageurl=$url") || return 1
captchaai_poll "$key" "$tid"
}
captchaai_turnstile() {
local key="$1" url="$2" sk="$3"
local tid=$(captchaai_submit "$key" -d "method=turnstile" -d "key=$sk" -d "pageurl=$url") || return 1
captchaai_poll "$key" "$tid"
}
captchaai_image() {
local key="$1" path="$2"
local b64=$(base64 -w 0 "$path" 2>/dev/null || base64 "$path")
local tid=$(captchaai_submit "$key" -d "method=base64" --data-urlencode "body=$b64") || return 1
captchaai_poll "$key" "$tid"
}
Sourcer et utiliser la bibliothèque
Un script appelant tient alors en quelques lignes.
#!/bin/bash
source ./captchaai.sh
API_KEY="YOUR_API_KEY"
# Check balance
echo "Balance: $(captchaai_balance "$API_KEY")"
# Solve reCAPTCHA v2
TOKEN=$(captchaai_recaptcha_v2 "$API_KEY" \
"https://example.com/login" \
"6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-")
echo "Token: ${TOKEN:0:50}..."
Soumettre le formulaire avec le token résolu
Résoudre le CAPTCHA n'est que la première moitié du travail : il faut renvoyer le token au formulaire, dans le champ g-recaptcha-response, aux côtés des autres paramètres du POST.
submit_form_with_token() {
local url="$1"
local token="$2"
shift 2
curl -s -X POST "$url" \
-d "g-recaptcha-response=${token}" \
"$@"
}
# Usage: solve then submit
TOKEN=$(captchaai_recaptcha_v2 "$API_KEY" \
"https://example.com/login" "SITEKEY")
RESPONSE=$(submit_form_with_token "https://example.com/login" \
"$TOKEN" \
-d "username=user@example.com" \
-d "password=password")
echo "Response: $RESPONSE"
Lancer plusieurs résolutions en parallèle
Un script séquentiel résout un CAPTCHA à la fois. Pour traiter plusieurs pages en une passe, lancez chaque résolution dans un job d'arrière-plan (&) puis attendez la fin des processus. Le nombre de threads de votre plan borne le parallélisme.
#!/bin/bash
source ./captchaai.sh
API_KEY="YOUR_API_KEY"
RESULTS_DIR=$(mktemp -d)
# Define tasks
declare -A TASKS
TASKS["site-a"]="https://site-a.com|SITEKEY_A"
TASKS["site-b"]="https://site-b.com|SITEKEY_B"
TASKS["site-c"]="https://site-c.com|SITEKEY_C"
# Launch parallel solves
pids=()
for name in "${!TASKS[@]}"; do
IFS='|' read -r url sitekey <<< "${TASKS[$name]}"
(
token=$(captchaai_recaptcha_v2 "$API_KEY" "$url" "$sitekey" 2>/dev/null)
if [ $? -eq 0 ]; then
echo "$token" > "${RESULTS_DIR}/${name}.token"
else
echo "FAILED" > "${RESULTS_DIR}/${name}.token"
fi
) &
pids+=($!)
done
# Wait for all
for pid in "${pids[@]}"; do
wait "$pid"
done
# Collect results
echo "=== Results ==="
for name in "${!TASKS[@]}"; do
token=$(cat "${RESULTS_DIR}/${name}.token")
if [ "$token" = "FAILED" ]; then
echo "$name: FAILED"
else
echo "$name: ${token:0:50}..."
fi
done
rm -rf "$RESULTS_DIR"
Gérer les erreurs avec un backoff exponentiel
Certaines erreurs sont transitoires (ERROR_NO_SLOT_AVAILABLE, ERROR_CAPTCHA_UNSOLVABLE) et méritent une nouvelle tentative ; réessayer sur une clé invalide ne sert à rien. Un backoff exponentiel espace les tentatives et évite de saturer l'API pendant un pic.
solve_with_retry() {
local api_key="$1"
local solve_cmd="$2"
shift 2
local max_retries="${1:-3}"
local retryable_errors=("ERROR_NO_SLOT_AVAILABLE" "ERROR_CAPTCHA_UNSOLVABLE")
local attempt=0
while [ "$attempt" -le "$max_retries" ]; do
if [ "$attempt" -gt 0 ]; then
local delay=$((2 ** attempt + RANDOM % 3))
echo "Retry $attempt/$max_retries after ${delay}s..." >&2
sleep "$delay"
fi
local result
result=$($solve_cmd "$api_key" "${@:2}")
if [ $? -eq 0 ]; then
echo "$result"
return 0
fi
# Check if error is retryable
local is_retryable=0
for err in "${retryable_errors[@]}"; do
if echo "$result" | grep -q "$err"; then
is_retryable=1
break
fi
done
if [ "$is_retryable" -eq 0 ]; then
echo "$result"
return 1
fi
attempt=$((attempt + 1))
done
echo "Max retries exceeded" >&2
return 1
}
Planifier l'exécution avec cron
Le cas d'usage le plus courant en shell. Imaginez un worker nocturne sur un VPS OVHcloud (région Gravelines) qui exporte chaque matin un rapport depuis un portail interne : il vérifie le solde, résout le CAPTCHA de connexion, puis télécharge les données. Côté RGPD, journalisez le strict nécessaire — jamais le token complet ni d'identifiants personnels dans /var/log.
# Edit crontab: crontab -e
# Run daily at 8 AM
0 8 * * * /path/to/captcha-automation.sh >> /var/log/captcha.log 2>&1
Exemple de script cron
Vérifiez le solde avant de résoudre : un compte à sec échoue sinon en silence.
#!/bin/bash
source /path/to/captchaai.sh
API_KEY="YOUR_API_KEY"
LOG_FILE="/var/log/captcha-$(date +%Y%m%d).log"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"; }
# Check balance first
BALANCE=$(captchaai_balance "$API_KEY")
log "Balance: $BALANCE"
if (( $(echo "$BALANCE < 1.0" | bc -l) )); then
log "WARNING: Low balance!"
exit 1
fi
# Solve and process
TOKEN=$(captchaai_recaptcha_v2 "$API_KEY" \
"https://portal.example.com" "SITEKEY")
if [ $? -eq 0 ]; then
log "Solved successfully"
# Submit form, download data, etc.
curl -s "https://portal.example.com/data" \
-d "g-recaptcha-response=$TOKEN" \
-o "/data/export-$(date +%Y%m%d).csv"
log "Data exported"
else
log "ERROR: Failed to solve CAPTCHA"
exit 1
fi
Empaqueter dans une image Docker
Pour un déploiement reproductible, une image Alpine avec bash, curl et jq pèse une dizaine de mégaoctets et embarque toute la logique — idéale comme base d'un worker CI/CD.
FROM alpine:3.19
RUN apk add --no-cache bash curl jq
COPY captchaai.sh /usr/local/lib/captchaai.sh
COPY automation.sh /app/automation.sh
RUN chmod +x /app/automation.sh
CMD ["/app/automation.sh"]
Dépannage
| Erreur | Cause probable | Correctif |
|---|---|---|
ERROR_WRONG_USER_KEY |
Clé API invalide ou mal copiée | Vérifiez la clé sur le tableau de bord |
ERROR_ZERO_BALANCE |
Solde épuisé | Rechargez le compte |
ERROR_NO_SLOT_AVAILABLE |
Tous vos threads sont occupés | Attendez, relancez avec backoff, ou passez à un plan supérieur |
CAPCHA_NOT_READY en boucle |
Résolution encore en cours | Normal ; augmentez max_wait si le délai d'expiration arrive trop tôt |
curl: (60) SSL certificate |
Bundle CA manquant | Ajoutez --cacert /path/to/ca-bundle.crt, ou -k en test uniquement |
jq: command not found |
jq non installé |
apt install jq ou brew install jq |
base64: invalid option -- 'w' |
Syntaxe base64 de macOS (BSD) | Utilisez base64 file au lieu de base64 -w 0 file |
| Réponse vide | Problème réseau | Ajoutez -v à curl pour tracer la requête |
FAQ
Quel plan CaptchaAI choisir pour un script cron ?
Pour un cron mono-thread, BASIC ($15/mois, 5 threads) convient : facturation par thread simultané, résolutions illimitées dans le mois. Montez en gamme seulement si vous résolvez beaucoup en parallèle.
Comment interroger le résultat sans marteler l'API ?
Laissez l'intervalle de polling à 5 secondes avec un délai d'expiration (300 s par défaut). Interroger toutes les secondes n'accélère rien et multiplie les requêtes inutiles.
Puis-je lancer plusieurs résolutions en parallèle depuis Bash ?
Oui, avec des jobs d'arrière-plan (&) puis wait. Gardez le nombre de résolutions concurrentes sous le nombre de threads de votre plan, sinon vous rencontrerez ERROR_NO_SLOT_AVAILABLE.
Comment stocker ma clé API sans la coder en dur ?
Passez par une variable d'environnement : export CAPTCHAAI_KEY="..." puis référencez $CAPTCHAAI_KEY. Ne committez jamais une clé dans un fichier versionné.
Le script fonctionne-t-il sur macOS ?
Oui. macOS utilise la version BSD de base64, gérée par le repli base64 -w 0 … || base64 … déjà présent dans le script.
Guides connexes
- PowerShell et CaptchaAI pour l'automatisation Windows
- Résoudre les CAPTCHA en Perl
- Configurer votre clé API CaptchaAI
Résolvez les CAPTCHA directement depuis la ligne de commande : créez votre compte et automatisez avec Bash et cURL.