Tích Hợp

Smartproxy + CaptchaAI: Thiết lập proxy dân dụng để giải CAPTCHA

CaptchaAI giải CAPTCHA từ hạ tầng riêng của nó, còn trang bạn kiểm thử chỉ thấy IP mà crawler đi ra: hai đường mạng tách biệt. Quy tắc rút ra: từ lúc tải trang đến lúc submit form, mọi request phải đi qua cùng một IP. Bài này dựng cấu hình Smartproxy + CaptchaAI theo quy tắc đó bằng Python, Selenium và Node.js.


Chọn loại IP trước khi viết dòng code đầu tiên

Lựa chọn này chi phối tần suất gặp CAPTCHA, chi phí băng thông và tuổi thọ phiên. Với pipeline QA và giám sát giá, cổng dân dụng là mặc định hợp lý.

Loại IP Quy mô pool Phù hợp với Tần suất gặp CAPTCHA
Dân dụng 55 triệu+ IP Scraping diện rộng Thấp
Datacenter 100 nghìn+ IP Tốc độ cao, chi phí thấp Trung bình – cao
Di động 10 triệu+ IP Trang tối ưu mobile Rất thấp
ISP IP tĩnh cấp dân dụng Phiên sống lâu Thấp

Vòng đời một token đi qua bốn mốc

Mốc 1 và mốc 4 bắt buộc dùng chung một IP; hai mốc giữa thì không:

  1. Tải trang qua gateway Smartproxy, trích sitekey từ HTML hoặc DOM.
  2. Gửi task tới in.php kèm pageurlgooglekey (reCAPTCHA v2) hoặc sitekey (Turnstile).
  3. polling res.php mỗi 5 giây cho tới khi có token.
  4. Submit form kèm g-recaptcha-response (hoặc cf-turnstile-response) qua IP ở mốc 1.

Bước 1: dựng gateway Smartproxy và hàm giải CAPTCHA

Một file Python gom hai lớp: dict proxies cho requests, và hàm solve_captcha dùng chung cho reCAPTCHA v2 và Turnstile.

import requests
import time

SMARTPROXY_USER = "spuser"
SMARTPROXY_PASS = "sppassword"
SMARTPROXY_HOST = "gate.smartproxy.com"
SMARTPROXY_PORT = 10001

CAPTCHAAI_KEY = "YOUR_API_KEY"
CAPTCHAAI_URL = "https://ocr.captchaai.com"

proxies = {
    "http": f"http://{SMARTPROXY_USER}:{SMARTPROXY_PASS}@{SMARTPROXY_HOST}:{SMARTPROXY_PORT}",
    "https": f"http://{SMARTPROXY_USER}:{SMARTPROXY_PASS}@{SMARTPROXY_HOST}:{SMARTPROXY_PORT}",
}

def fetch_page(url):
    return requests.get(url, proxies=proxies, timeout=30)

def solve_captcha(site_url, sitekey, captcha_type="recaptcha_v2"):
    submit_data = {
        "key": CAPTCHAAI_KEY,
        "pageurl": site_url,
        "json": 1,
    }

    if captcha_type == "turnstile":
        submit_data["method"] = "turnstile"
        submit_data["sitekey"] = sitekey
    else:
        submit_data["method"] = "userrecaptcha"
        submit_data["googlekey"] = sitekey

    resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data=submit_data)
    data = resp.json()
    if data["status"] != 1:
        raise Exception(f"Submit failed: {data['request']}")

    task_id = data["request"]

    for _ in range(60):
        time.sleep(5)
        resp = requests.get(f"{CAPTCHAAI_URL}/res.php", params={
            "key": CAPTCHAAI_KEY,
            "action": "get",
            "id": task_id,
            "json": 1,
        })
        data = resp.json()
        if data["request"] == "CAPCHA_NOT_READY":
            continue
        if data["status"] == 1:
            return data["request"]
        raise Exception(f"Solve: {data['request']}")

    raise TimeoutError("Timeout")

