Use Cases

Resolvendo CAPTCHAs em sites japoneses e coreanos

Envie a imagem para a CaptchaAI com language=2 e o texto volta em hiragana, katakana, kanji ou hangul, sem treinar modelo nenhum. O que trava a integração raramente é o CAPTCHA — é a pilha em volta dele: um OCR calibrado para o alfabeto latino, uma resposta decodificada como Latin-1, um formulário que recusa o texto certo porque ele chegou como interrogações.

Se você mantém coleta autorizada, testes de integração ou monitoramento nesses dois mercados, o roteiro abaixo cobre os tipos de desafio, o código em Python e Node.js e os erros de codificação que consomem a maior parte da depuração.

Por que o OCR latino falha com hiragana, katakana e hangul

Um reconhecedor treinado em 26 letras conta com espaço de busca pequeno e formas bem separadas. As escritas japonesa e coreana quebram as duas premissas de uma vez.

Sistema de escrita Caracteres Efeito no OCR
Hiragana (ひらがな) 46 sinais básicos traços curvos muito parecidos entre si
Katakana (カタカナ) 46 sinais básicos formas retas que se confundem com o hiragana
Kanji (漢字) milhares de ideogramas subconjunto comum, mas denso em traços
Hangul (한글) 24 letras, cerca de 11.000 blocos blocos silábicos, não sequência linear
Misto (JP) os três mais latim quatro conjuntos numa única imagem

Some o ruído clássico de CAPTCHA — distorção, linhas cruzadas, fundo texturizado — e fica claro por que um OCR local com pacote de idioma padrão devolve resultado inutilizável. A CaptchaAI trata esses casos pelo mesmo endpoint de imagem/OCR dos desafios em latim; a diferença está em language=2, que aciona o reconhecimento CJK e cobre o script misto.

Tipos de desafio que aparecem no Japão e na Coreia

Nem tudo nesses portais é CAPTCHA de imagem — e parte dos tipos não está no catálogo da CaptchaAI.

Região O que aparece Caracteres Cobertura da CaptchaAI
Japão imagem com hiragana, katakana ou kanji hiragana, katakana, kanji, latim Imagem/OCR com language=2
Coreia imagem com hangul hangul, latim Imagem/OCR com language=2
Japão e Coreia reCAPTCHA v2 e v3 localizado baseado em token reCAPTCHA v2 e v3
Japão hCaptcha em parte dos portais ❌ não suportado
Coreia sliders proprietários locais ❌ fora dos tipos suportados

A interface localizada do reCAPTCHA não muda nada: o desafio continua baseado em token e o fluxo é idêntico ao de qualquer site. Já os widgets proprietários coreanos e o hCaptcha exigem outra decisão de produto — não os force pelo endpoint de imagem.

Python: enviar a imagem e consultar o resultado

São três etapas: ler ou baixar a imagem, enviar em base64 para in.php com language=2 e consultar res.php até o texto ficar pronto. A terceira função cobre o caso mais comum: a imagem está atrás de uma sessão autenticada.

import requests
import base64
import time

API_KEY = "YOUR_API_KEY"
SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"


def solve_japanese_captcha(image_path: str) -> str:
    """Solve a Japanese character image CAPTCHA."""
    with open(image_path, "rb") as f:
        image_b64 = base64.b64encode(f.read()).decode()

    resp = requests.post(SUBMIT_URL, data={
        "key": API_KEY,
        "method": "base64",
        "body": image_b64,
        "language": 2,          # CJK character support
        "json": 1,
    }, timeout=30).json()

    if resp.get("status") != 1:
        raise RuntimeError(f"Submit: {resp.get('request')}")

    task_id = resp["request"]
    for _ in range(24):
        time.sleep(5)
        poll = requests.get(RESULT_URL, params={
            "key": API_KEY, "action": "get", "id": task_id, "json": 1,
        }, timeout=15).json()

        if poll.get("request") == "CAPCHA_NOT_READY":
            continue
        if poll.get("status") == 1:
            return poll["request"]
        raise RuntimeError(f"Solve: {poll.get('request')}")

    raise RuntimeError("Timeout")


