Troubleshooting

Изображение CAPTCHA возвращает неправильный ответ: оптимизация качества

Если API CaptchaAI регулярно присылает неверный текст на изображение CAPTCHA, дело почти никогда не в самом распознавании. В девяти случаях из десяти причина в том, что именно и как вы отправляете на вход: обрезанный скриншот, пережатый PNG, битая base64-строка или недостающая подсказка по языку.

Ниже — таблица частых причин, быстрая самопроверка по симптому и шесть конкретных исправлений с рабочим кодом на Python, которые закрывают почти все подобные тикеты в поддержку.


Причины неправильного ответа

Причина Частота Исправление
Изображение обрезано неправильно Очень часто Захватывайте полный элемент CAPTCHA, а не кусок страницы
Низкое разрешение или сильное сжатие Часто Отправляйте изображение более высокого качества
Неправильная кодировка изображения Часто Проверьте кодировку base64 «туда-обратно»
Нет подсказки по языку или типу символов Иногда Добавьте language или textinstructions
Устаревший или просроченный кадр Иногда Делайте свежий снимок непосредственно перед решением

Первые две строки этой таблицы объясняют большинство обращений в поддержку: скрипт снимает скриншот всей формы вместо самого поля CAPTCHA либо сохраняет картинку через конвертер, который режет качество.


Быстрая самопроверка: от симптома к исправлению

Сверьтесь со своим случаем ниже — список отсортирован так же, как исправления дальше по статье, и почти всегда указывает на один конкретный пункт:

  • Ответ — бессмысленный набор символов → скорее всего сломана кодировка base64 → см. Исправление 2.
  • Ответ близок, но не совпадает → низкое качество или резкость изображения → см. Исправление 3 (предобработка).
  • В ответе неверное число символов → не заданы min_len/max_len → см. Исправление 4.
  • В ответе перепутаны буквы и цифры → не задан параметр numeric → см. Исправление 4.
  • Возвращается пустой ответ → пустой или повреждённый файл → см. Исправление 1 (проверка перед отправкой).
  • Ответ верный, но форма его не принимает → чувствительность к регистру → добавьте textinstructions (см. Исправление 4).

Исправление 1. Проверьте качество изображения перед отправкой

Прежде чем разбираться с кодировкой или подсказками, убедитесь, что сам файл вообще пригоден для распознавания: не слишком маленький, не пустой и не «пережат» до артефактов. Простая функция-валидатор ловит большинство проблем ещё до сетевого запроса и экономит поток на заведомо провальных задачах.

import base64
from io import BytesIO
from PIL import Image


def validate_captcha_image(image_path):
    """Check image quality before submitting to CaptchaAI."""
    img = Image.open(image_path)
    width, height = img.size
    issues = []

    # Minimum resolution
    if width < 50 or height < 20:
        issues.append(f"Too small: {width}x{height}px (min 50x20)")

    # Check if mostly blank
    pixels = list(img.getdata())
    if img.mode == "RGB":
        white_count = sum(1 for p in pixels if p[0] > 250 and p[1] > 250 and p[2] > 250)
    else:
        white_count = sum(1 for p in pixels if p > 250)

    blank_ratio = white_count / len(pixels)
    if blank_ratio > 0.95:
        issues.append(f"Image appears blank ({blank_ratio:.0%} white)")

    # File size check
    img_bytes = BytesIO()
    img.save(img_bytes, format="PNG")
    size_kb = img_bytes.tell() / 1024
    if size_kb < 1:
        issues.append(f"File too small ({size_kb:.1f} KB) — may be empty")
    if size_kb > 600:
        issues.append(f"File too large ({size_kb:.0f} KB) — submit under 600 KB")

    return issues


issues = validate_captcha_image("captcha.png")
if issues:
    for issue in issues:
        print(f"WARNING: {issue}")
else:
    print("Image quality OK")

Исправление 2. Кодируйте base64 без потерь