Vòng polling chạy tối đa 60 lần, cách nhau 5 giây — trần chờ khoảng 5 phút. Mã lỗi khác CAPCHA_NOT_READY thì ném exception ngay để khỏi kẹt thread.


Bước 2: khóa IP bằng phiên cố định 10 phút

Mặc định gateway đổi IP theo từng request — nguyên nhân số một khiến token hợp lệ bị form từ chối. Gắn định danh phiên vào username để giữ nguyên IP.

import random
import string

def get_sticky_proxy(session_duration_minutes=10):
    """Create a sticky session proxy (same IP for duration)."""
    session_id = "".join(random.choices(string.ascii_lowercase + string.digits, k=8))

    proxy_url = (
        f"http://{SMARTPROXY_USER}"
        f"-session-{session_id}"
        f"-sessionduration-{session_duration_minutes}"
        f":{SMARTPROXY_PASS}@{SMARTPROXY_HOST}:{SMARTPROXY_PORT}"
    )

    return {"http": proxy_url, "https": proxy_url}

# Use same IP for entire CAPTCHA workflow
sticky = get_sticky_proxy(session_duration_minutes=10)

# Page load
resp = requests.get("https://staging.example.com/qa-form", proxies=sticky)

# Solve CAPTCHA
token = solve_captcha("https://staging.example.com/qa-form", "SITEKEY_HERE")

# Submit with same IP
resp = requests.post(
    "https://target.com/submit",
    data={"g-recaptcha-response": token},
    proxies=sticky,
)

Mười phút phủ trọn luồng tải trang → giải → submit của phần lớn biểu mẫu; form nhiều bước thì nâng session_duration_minutes lên 30 phút. Đừng dồn quá nhiều request vào một phiên.


Bước 3: gắn mã quốc gia vào username cho đúng thị trường

Cấu hình CAPTCHA thay đổi theo vị trí địa lý, nên IP thoát quyết định bạn thấy phiên bản nào của trang. Mã quốc gia nằm ngay trong username:

# Smartproxy country targeting via username
def get_country_proxy(country_code):
    proxy_url = (
        f"http://{SMARTPROXY_USER}"
        f"-country-{country_code}"
        f":{SMARTPROXY_PASS}@{SMARTPROXY_HOST}:{SMARTPROXY_PORT}"
    )
    return {"http": proxy_url, "https": proxy_url}

# US proxy
us_proxy = get_country_proxy("us")

# UK proxy
uk_proxy = get_country_proxy("gb")

# Germany proxy
de_proxy = get_country_proxy("de")

Đối chiếu danh sách quốc gia trong dashboard Smartproxy trước khi hardcode, và log mã quốc gia mỗi lần chạy.


Lấy sitekey từ DOM khi trang render bằng JavaScript

Với trang dựng bằng JavaScript, requests không thấy data-sitekey trong HTML thô. Hãy để trình duyệt tải trang qua đúng gateway ấy rồi đọc sitekey từ DOM.

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

def create_smartproxy_driver(country=None, sticky_session=None):
    proxy_user = SMARTPROXY_USER
    if country:
        proxy_user += f"-country-{country}"
    if sticky_session:
        proxy_user += f"-session-{sticky_session}"

    proxy_url = f"{proxy_user}:{SMARTPROXY_PASS}@{SMARTPROXY_HOST}:{SMARTPROXY_PORT}"

    options = webdriver.ChromeOptions()
    options.add_argument(f"--proxy-server=http://{SMARTPROXY_HOST}:{SMARTPROXY_PORT}")
    options.add_argument("")
    options.add_argument("--window-size=1920,1080")

    # For authenticated proxies, use seleniumwire or extension
    return webdriver.Chrome(options=options)