def solve_korean_captcha(image_path: str) -> str:
    """Solve a Korean hangul image CAPTCHA."""
    with open(image_path, "rb") as f:
        image_b64 = base64.b64encode(f.read()).decode()

    resp = requests.post(SUBMIT_URL, data={
        "key": API_KEY,
        "method": "base64",
        "body": image_b64,
        "language": 2,
        "json": 1,
    }, timeout=30).json()

    if resp.get("status") != 1:
        raise RuntimeError(f"Submit: {resp.get('request')}")

    task_id = resp["request"]
    for _ in range(24):
        time.sleep(5)
        poll = requests.get(RESULT_URL, params={
            "key": API_KEY, "action": "get", "id": task_id, "json": 1,
        }, timeout=15).json()

        if poll.get("request") == "CAPCHA_NOT_READY":
            continue
        if poll.get("status") == 1:
            return poll["request"]
        raise RuntimeError(f"Solve: {poll.get('request')}")

    raise RuntimeError("Timeout")


def solve_captcha_from_session(session: requests.Session,
                                captcha_url: str,
                                language: int = 2) -> str:
    """Download and solve a CAPTCHA within a session context."""
    resp = session.get(captcha_url, timeout=15)
    image_b64 = base64.b64encode(resp.content).decode()

    submit = requests.post(SUBMIT_URL, data={
        "key": API_KEY,
        "method": "base64",
        "body": image_b64,
        "language": str(language),
        "json": 1,
    }, timeout=30).json()

    if submit.get("status") != 1:
        raise RuntimeError(f"Submit: {submit.get('request')}")

    task_id = submit["request"]
    for _ in range(24):
        time.sleep(5)
        poll = requests.get(RESULT_URL, params={
            "key": API_KEY, "action": "get", "id": task_id, "json": 1,
        }, timeout=15).json()

        if poll.get("request") == "CAPCHA_NOT_READY":
            continue
        if poll.get("status") == 1:
            return poll["request"]
        raise RuntimeError(f"Solve: {poll.get('request')}")

    raise RuntimeError("Timeout")


# --- Usage ---

# Japanese CAPTCHA
jp_text = solve_japanese_captcha("japanese_captcha.png")
print(f"Japanese CAPTCHA: {jp_text}")

# Korean CAPTCHA from a live session
session = requests.Session()
session.headers["Accept-Language"] = "ko-KR,ko;q=0.9"
session.get("https://example.kr/login")  # establish session
kr_text = solve_captcha_from_session(session, "https://example.kr/captcha/image")
print(f"Korean CAPTCHA: {kr_text}")

Repare no Accept-Language da sessão coreana: muitos portais servem a imagem no idioma negociado pelo cabeçalho, e pedir a página em inglês esperando hangul é fonte silenciosa de erro.

Node.js: o mesmo fluxo com fetch

Em Node.js o envio é o mesmo POST com URLSearchParams e a consulta, um GET em intervalo fixo. A segunda função baixa a imagem da URL e repassa cookies quando o desafio está atrás de sessão.

const API_KEY = "YOUR_API_KEY";
const SUBMIT_URL = "https://ocr.captchaai.com/in.php";
const RESULT_URL = "https://ocr.captchaai.com/res.php";
const fs = require("fs");

async function solveAsianCaptcha(imagePath) {
  const imageB64 = fs.readFileSync(imagePath, "base64");

  const body = new URLSearchParams({
    key: API_KEY,
    method: "base64",
    body: imageB64,
    language: "2",
    json: "1",
  });

  const resp = await (await fetch(SUBMIT_URL, { method: "POST", body })).json();
  if (resp.status !== 1) throw new Error(`Submit: ${resp.request}`);

  const taskId = resp.request;
  for (let i = 0; i < 24; i++) {
    await new Promise((r) => setTimeout(r, 5000));
    const url = `${RESULT_URL}?key=${API_KEY}&action=get&id=${taskId}&json=1`;
    const poll = await (await fetch(url)).json();
    if (poll.request === "CAPCHA_NOT_READY") continue;
    if (poll.status === 1) return poll.request;
    throw new Error(`Solve: ${poll.request}`);
  }
  throw new Error("Timeout");
}

async function solveFromUrl(captchaUrl, cookies = "") {
  const resp = await fetch(captchaUrl, {
    headers: { Cookie: cookies, "Accept-Language": "ja-JP,ja;q=0.9" },
  });
  const buffer = await resp.arrayBuffer();
  const imageB64 = Buffer.from(buffer).toString("base64");

  const body = new URLSearchParams({
    key: API_KEY, method: "base64", body: imageB64,
    language: "2", json: "1",
  });

  const submitResp = await (await fetch(SUBMIT_URL, { method: "POST", body })).json();
  if (submitResp.status !== 1) throw new Error(`Submit: ${submitResp.request}`);

  const taskId = submitResp.request;
  for (let i = 0; i < 24; i++) {
    await new Promise((r) => setTimeout(r, 5000));
    const url = `${RESULT_URL}?key=${API_KEY}&action=get&id=${taskId}&json=1`;
    const poll = await (await fetch(url)).json();
    if (poll.request === "CAPCHA_NOT_READY") continue;
    if (poll.status === 1) return poll.request;
    throw new Error(`Solve: ${poll.request}`);
  }
  throw new Error("Timeout");
}

