So Sánh

Giải quyết CAPTCHA song song và tuần tự: Đánh đổi hiệu suất

Dưới 500 CAPTCHA/ngày, giải tuần tự là đủ và code đơn giản hơn nhiều; vượt ngưỡng đó, song song tăng thông lượng gấp hàng chục lần, đổi lại là lỗi đồng thời và debug khó hơn. Bài này so sánh trực tiếp hai cách với CaptchaAI, kèm code Python và JavaScript chạy được ngay.

Script tuần tự đang mất vài phút để xử lý 100 CAPTCHA? Phần dưới đây chỉ ra chỗ nên chuyển sang song song — và chỗ chưa cần.

Chọn nhanh: tuần tự hay song song

  1. Dưới 500 giải/ngày, hoặc đang debug → chọn tuần tự.
  2. Trên 500 giải/ngày, pipeline đã ổn định → chuyển sang song song.
  3. Kết quả phải dùng đúng thứ tự phát sinh → tuần tự, hoặc song song kèm index tracking.
  4. Container nhỏ, ít RAM (dưới 256 MB) → tuần tự, hoặc giới hạn max_concurrent thấp.

Giải tuần tự: khi mã đơn giản quan trọng hơn tốc độ

Cách hoạt động

Từng CAPTCHA một: gửi task, đợi, polling res.php đến khi có kết quả, rồi mới sang task tiếp theo. Đoạn code dưới gửi 10 task tuần tự — hết khoảng 150 giây vì mỗi lượt phải chờ lượt trước hoàn tất.

# sequential_solver.py
import os
import time
import requests

API_KEY = os.environ.get("CAPTCHAAI_KEY", "YOUR_API_KEY")

def solve_sequential(tasks):
    """Solve CAPTCHAs one by one."""
    results = []
    session = requests.Session()

    for task in tasks:
        # Submit
        resp = session.get("https://ocr.captchaai.com/in.php", params={
            "key": API_KEY,
            "method": "userrecaptcha",
            "googlekey": task["sitekey"],
            "pageurl": task["pageurl"],
            "json": "1",
        })
        result = resp.json()
        if result.get("status") != 1:
            results.append({"error": result.get("request")})
            continue

        task_id = result["request"]
        time.sleep(15)

        # Poll
        token = None
        for _ in range(25):
            poll = session.get("https://ocr.captchaai.com/res.php", params={
                "key": API_KEY, "action": "get",
                "id": task_id, "json": "1",
            })
            poll_result = poll.json()
            if poll_result.get("status") == 1:
                token = poll_result["request"]
                break
            if poll_result.get("request") != "CAPCHA_NOT_READY":
                break
            time.sleep(5)

        results.append({"token": token} if token else {"error": "timeout"})

    return results

# 10 tasks sequentially → ~150 seconds total
tasks = [{"sitekey": "SITEKEY", "pageurl": "https://example.com"}] * 10
start = time.time()
results = solve_sequential(tasks)
print(f"Completed in {time.time() - start:.0f}s")

Ưu điểm

Log lỗi rõ ràng, dễ trace; không cần race condition hay cô lập trạng thái; thứ tự kết quả tự động đúng.

Giải song song: tăng thông lượng bằng asyncio và Promise.all

Cách hoạt động với Python

Gửi toàn bộ CAPTCHA gần như cùng lúc, polling đồng thời qua asyncio.gather. Semaphore là van an toàn — giới hạn số task chạy song song, tránh tràn API và bị trả lỗi.

# parallel_solver.py
import os
import asyncio
import aiohttp

API_KEY = os.environ.get("CAPTCHAAI_KEY", "YOUR_API_KEY")