def scrape_with_captcha(url, country="us"):
    session_id = "".join(random.choices(string.ascii_lowercase, k=8))
    driver = create_smartproxy_driver(country=country, sticky_session=session_id)

    try:
        driver.get(url)
        time.sleep(3)

        sitekey = driver.execute_script(
            "return document.querySelector('[data-sitekey]')?.getAttribute('data-sitekey')"
        )

        if sitekey:
            token = solve_captcha(url, sitekey)
            driver.execute_script(f"""
                document.querySelector('#g-recaptcha-response').value = '{token}';
            """)
            driver.find_element(By.CSS_SELECTOR, "form").submit()
            time.sleep(3)

        return driver.page_source

    finally:
        driver.quit()

Chrome không nhận username/password trong --proxy-server: với proxy có xác thực, dùng Selenium Wire, nếu không driver sẽ trả 407 ở request đầu tiên.


Bản Node.js: một agent mạng, một cặp request giải CAPTCHA

Crawler Node.js giữ nguyên logic đó: một HttpsProxyAgent cho tầng mạng, một cặp request in.php / res.php cho tầng giải CAPTCHA.

const axios = require("axios");
const HttpsProxyAgent = require("https-proxy-agent");

const CAPTCHAAI_KEY = "YOUR_API_KEY";

function getSmartproxyAgent(options = {}) {
  let user = "spuser";
  if (options.country) user += `-country-${options.country}`;
  if (options.session) user += `-session-${options.session}`;

  return new HttpsProxyAgent(
    `http://${user}:[email protected]:10001`
  );
}

async function scrapeWithCaptcha(url, sitekey) {
  const agent = getSmartproxyAgent({
    country: "us",
    session: `sess-${Date.now()}`,
  });

  // Fetch page through proxy
  const pageResp = await axios.get(url, { httpsAgent: agent });

  // Solve CAPTCHA via CaptchaAI (no proxy needed)
  const submitResp = await axios.post(
    "https://ocr.captchaai.com/in.php",
    null,
    {
      params: {
        key: CAPTCHAAI_KEY,
        method: "userrecaptcha",
        googlekey: sitekey,
        pageurl: url,
        json: 1,
      },
    }
  );

  const taskId = submitResp.data.request;

  // Poll for result
  for (let i = 0; i < 60; i++) {
    await new Promise((r) => setTimeout(r, 5000));

    const result = await axios.get("https://ocr.captchaai.com/res.php", {
      params: {
        key: CAPTCHAAI_KEY,
        action: "get",
        id: taskId,
        json: 1,
      },
    });

    if (result.data.request === "CAPCHA_NOT_READY") continue;
    if (result.data.status === 1) return result.data.request;
  }

  throw new Error("Timeout");
}

getSmartproxyAgent ghép mã quốc gia và session ID vào username đúng quy ước ở bước 2 và bước 3; vòng polling vẫn giữ nhịp 5 giây.


Số worker nên bằng số thread: cách chọn gói CaptchaAI

CaptchaAI tính tiền theo thread (luồng giải đồng thời), không theo từng lần giải, nên biến số cần canh là độ đồng thời chứ không phải tổng số CAPTCHA. ThreadPoolExecutor(max_workers=5) khớp gói BASIC ($15/tháng, 5 thread); cần 15 luồng thì lên STANDARD ($30/tháng, 15 thread).

from concurrent.futures import ThreadPoolExecutor, as_completed

def process_url(url):
    session_id = "".join(random.choices(string.ascii_lowercase, k=8))
    proxy = get_sticky_proxy(10)

    try:
        resp = requests.get(url, proxies=proxy, timeout=30)

        # Check if CAPTCHA is present (simplified detection)
        if "data-sitekey" in resp.text:
            import re
            match = re.search(r'data-sitekey="([^"]+)"', resp.text)
            if match:
                sitekey = match.group(1)
                token = solve_captcha(url, sitekey)
                return {"url": url, "status": "solved", "token": token[:30]}

        return {"url": url, "status": "no_captcha"}

    except Exception as e:
        return {"url": url, "status": "error", "error": str(e)}