Отдельный класс багов — это не само изображение, а то, что происходит с ним при кодировании: путают путь к файлу и его содержимое, забывают открыть файл в бинарном режиме или обрезают строку при логировании. Проверка «туда-обратно» (encode → decode → сравнить с оригиналом) ловит это за одну строку кода.

import base64


def encode_captcha(image_path):
    """Properly encode a CAPTCHA image to base64."""
    with open(image_path, "rb") as f:
        raw = f.read()

    encoded = base64.b64encode(raw).decode("ascii")

    # Verify round-trip
    decoded = base64.b64decode(encoded)
    assert decoded == raw, "Base64 encoding corrupted the image"

    return encoded


# WRONG — encoding a file path string
bad = base64.b64encode(b"captcha.png").decode()  # Encodes filename, not image!

# CORRECT — encoding file contents
with open("captcha.png", "rb") as f:
    good = base64.b64encode(f.read()).decode()

Исправление 3. Предобработка: масштаб, контраст, резкость

Предобработка нужна не всегда — но если исходный кадр мелкий или блёклый (типично для мобильных скриншотов и сжатых прокси-каналов), апскейл, усиление контраста и резкость перед кодированием заметно повышают шанс на верный ответ.

from PIL import Image, ImageFilter, ImageEnhance
from io import BytesIO
import base64


def preprocess_captcha(image_path):
    """Improve image quality for better OCR accuracy."""
    img = Image.open(image_path)

    # Convert to RGB if needed
    if img.mode != "RGB":
        img = img.convert("RGB")

    # Upscale small images
    width, height = img.size
    if width < 200:
        scale = 200 / width
        img = img.resize(
            (int(width * scale), int(height * scale)),
            Image.LANCZOS,
        )

    # Increase contrast
    enhancer = ImageEnhance.Contrast(img)
    img = enhancer.enhance(1.5)

    # Sharpen
    img = img.filter(ImageFilter.SHARPEN)

    # Convert to PNG bytes
    buffer = BytesIO()
    img.save(buffer, format="PNG")
    return base64.b64encode(buffer.getvalue()).decode()

Исправление 4. Передавайте подсказки по языку и типу символов

Если известны ограничения формата ответа — язык алфавита, только цифры, диапазон длины — их стоит передать явно. Это не обязательный шаг, но он резко сокращает долю ответов «почти верно, но не то»: сервис перестаёт гадать между похожими символами разных алфавитов.

import requests


def solve_image(api_key, image_base64, **hints):
    """Submit image CAPTCHA with quality hints."""
    data = {
        "key": api_key,
        "method": "base64",
        "body": image_base64,
        "json": 1,
    }

    # Add optional hints for better accuracy
    if "language" in hints:
        data["language"] = hints["language"]  # 0=default, 1=Cyrillic, 2=Latin
    if "textinstructions" in hints:
        data["textinstructions"] = hints["textinstructions"]
    if "numeric" in hints:
        data["numeric"] = hints["numeric"]  # 1=digits only, 2=letters only
    if "min_len" in hints:
        data["min_len"] = hints["min_len"]
    if "max_len" in hints:
        data["max_len"] = hints["max_len"]

    resp = requests.post("https://ocr.captchaai.com/in.php", data=data, timeout=30)
    return resp.json()


# Example: Digits-only CAPTCHA, 4-6 characters
result = solve_image(
    "YOUR_API_KEY",
    encoded_image,
    numeric=1,
    min_len=4,
    max_len=6,
)

# Example: Case-sensitive text
result = solve_image(
    "YOUR_API_KEY",
    encoded_image,
    textinstructions="Case-sensitive, enter exactly as shown",
)

Исправление 5. Снимайте сам элемент CAPTCHA, а не всю страницу

Скриншот всей страницы с последующей ручной обрезкой координатами — источник большинства «съехавших» кадров: после редизайна формы координаты уже не совпадают с полем. Селектор элемента снимает ровно нужную область независимо от вёрстки вокруг.

