API Tutorials

Ordre des images BLS CAPTCHA et gestion des réponses de la grille

Un CAPTCHA en grille BLS renvoie des positions dans une grille d'images, pas un simple token. Tout le travail consiste à traduire ces positions en clics sur les bonnes cellules — ou en masque de bits — au format exact attendu par le formulaire, puis à les injecter sans casser la session.

Les portails de rendez-vous BLS (demandes de visa) sont un cas fréquent pour les lecteurs du Maghreb francophone ; ce guide suppose que vous automatisez votre propre démarche, sur un environnement autorisé. La suite couvre la cartographie de la grille, la résolution via l'API CaptchaAI, l'analyse de la réponse et son injection avec Selenium.

Trois types de défis en grille BLS

La forme de la réponse — et donc la manière de l'injecter — dépend directement du type de grille affiché. On en rencontre trois.

Ordre des images

Les images doivent suivre une séquence précise (chiffres croissants, ordre alphabétique). La réponse est une liste ordonnée de positions : l'ordre des clics compte autant que le choix des cellules.

Sélection d'images

Il faut cliquer toutes les images correspondant à une consigne, par exemple « sélectionnez les images contenant du texte ». La réponse est un ensemble de positions, sans ordre imposé.

Correspondance de motif

Il faut repérer les images qui reproduisent un motif ou un échantillon affiché. Là encore, la réponse se résume à une liste de positions dans la grille.

Cartographier la grille : index et coordonnées

Chaque cellule porte un index à plat, numéroté de gauche à droite et de haut en bas ; les grilles BLS font en général 3×3 ou 4×4. Les deux fonctions ci-dessous font l'aller-retour entre un index et un couple (ligne, colonne), indispensable dès que le site raisonne en coordonnées plutôt qu'en index bruts.

# grid_mapping.py

# BLS grids typically use 3x3 or 4x4 layouts
# Each cell maps to an index:

# 3x3 grid:
# [0] [1] [2]
# [3] [4] [5]
# [6] [7] [8]

# 4x4 grid:
#  [0]  [1]  [2]  [3]
#  [4]  [5]  [6]  [7]
#  [8]  [9] [10] [11]
# [12] [13] [14] [15]

def grid_position(index, cols=3):
    """Convert flat index to row, column."""
    return index // cols, index % cols


def index_from_position(row, col, cols=3):
    """Convert row, column to flat index."""
    return row * cols + col


# Example: For a 3x3 grid, position (1, 2) = index 5
print(grid_position(5, cols=3))   # (1, 2)
print(index_from_position(1, 2))  # 5

Résoudre la grille via l'API CaptchaAI

On soumet le défi avec la méthode bls sur in.php, puis on interroge res.php jusqu'à récupérer les positions. Le paramètre instructions transmet la consigne affichée à l'utilisateur, ce qui aide à lever les grilles ambiguës. Prévoyez un premier délai avant l'interrogation : la résolution n'est pas instantanée.

# solve_bls_grid.py
import requests
import time
import os
import json


def solve_bls_grid(sitekey, pageurl, instructions=None):
    """Solve a BLS grid CAPTCHA and get response indices."""
    api_key = os.environ["CAPTCHAAI_API_KEY"]

    payload = {
        "key": api_key,
        "method": "bls",
        "sitekey": sitekey,
        "pageurl": pageurl,
        "json": 1,
    }
    if instructions:
        payload["instructions"] = instructions

    resp = requests.post(
        "https://ocr.captchaai.com/in.php",
        data=payload,
        timeout=30,
    )
    result = resp.json()
    if result.get("status") != 1:
        raise RuntimeError(f"Submit failed: {result.get('request')}")

    task_id = result["request"]

    time.sleep(10)
    for _ in range(30):
        resp = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": api_key,
            "action": "get",
            "id": task_id,
            "json": 1,
        }, timeout=15)
        data = resp.json()
        if data.get("status") == 1:
            return data["request"]
        if data["request"] != "CAPCHA_NOT_READY":
            raise RuntimeError(data["request"])
        time.sleep(5)

    raise TimeoutError("BLS grid solve timeout")

Analyser la réponse : JSON, index ou masque de bits

L'API renvoie les positions sous plusieurs formes : un tableau JSON, une liste d'index séparés par des virgules, ou une valeur unique. La première fonction normalise ces trois cas en une seule liste d'entiers ; la seconde la convertit dans le format qu'attend le formulaire — certains sites veulent un masque de bits (010010000) plutôt que des index bruts.

# parse_response.py
import json


def parse_grid_response(solution):
    """Parse CaptchaAI BLS response into actionable grid data."""
    # Solution may be JSON or comma-separated indices
    if isinstance(solution, str):
        try:
            parsed = json.loads(solution)
            return parsed
        except json.JSONDecodeError:
            pass

        # Try comma-separated indices
        if "," in solution:
            return [int(x.strip()) for x in solution.split(",")]

        # Single value
        return [solution]

    return solution


def format_for_submission(indices, grid_size=9):
    """Format indices for form submission."""
    # Some sites expect a bitmask
    bitmask = ["0"] * grid_size
    for idx in indices:
        if isinstance(idx, int) and 0 <= idx < grid_size:
            bitmask[idx] = "1"

    return {
        "indices": indices,
        "bitmask": "".join(bitmask),
        "count": len(indices),
    }

Injecter la solution avec Selenium

