Hướng Dẫn API

Bash Script + cURL + CaptchaAI: Tự động hóa Shell CAPTCHA

Cron job của bạn chạy lúc 3 giờ sáng, gặp reCAPTCHA, và dừng lại — không có Python trên server, không tiện SSH vào debug ngay. Tình huống này quen thuộc với các đội QA/outsourcing tại Việt Nam vận hành nhiều VPS Linux tối giản: Bash và cURL luôn có sẵn, không cần cài thêm runtime để gọi API CaptchaAI.

Bài này hướng dẫn giải reCAPTCHA v2/v3, Cloudflare Turnstile và CAPTCHA hình ảnh bằng Bash thuần cộng cURL — không cần Python hay Node.js. Quy trình chỉ có 4 bước, lặp lại cho mọi loại CAPTCHA:

  1. Gửi task tới in.php kèm sitekey và URL trang
  2. Nhận về task ID
  3. Polling res.php cho tới khi có kết quả
  4. Dùng token nhận được để gửi form hoặc tiếp tục workflow

Vì sao chọn Bash + cURL để giải CAPTCHA

Yếu tố Vì sao quan trọng
Không phụ thuộc Bash và cURL có sẵn trên mọi hệ Linux/macOS, không cần cài thêm gì
Nhẹ Không runtime, không package manager, không bước setup
Hợp với cron Lên lịch tác vụ liên quan CAPTCHA bằng cron chuẩn, không cần daemon riêng
Chạy tốt trong CI/CD Docker, GitHub Actions, Jenkins, GitLab CI đều hỗ trợ sẵn
Ghép pipeline dễ dàng Nối kết quả giải CAPTCHA với jq, grep, awk và các lệnh Unix khác

Chuẩn bị trước khi bắt đầu

  1. Bash 4.0 trở lên
  2. cURL (có sẵn mặc định trên Linux/macOS)
  3. jq để đọc JSON: apt install jq hoặc brew install jq
  4. API key CaptchaAI (lấy tại đây)

Hai hàm nền tảng dùng chung: gửi task và polling

Gửi task lên CaptchaAI

#!/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"
}

Polling kết quả từ res.php

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
}

Giải từng loại CAPTCHA bằng Bash

reCAPTCHA v2

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://staging.example.com/qa-login" \
    "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-")

echo "Token: ${TOKEN:0:50}..."

Cloudflare Turnstile

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...")

reCAPTCHA v3

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"
}

CAPTCHA hình ảnh (OCR)

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"

Đóng gói thành thư viện dùng lại: captchaai.sh

Lưu dưới dạng captchaai.sh và source từ các script khác thay vì lặp lại code ở trên trong từng job:

#!/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 "sitekey=$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"
}

Dùng thư viện captchaai.sh

#!/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://staging.example.com/qa-login" \
    "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-")

echo "Token: ${TOKEN:0:50}..."

Vận hành trong thực tế: song song, retry, cron, Docker

Gửi form bằng token đã giải

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://staging.example.com/qa-login" "SITEKEY")

RESPONSE=$(submit_form_with_token "https://staging.example.com/qa-login" \
    "$TOKEN" \
    -d "[email protected]" \
    -d "password=password")

echo "Response: $RESPONSE"

Giải song song bằng background job

Chạy từng lần giải trong subshell nền rồi gom kết quả qua file tạm:

#!/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"

Retry tự động: backoff theo cấp số nhân

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
}

Chạy tự động bằng cron job

# Edit crontab: crontab -e
# Run daily at 8 AM
0 8 * * * /path/to/captcha-automation.sh >> /var/log/captcha.log 2>&1

Ví dụ thực tế: một số đội QA Việt Nam ghép cron với script này để theo dõi giá công khai trên các sàn thương mại điện tử mỗi đêm, chạy trên VPS Linux giá rẻ.

Ví dụ script cron hoàn chỉnh

#!/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

Đóng gói bằng Docker

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"]

Khắc phục sự cố

Lỗi Nguyên nhân Cách xử lý
ERROR_WRONG_USER_KEY API key không hợp lệ Xác minh key tại dashboard
ERROR_ZERO_BALANCE Hết số dư Nạp tiền vào tài khoản
curl: (60) SSL certificate Thiếu CA bundle Thêm --cacert /path/to/ca-bundle.crt, hoặc -k khi test
jq: command not found Chưa cài jq apt install jq hoặc brew install jq
base64: invalid option -- 'w' Cú pháp base64 của macOS Dùng base64 file thay vì base64 -w 0 file
Phản hồi trống Sự cố mạng Thêm cờ -v vào curl để debug

Câu hỏi thường gặp

Bash script này có bắt buộc phải cài jq không?

Không tuyệt đối, nhưng nên dùng — jq đọc JSON đáng tin cậy hơn grep/sed thủ công.

Có chạy được trên WSL hoặc Git Bash trên Windows không?

Có. WSL2 chạy Bash và cURL y hệt Linux, chỉ cần apt install jq một lần. Git Bash cũng chạy được, nhưng phải cài jq riêng.

Nên polling trong một lần chạy, hay để cron gọi lại nhiều lần?

Polling trong cùng lần chạy phù hợp khi cần token ngay. Với job hàng loạt, tách gửi task và lấy kết quả thành hai cron job riêng sẽ nhẹ tài nguyên hơn.

Có giải nhiều CAPTCHA song song trong cùng một script cron được không?

Được — xem phần giải song song ở trên. Số lượt chạy đồng thời tối đa phụ thuộc số thread của gói CaptchaAI, ví dụ BASIC ($15/tháng, 5 thread) cho phép 5 lượt cùng lúc.

Lưu API key an toàn thế nào trong script?

Dùng biến môi trường export CAPTCHAAI_KEY="...", hoặc file .env không commit vào git.


Hướng dẫn liên quan


Giải CAPTCHA ngay từ dòng lệnh — lấy API key của bạn và tự động hóa bằng Bash.

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