from selenium import webdriver
from selenium.webdriver.common.by import By
import base64


def capture_captcha_element(driver, selector):
    """Screenshot only the CAPTCHA element, not the full page."""
    element = driver.find_element(By.CSS_SELECTOR, selector)

    # Element screenshot (better than page crop)
    png_bytes = element.screenshot_as_png

    # Verify it's not empty
    if len(png_bytes) < 500:
        raise ValueError("Screenshot too small — element may not be visible")

    return base64.b64encode(png_bytes).decode()


# Usage
driver = webdriver.Chrome()
driver.get("https://example.com")
image_b64 = capture_captcha_element(driver, "img#captchaImage")

Для команд, тестирующих формы на CI-раннере в европейском дата-центре (частый выбор для RU-инфраструктуры), тот же принцип работает и в headless-режиме — просто убедитесь, что элемент действительно отрисовался, прежде чем снимать его.


Исправление 6. Свежий кадр для динамических и ротируемых капч

Динамические CAPTCHA перевыпускают изображение через несколько секунд — если между снимком и отправкой прошла пауза (например, скрипт сначала логирует, потом шлёт запрос), CaptchaAI честно распознает уже устаревший кадр, а сайт такой ответ не примет: капча к этому моменту сменилась.

import time


def solve_with_fresh_image(driver, api_key, captcha_selector):
    """Capture and solve CAPTCHA immediately to avoid expiry."""
    # Wait for CAPTCHA to load fully
    time.sleep(2)

    # Capture fresh
    element = driver.find_element(By.CSS_SELECTOR, captcha_selector)
    png_bytes = element.screenshot_as_png
    body = base64.b64encode(png_bytes).decode()

    # Submit immediately
    resp = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": api_key,
        "method": "base64",
        "body": body,
        "json": 1,
    }, timeout=30)
    result = resp.json()

    if result.get("status") != 1:
        raise RuntimeError(result.get("request"))

    task_id = result["request"]

    # Poll — image CAPTCHAs solve fast
    time.sleep(5)
    for _ in range(12):
        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(3)

    raise TimeoutError("Image solve timeout")

Частые вопросы

Ниже — вопросы, которые чаще всего задают разработчики после того, как самопроверка выше не дала однозначного ответа.

Вопрос Ответ
Что означает параметр language: 0, 1, 2? Это подсказка по алфавиту ожидаемого ответа: 0 — по умолчанию, 1 — кириллица, 2 — латиница. Для форм на русскоязычных сайтах, где текст на картинке кириллический, явная передача language=1 снижает долю ответов, где сервис принял букву за похожую латинскую.
Нужно ли предварительно обрабатывать изображение перед отправкой? Только если исходный кадр низкого качества. Стандартные CAPTCHA-изображения CaptchaAI обрабатывает и без предобработки. Апскейл совсем маленьких картинок и повышение контраста помогают в пограничных случаях — мелкий шрифт, слабый JPEG-артефакт, полупрозрачный фон.
Как ограничить длину ответа, если капча всегда из 4–6 символов? Передайте min_len и max_len в запросе. Это не меняет само распознавание, но отсекает варианты неверной длины и помогает быстрее заметить, что проблема не в длине, а в чём-то другом — например, в кодировке.
PNG или JPEG — какой формат лучше для CAPTCHA? PNG предпочтительнее: он без потерь и не размывает мелкий текст, как это иногда делает агрессивное сжатие JPEG. Если источник — уже готовый JPEG с сайта, не пересжимайте его повторно при перекодировании в base64.
Можно ли сообщить о неправильном ответе через reportbad? Да, используйте конечную точку reportbad с ID задачи. Это не только помогает статистике точности, но и в части случаев возвращает средства на баланс за конкретную ошибочную задачу.

Похожие материалы


Решайте изображения точно — попробуйте CaptchaAI.

Комментарии для этой статьи отключены.