async def solve_one(session, sitekey, pageurl, semaphore):
    """Solve a single CAPTCHA within concurrency limits."""
    async with semaphore:
        # Submit
        async with session.get("https://ocr.captchaai.com/in.php", params={
            "key": API_KEY, "method": "userrecaptcha",
            "googlekey": sitekey, "pageurl": pageurl, "json": "1",
        }) as resp:
            result = await resp.json(content_type=None)

        if result.get("status") != 1:
            return {"error": result.get("request")}

        task_id = result["request"]
        await asyncio.sleep(15)

        # Poll
        for _ in range(25):
            async with session.get("https://ocr.captchaai.com/res.php", params={
                "key": API_KEY, "action": "get",
                "id": task_id, "json": "1",
            }) as resp:
                poll_result = await resp.json(content_type=None)

            if poll_result.get("status") == 1:
                return {"token": poll_result["request"]}
            if poll_result.get("request") != "CAPCHA_NOT_READY":
                return {"error": poll_result.get("request")}

            await asyncio.sleep(5)

        return {"error": "timeout"}

async def solve_parallel(tasks, max_concurrent=50):
    """Solve CAPTCHAs in parallel with concurrency control."""
    semaphore = asyncio.Semaphore(max_concurrent)
    connector = aiohttp.TCPConnector(limit=max_concurrent)

    async with aiohttp.ClientSession(connector=connector) as session:
        coros = [
            solve_one(session, t["sitekey"], t["pageurl"], semaphore)
            for t in tasks
        ]
        return await asyncio.gather(*coros)

# 10 tasks in parallel → ~20 seconds total
import time
tasks = [{"sitekey": "SITEKEY", "pageurl": "https://example.com"}] * 10
start = time.time()
results = asyncio.run(solve_parallel(tasks))
print(f"Completed in {time.time() - start:.0f}s")

Ví dụ JavaScript

Cùng logic, viết bằng axiosPromise.all — phù hợp nếu stack của bạn là Node.js thay vì Python.

// parallel_solver.js
const axios = require('axios');
const https = require('https');

const API_KEY = process.env.CAPTCHAAI_KEY || 'YOUR_API_KEY';
const agent = new https.Agent({ keepAlive: true, maxSockets: 50 });
const api = axios.create({ baseURL: 'https://ocr.captchaai.com', httpsAgent: agent });

async function solveOne(sitekey, pageurl) {
  const submit = await api.get('/in.php', {
    params: { key: API_KEY, method: 'userrecaptcha', googlekey: sitekey, pageurl, json: '1' },
  });
  if (submit.data.status !== 1) return { error: submit.data.request };

  await new Promise(r => setTimeout(r, 15000));

  for (let i = 0; i < 25; i++) {
    const poll = await api.get('/res.php', {
      params: { key: API_KEY, action: 'get', id: submit.data.request, json: '1' },
    });
    if (poll.data.status === 1) return { token: poll.data.request };
    if (poll.data.request !== 'CAPCHA_NOT_READY') return { error: poll.data.request };
    await new Promise(r => setTimeout(r, 5000));
  }
  return { error: 'timeout' };
}

(async () => {
  const tasks = Array.from({ length: 10 }, () => ({
    sitekey: 'SITEKEY', pageurl: 'https://example.com',
  }));

  const start = Date.now();
  const results = await Promise.all(tasks.map(t => solveOne(t.sitekey, t.pageurl)));
  console.log(`Completed in ${((Date.now() - start) / 1000).toFixed(0)}s`);
  console.log(`Solved: ${results.filter(r => r.token).length}/${tasks.length}`);

  agent.destroy();
})();

Ví dụ thực tế: kết hợp cả hai cách trong một pipeline

Không cần chọn hẳn một phe: giữ luồng nghiệp vụ tuần tự nhưng gom riêng phần giải CAPTCHA thành batch và chạy song song.

Một đội thu thập giá sản phẩm trên Shopee và Tiki cho một công ty outsource ở Hà Nội xử lý khoảng 2.000 URL mỗi đêm. Trích sitekey vẫn chạy tuần tự vì dễ log lỗi theo URL; giải CAPTCHA thì gom batch 20 tác vụ chạy song song — rút thời gian xử lý cả lô từ 8 giờ xuống dưới 1 giờ.