// Usage
const jpText = await solveAsianCaptcha("japanese_captcha.png");
console.log(`Japanese: ${jpText}`);

Cenário: equipe em São Paulo monitorando portais em JST

Um caso do nosso lado do mundo: uma equipe de QA em São Paulo roda verificações diárias em ambientes de parceiros japoneses e coreanos, na janela comercial de Tóquio (JST, doze horas à frente de Brasília), com a pilha em sa-east-1. O round-trip até a Ásia já pesa antes de qualquer CAPTCHA.

Três decisões que fazem diferença nesse arranjo:

  1. Separe tempo de rede de tempo de resolução. Meça o RTT e o tempo de resolução em métricas distintas; sem isso, uma rota transpacífica lenta vira "o solucionador está lento" no relatório.
  2. Dimensione por threads, não por volume. A cobrança é por thread concorrente, com resoluções ilimitadas por thread no mês: uma bateria noturna com pouco paralelismo cabe no BASIC (US$ 15/mês, 5 threads), dezenas de verificações simultâneas pedem ADVANCE (US$ 90/mês, 50 threads).
  3. Registre o texto em UTF-8 desde o primeiro log. Se o pipeline achata tudo para ASCII, some a evidência para auditar um erro de reconhecimento — e, em coleta sujeita à LGPD, um log ilegível atrapalha a demonstração do que foi coletado. Opere sempre em ambiente próprio, em staging ou com autorização do portal.

Erros comuns e como corrigir

Sintoma Causa provável Correção
Hiragana devolvido como katakana formas próximas (por exemplo, り e リ) confirme language=2 no envio; imagens maiores reduzem a ambiguidade
Hangul vira interrogações resposta decodificada como Latin-1 force UTF-8 na resposta (response.encoding = 'utf-8') e no console
Scripts misturados falham hiragana, kanji e latim juntos language=2 já cobre script misto; não fatie a imagem
Precisão baixa em texto estilizado distorção forte ou fundo texturizado capture na resolução original, sem recomprimir
Formulário recusa o texto correto bytes já escapados no campo envie a string UTF-8 crua e deixe a biblioteca HTTP codificar

Perguntas frequentes

E o hCaptcha que aparece em parte dos portais japoneses?

Não está entre os tipos suportados, assim como o FunCaptcha (Arkose Labs). A CaptchaAI cobre reCAPTCHA v2 e v3, Turnstile e Cloudflare Challenge, GeeTest v3, imagem/OCR, grade de imagens e BLS.

Preciso de um servidor no Japão ou na Coreia para resolver esses CAPTCHAs?

Não. A resolução acontece na API da CaptchaAI e independe de onde seu código roda: você envia a imagem em base64 e recebe o texto. A região da sua infraestrutura afeta o carregamento da página do portal, não o reconhecimento dos caracteres.

Quanto custa manter um volume alto de desafios CJK?

O plano é dimensionado por threads concorrentes, com resoluções ilimitadas por thread no mês. Valide no BASIC (US$ 15/mês, 5 threads) e suba conforme o paralelismo real: STANDARD (US$ 30/mês, 15 threads), ADVANCE (US$ 90/mês, 50 threads). Não há taxa por CAPTCHA nem sobretaxa por tipo.

Por que o texto reconhecido aparece como interrogações no meu terminal?

Quase nunca é erro de reconhecimento: é o console ou o pipeline de logs sem UTF-8. Salve a resposta em arquivo e inspecione os bytes. No Windows, chcp 65001 costuma resolver a exibição.

Artigos relacionados

Próximos passos

Comece pelo caso mais simples: uma imagem em disco, language=2 no envio, o texto impresso em UTF-8. Depois, é só encaixar o fluxo na sessão autenticada. Pegue sua chave de API da CaptchaAI e resolva o primeiro desafio CJK hoje.

Guias relacionados:

Os comentários estão desativados para este artigo.