Deux stratégies selon le formulaire : cliquer les cellules dans le bon ordre, ou écrire directement la réponse dans un champ caché. Pour les grilles d'ordre, laissez une pause un peu plus longue entre les clics — un enchaînement trop rapide fait souvent rejeter la séquence côté serveur.

# inject_grid.py
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time


def click_grid_cells(driver, indices):
    """Click specific grid cells based on solution indices."""
    wait = WebDriverWait(driver, 10)

    # Find all grid cells
    cells = wait.until(
        EC.presence_of_all_elements_located(
            (By.CSS_SELECTOR, ".captcha-grid .cell, .bls-grid img, .grid-item")
        )
    )

    for idx in indices:
        if isinstance(idx, int) and idx < len(cells):
            cells[idx].click()
            time.sleep(0.3)  # Brief delay between clicks


def set_order_sequence(driver, ordered_indices):
    """Click grid cells in the correct order for ordering challenges."""
    wait = WebDriverWait(driver, 10)

    cells = wait.until(
        EC.presence_of_all_elements_located(
            (By.CSS_SELECTOR, ".captcha-grid .cell, .bls-grid img")
        )
    )

    for idx in ordered_indices:
        if isinstance(idx, int) and idx < len(cells):
            cells[idx].click()
            time.sleep(0.5)  # Ordering needs pauses between clicks


def inject_hidden_response(driver, solution_value):
    """Set the solution in a hidden input field."""
    driver.execute_script("""
        var inputs = document.querySelectorAll(
            'input[name*="captcha"], input[name*="response"], #captcha-answer'
        );
        for (var i = 0; i < inputs.length; i++) {
            inputs[i].value = arguments[0];
        }
    """, str(solution_value))

Assembler le flux BLS de bout en bout

Le flux complet enchaîne les étapes précédentes : attendre la grille, lire le sitekey et la consigne, résoudre via l'API, choisir la méthode d'injection selon la présence de cellules cliquables, puis soumettre le formulaire.

# full_flow.py
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


def handle_bls_grid(driver, pageurl):
    """Complete BLS grid CAPTCHA handling."""

    wait = WebDriverWait(driver, 15)

    # Wait for CAPTCHA to load
    captcha = wait.until(
        EC.presence_of_element_located(
            (By.CSS_SELECTOR, "[data-sitekey], .bls-captcha")
        )
    )
    sitekey = captcha.get_attribute("data-sitekey")

    # Get instructions
    instructions = None
    try:
        inst = driver.find_element(By.CSS_SELECTOR, ".captcha-instructions")
        instructions = inst.text.strip()
    except Exception:
        pass

    # Solve via CaptchaAI
    solution = solve_bls_grid(sitekey, pageurl, instructions)
    parsed = parse_grid_response(solution)

    # Determine response method
    grid_cells = driver.find_elements(
        By.CSS_SELECTOR, ".captcha-grid .cell, .bls-grid img"
    )

    if grid_cells:
        # Click-based response
        if isinstance(parsed, list) and all(isinstance(x, int) for x in parsed):
            click_grid_cells(driver, parsed)
        else:
            inject_hidden_response(driver, solution)
    else:
        # Hidden input response
        inject_hidden_response(driver, solution)

    # Submit
    submit = driver.find_element(
        By.CSS_SELECTOR, "button[type='submit'], .submit-btn, #verify"
    )
    submit.click()

    return True

Quelle méthode d'injection choisir ?

Trois cas couvrent la quasi-totalité des formulaires BLS. Ce tableau relie chaque type de grille à la forme de réponse attendue et à la fonction à appeler.

Type de grille Forme de la réponse Fonction à appeler
Ordre des images Liste ordonnée de positions set_order_sequence()
Sélection d'images Ensemble de positions click_grid_cells()
Réponse en champ caché Chaîne ou masque de bits inject_hidden_response()

Dépannage

Problème Cause Correctif
Clics sur les mauvaises cellules Sélecteur CSS de cellule inadapté Inspectez le HTML de la grille et ajustez les sélecteurs
Séquence d'ordre rejetée Clics trop rapprochés Ajoutez 300 à 500 ms entre les clics
Format de réponse inattendu Le site attend un masque de bits, pas des index Convertissez avec format_for_submission()
Grille incomplète à la résolution Images chargées trop lentement Attendez le chargement complet avant de résoudre

FAQ

Quel format de réponse renvoie l'API pour une grille BLS ?

Un tableau JSON de positions, une liste d'index séparés par des virgules, ou une valeur unique, selon le défi. La fonction parse_grid_response() ramène ces trois cas à une seule liste exploitable.

Faut-il cliquer les cellules ou injecter la réponse dans un champ caché ?

Cela dépend du formulaire : si la grille expose des cellules cliquables, reproduisez les clics dans l'ordre ; sinon, écrivez la valeur renvoyée dans le champ caché. L'enchaînement complet détecte automatiquement le bon cas.

Combien coûte la résolution des CAPTCHA BLS avec CaptchaAI ?

La facturation repose sur les threads, pas sur le nombre de résolutions. Le forfait BASIC ($15/mois, 5 threads) couvre 5 résolutions simultanées, avec un nombre illimité de résolutions par thread ; montez en gamme quand vous avez besoin de plus de parallélisme.

Une solution BLS est-elle réutilisable ?

Non. Chaque solution est liée à une session de défi précise ; résolvez une nouvelle grille à chaque tentative plutôt que de rejouer une réponse existante.

Guides connexes

Créez votre compte CaptchaAI et résolvez votre première grille BLS.

Les commentaires sont désactivés pour cet article.