# Process 10 URLs sequentially, but solve their CAPTCHAs in a parallel batch
urls = get_next_batch()  # 10 URLs
captcha_params = [extract_sitekey(url) for url in urls]  # Sequential extraction
tokens = asyncio.run(solve_parallel(captcha_params, max_concurrent=10))  # Parallel solving
for url, result in zip(urls, tokens):
    submit_form(url, result.get("token"))  # Sequential submission

Thông lượng theo mức đồng thời

Số lượng giải đồng thời Thông lượng ước tính/giờ Bộ nhớ (Python) Độ phức tạp
1 (tuần tự) 240 30 MB Thấp
10 2.400 50 MB Thấp
25 6.000 80 MB Trung bình
50 10.000+ 120 MB Trung bình
100 18.000+ 200 MB Cao

Dựa trên reCAPTCHA v2 với thời gian giải trung bình 15 giây.

Khi nào nên chuyển hẳn sang song song

  1. Thông lượng hiện tại không theo kịp khối lượng phát sinh mỗi giờ.
  2. Máy chủ đủ RAM — mỗi 50 tác vụ đồng thời cần thêm khoảng 120 MB.
  3. Bạn sẵn sàng thêm cô lập lỗi và theo dõi thứ tự nếu logic cần.
  4. Chi phí API không đổi vì CaptchaAI tính theo thread, không theo request đồng thời.

Xử lý sự cố thường gặp

Bốn lỗi hay gặp nhất khi chuyển sang song song:

  • Chậm hơn dự kiến — semaphore giới hạn quá chặt; tăng max_concurrent.
  • Lỗi ngẫu nhiên chỉ khi chạy song song — trạng thái dùng chung bị ghi đè; cô lập trạng thái riêng cho từng coroutine/promise.
  • Kết quả sai thứ tự — hiếm gặp vì asyncio.gather giữ đúng thứ tự; nếu cần, dùng index tracking.
  • ERROR_NO_SLOT_AVAILABLE — gửi quá nhiều task cùng lúc; thêm độ trễ nhỏ hoặc giảm max_concurrent.

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

Bốn câu hay gặp khi cân nhắc chuyển sang song song.

Chuyển từ tuần tự sang song song có phải viết lại toàn bộ code không?

Không, nếu logic giải đã được tách thành một hàm độc lập — chỉ cần gói lời gọi trong asyncio.gather (Python) hoặc Promise.all (JavaScript).

Chạy 50 tác vụ song song cần bao nhiêu RAM?

Khoảng 120 MB với Python. Container nhỏ (dưới 256 MB) nên giới hạn max_concurrent ở 25.

Giải song song có cần nâng lên gói nhiều thread hơn không?

Có liên quan nhưng không bắt buộc. CaptchaAI tính phí theo thread, giải không giới hạn trên mỗi thread — số thread trong gói quyết định mức đồng thời tối đa, không quyết định chi phí mỗi lần giải. Gói BASIC ($15/tháng, 5 thread) vẫn chạy song song được, chỉ giới hạn 5 tác vụ cùng lúc.

Đồng thời thực tế nên dừng ở mức nào?

Phần lớn hệ thống đạt lợi nhuận giảm dần sau khoảng 100 tác vụ đồng thời — nên tối ưu tham số giải thay vì tăng thêm concurrency.

Bước tiếp theo

Chọn chiến lược đúng với khối lượng thực tế của bạn — bắt đầu tuần tự nếu đang debug, chuyển sang song song khi vượt 500 giải/ngày. Lấy API key CaptchaAI và áp dụng ngay ví dụ Python hoặc JavaScript ở trên.

  1. Giải CAPTCHA song song ở quy mô lớn
  2. Xử lý 10.000 tác vụ mỗi giờ
  3. Tối ưu bộ nhớ và CPU cho worker giải CAPTCHA
Os comentários estão desativados para este artigo.