urls = [
    "https://site1.com/page",
    "https://site2.com/page",
    "https://site3.com/page",
]

with ThreadPoolExecutor(max_workers=5) as executor:
    futures = {executor.submit(process_url, u): u for u in urls}

    for future in as_completed(futures):
        result = future.result()
        print(f"[{result['status']}] {result['url']}")

Đặt max_workers cao hơn số thread chỉ tạo thêm hàng đợi, thấp hơn thì bạn trả tiền cho thread nhàn rỗi.


Bảng lỗi hay gặp khi ghép proxy với CaptchaAI

Triệu chứng Nguyên nhân Cách xử lý
407 Proxy Authentication Required Sai định dạng username/password Đối chiếu trong dashboard Smartproxy
IP đổi giữa chừng Chưa gắn định danh phiên Thêm -session-ID vào username
Request nào cũng gặp CAPTCHA Đang trỏ vào endpoint datacenter Chuyển sang cổng dân dụng
Token bị từ chối sau khi giải IP đổi giữa tải trang và submit Kéo dài thời lượng phiên
ERROR_WRONG_GOOGLEKEY Nhầm sitekey của widget khác Đọc data-sitekey từ DOM sau khi render

Hai dòng đầu chiếm phần lớn sự cố thực tế, và cả hai lộ ra ngay nếu bạn log IP thoát.


Ví dụ thực tế: đội QA ở TP.HCM giám sát giá catalog của chính mình

Một đội sáu người ở TP.HCM theo dõi giá danh mục sản phẩm chính công ty họ bán trên Shopee và Lazada, ngày hai lần. Trang danh sách công khai tải bình thường, nhưng luồng đăng nhập gian hàng trên staging lại dựng reCAPTCHA v2. Cấu hình cuối cùng khá gọn: mỗi tài khoản một phiên cố định 10 phút, năm worker song song trên gói BASIC ($15/tháng, 5 thread), ID task và thời gian giải ghi vào log.

Log đó cũng là hồ sơ tuân thủ theo Nghị định 13/2023/NĐ-CP về bảo vệ dữ liệu cá nhân: phạm vi chỉ gồm dữ liệu công khai và hệ thống do chính đội vận hành.


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

Vì sao token giải xong vẫn bị form từ chối?

Gần như luôn vì IP đã đổi giữa lúc tải trang và lúc submit. Log IP thoát ở cả hai thời điểm; nếu khác nhau, chuyển sang phiên cố định.

Request tới in.php có cần đi qua proxy không?

Không. CaptchaAI chỉ nhận pageurlsitekey, nên in.phpres.php đi thẳng từ máy chủ của bạn; chỉ tải trang và submit form mới qua gateway Smartproxy.

Gói CaptchaAI nào đủ cho pipeline 5–15 luồng song song?

BASIC ($15/tháng, 5 thread) đủ cho năm worker; cần 15 luồng thì dùng STANDARD ($30/tháng, 15 thread) — gói tính theo thread, không giới hạn số lần giải.

Nên dùng IP dân dụng hay datacenter cho pipeline giám sát giá?

Bắt đầu bằng cổng dân dụng: tần suất gặp thử thách thấp hơn nên tổng chi phí thường rẻ hơn dù băng thông đắt hơn.

CaptchaAI có giải hCaptcha hoặc FunCaptcha không?

Không. Danh sách hỗ trợ gồm reCAPTCHA v2/v3, Cloudflare Turnstile, Cloudflare Challenge, GeeTest v3, CAPTCHA ảnh/OCR và grid-image, cùng BLS CAPTCHA. GeeTest v4 mới sắp ra mắt; CaptchaFox, Friendly Captcha và Lemin ở giai đoạn beta.


Hướng dẫn liên quan


Bắt đầu bằng một phiên cố định và một sitekey trên staging của bạn — lấy API key CaptchaAI rồi chạy đoạn Python ở bước 1.

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