Trường Hợp Sử Dụng

Xử lý CAPTCHA khi scraping bằng Python với CaptchaAI

Script Python dùng requests quét được hàng nghìn trang mỗi giờ mà không cần mở trình duyệt — cho đến khi gặp reCAPTCHA hoặc Cloudflare Turnstile. Cách xử lý nhanh nhất không phải chuyển cả scraper sang Selenium, mà gắn API CaptchaAI ngay vào lớp gửi request hiện có: lấy sitekey, gửi task tới in.php, polling res.php để nhận token, rồi dùng token điền vào form. Bài này đi kèm code cho scraping nhiều trang, CAPTCHA hình ảnh và retry cho môi trường production.

Cần chuẩn bị gì trước khi bắt đầu

  • Python 3.7+ — cần có sẵn pip.
  • Thư viện requests — cài bằng pip install requests.
  • Thư viện beautifulsoup4 — cài bằng pip install beautifulsoup4.
  • API key CaptchaAI — lấy tại captchaai.com.

Bốn bước xử lý CAPTCHA khi scraping

Mọi đoạn code trong bài đều đi theo đúng một luồng, chỉ khác nhau ở loại CAPTCHA và cách trích tham số:

  1. Trích sitekey (hoặc pageurl) từ HTML của trang đích.
  2. Gửi task tới in.php kèm sitekey và pageurl.
  3. Polling res.php cho đến khi nhận được token.
  4. Điền token vào đúng field (g-recaptcha-response, cf-turnstile-response…) rồi submit lại form.

Điền token vào field sai tên là nguyên nhân phổ biến nhất khiến scraper "giải xong vẫn bị chặn" — quan trọng hơn cả tốc độ giải.

Class CaptchaSolver dùng lại cho nhiều dự án Python

Viết một class solver duy nhất rồi tái sử dụng ở mọi script scraping thay vì lặp lại logic gửi/polling mỗi lần:

import requests
import time

class CaptchaSolver:
    def __init__(self, api_key):
        self.api_key = api_key
        self.base = "https://ocr.captchaai.com"

    def _submit(self, params):
        params["key"] = self.api_key
        resp = requests.get(f"{self.base}/in.php", params=params)
        if not resp.text.startswith("OK|"):
            raise Exception(f"Submit error: {resp.text}")
        return resp.text.split("|")[1]

    def _poll(self, task_id, timeout=300):
        deadline = time.time() + timeout
        while time.time() < deadline:
            time.sleep(5)
            resp = requests.get(f"{self.base}/res.php", params={
                "key": self.api_key,
                "action": "get",
                "id": task_id
            })
            if resp.text == "CAPCHA_NOT_READY":
                continue
            if resp.text.startswith("OK|"):
                return resp.text.split("|")[1]
            raise Exception(f"Solve error: {resp.text}")
        raise TimeoutError("Solve timed out")

    def solve_recaptcha_v2(self, site_key, page_url):
        task_id = self._submit({
            "method": "userrecaptcha",
            "googlekey": site_key,
            "pageurl": page_url
        })
        return self._poll(task_id)

    def solve_recaptcha_v3(self, site_key, page_url, action="verify"):
        task_id = self._submit({
            "method": "userrecaptcha",
            "googlekey": site_key,
            "pageurl": page_url,
            "version": "v3",
            "action": action
        })
        return self._poll(task_id)

    def solve_turnstile(self, site_key, page_url):
        task_id = self._submit({
            "method": "turnstile",
            "sitekey": site_key,
            "pageurl": page_url
        })
        return self._poll(task_id)

    def solve_image(self, image_base64):
        task_id = self._submit({
            "method": "base64",
            "body": image_base64
        })
        return self._poll(task_id)

Các phương thức đã dựng sẵn trong class

  • solve_recaptcha_v2 — dùng cho reCAPTCHA v2 checkbox/invisible.
  • solve_recaptcha_v3 — cần truyền thêm action khớp với hành động thật trên trang.
  • solve_turnstile — dùng cho Cloudflare Turnstile.
  • solve_image — nhận ảnh CAPTCHA đã base64-hoá, trả về text.

Quét biểu mẫu có reCAPTCHA từng bước

Ví dụ dưới đây tải trang, trích sitekey từ HTML, giải token rồi gửi lại chính form đó trong cùng một session.

Giữ nguyên requests.Session() xuyên suốt — nhiều trang kiểm tra cookie phiên trước khi chấp nhận token, tách session ra là nguyên nhân phổ biến khiến submit thất bại dù token hợp lệ.

from bs4 import BeautifulSoup
import requests

solver = CaptchaSolver("YOUR_API_KEY")
session = requests.Session()
session.headers.update({
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
})

# Step 1: Load the page
url = "https://example.com/search"
page = session.get(url)
soup = BeautifulSoup(page.text, "html.parser")

# Step 2: Extract the site key
recaptcha_div = soup.find("div", class_="g-recaptcha")
site_key = recaptcha_div["data-sitekey"]

# Step 3: Solve the CAPTCHA
token = solver.solve_recaptcha_v2(site_key, url)

# Step 4: Submit the form with the token
form_data = {
    "q": "search term",
    "g-recaptcha-response": token
}
result = session.post(url, data=form_data)

# Step 5: Parse the results
result_soup = BeautifulSoup(result.text, "html.parser")
items = result_soup.find_all("div", class_="result-item")
for item in items:
    print(item.text.strip())

Quét nhiều trang bị chặn CAPTCHA

Vì sao phải giải lại cho từng trang

Token CaptchaAI gắn với một pageurl và một lần submit cụ thể, không dùng lại được cho URL trang tiếp theo — vòng lặp dưới đây giải lại token ở đầu mỗi trang thay vì chỉ giải một lần rồi tái sử dụng:

def scrape_all_pages(base_url, site_key, max_pages=10):
    solver = CaptchaSolver("YOUR_API_KEY")
    session = requests.Session()
    session.headers.update({
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
    })
    all_results = []

    for page_num in range(1, max_pages + 1):
        page_url = f"{base_url}?page={page_num}"

        # Solve CAPTCHA for each page if needed
        token = solver.solve_recaptcha_v2(site_key, page_url)

        resp = session.get(page_url, params={
            "g-recaptcha-response": token,
            "page": page_num
        })

        soup = BeautifulSoup(resp.text, "html.parser")
        items = soup.find_all("div", class_="item")

        if not items:
            break

        all_results.extend([item.text.strip() for item in items])
        print(f"Page {page_num}: {len(items)} items")

        time.sleep(2)  # Polite delay

    return all_results

Giải CAPTCHA hình ảnh (OCR)

Một số trang vẫn dùng CAPTCHA văn bản dạng ảnh thay vì reCAPTCHA. Quy trình khác ở chỗ base64-hoá ảnh trước khi gửi, nhưng vẫn theo đúng bốn bước gửi → polling → token:

import base64

def scrape_with_image_captcha(url):
    solver = CaptchaSolver("YOUR_API_KEY")
    session = requests.Session()

    page = session.get(url)
    soup = BeautifulSoup(page.text, "html.parser")

    # Find the CAPTCHA image
    captcha_img = soup.find("img", {"id": "captcha-image"})
    captcha_url = captcha_img["src"]

    # Download and encode the image
    img_resp = session.get(captcha_url)
    img_base64 = base64.b64encode(img_resp.content).decode()

    # Solve
    captcha_text = solver.solve_image(img_base64)

    # Submit
    form_data = {
        "captcha": captcha_text,
        "username": "user"
    }
    result = session.post(url, data=form_data)
    return result.text

Thêm retry cho scraper chạy production

Mạng chập chờn hoặc token hết hạn giữa lúc gửi là chuyện bình thường khi scraper chạy liên tục 24/7 — bọc lần gọi solve trong logic thử lại thay vì để cả job chết vì một request lỗi:

def solve_with_retry(solver, site_key, page_url, max_retries=3):
    for attempt in range(max_retries):
        try:
            return solver.solve_recaptcha_v2(site_key, page_url)
        except Exception as e:
            if attempt == max_retries - 1:
                raise
            print(f"Attempt {attempt + 1} failed: {e}. Retrying...")
            time.sleep(2)

Với job chạy production nhiều giờ liên tục, log số lần retry theo từng site — tần suất retry tăng đột biến thường báo hiệu trang đã đổi sitekey hoặc thêm lớp chặn mới.

Ví dụ thực tế: theo dõi giá trên sàn thương mại điện tử

Một tình huống quen thuộc với các đội scraping engineer ở TP.HCM và Hà Nội là theo dõi giá trên Shopee, Lazada hoặc Tiki để phục vụ nghiên cứu thị trường hoặc đối chiếu với catalogue của chính doanh nghiệp. Các trang này thường đặt reCAPTCHA v3 hoặc Turnstile sau form tìm kiếm hoặc trang sản phẩm khi vượt một ngưỡng request nhất định. Class CaptchaSolver áp dụng nguyên vẹn: gọi solve_recaptcha_v3 hoặc solve_turnstile trước khi gửi request tìm kiếm, giữ requests.Session() để không mất cookie, và giãn time.sleep() giữa các lần gửi.

Cần quét song song nhiều danh mục thay vì lặp tuần tự? Chuyển phần gửi task sang bất đồng bộ — xem tích hợp aiohttp với CaptchaAI.

Nếu quy trình có chạm tới dữ liệu cá nhân (tên người bán, số điện thoại trong đánh giá…), ghi log rõ nguồn và áp dụng nguyên tắc tối thiểu hoá dữ liệu theo Nghị định 13/2023/NĐ-CP trước khi lưu trữ lâu dài — đây không phải tư vấn pháp lý, chỉ là thói quen nên có.

Lỗi thường gặp khi tích hợp và cách xử lý

Vấn đề Nguyên nhân Cách xử lý
ERROR_WRONG_USER_KEY API key không hợp lệ Kiểm tra lại key trong bảng điều khiển
ERROR_ZERO_BALANCE Tài khoản hết số dư Nạp thêm số dư vào tài khoản
Gửi form xong lại quay về trang CAPTCHA Token đã hết hạn hoặc sai tên field Dùng token ngay sau khi nhận; kiểm tra lại tên field của form
ConnectionError Sự cố mạng Thêm logic thử lại với backoff tăng dần
Kết quả trống sau khi gửi Trang yêu cầu cookie/session Dùng requests.Session() để giữ cookie xuyên suốt

CAPTCHA xuất hiện đột biến hoặc request bị chặn thẳng tay thường là dấu hiệu rate limit, không phải lỗi ở solver — xem xoay proxy khi scraping có CAPTCHA trước khi tăng số luồng gửi.

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

Có cần Selenium để scraping CAPTCHA bằng Python không?

Không phải lúc nào cũng cần. Nếu form hoạt động được với HTTP POST tiêu chuẩn, requests + CaptchaAI nhanh và nhẹ hơn Selenium. Chỉ cần Selenium khi trang bắt buộc render JavaScript trước khi form hoặc CAPTCHA xuất hiện trong DOM.

CaptchaAI hỗ trợ những loại CAPTCHA nào khi scraping bằng Python?

CaptchaAI giải được reCAPTCHA v2/v3, Cloudflare Turnstile và Challenge, GeeTest v3, cùng CAPTCHA hình ảnh/OCR như trong bài. hCaptcha và FunCaptcha (Arkose Labs) hiện chưa được hỗ trợ.

Giải CAPTCHA bằng Python với CaptchaAI tốn bao nhiêu?

CaptchaAI tính phí theo thread chạy song song, không theo từng lần giải. Gói BASIC ($15/tháng, 5 thread) là mức khởi điểm phổ biến; tăng thread khi cần scraping nhiều trang cùng lúc.

reCAPTCHA v3 trả về score thấp thì scraper phải làm gì?

Score thấp nghĩa là trang đánh giá phiên hiện tại rủi ro cao, không phải lỗi của token. Gọi solve_recaptcha_v3 với action khớp đúng hành động thật trên trang (search, login…), giữ User-Agent và cookie nhất quán trong toàn session.

Scraping trang có CAPTCHA có hợp pháp không?

Tuỳ điều khoản sử dụng và loại dữ liệu thu thập. An toàn nhất là giới hạn ở dữ liệu công khai, tuân thủ robots.txt, và áp dụng nguyên tắc tối thiểu hoá dữ liệu nếu có chạm đến thông tin cá nhân. Đây là hướng dẫn kỹ thuật, không phải tư vấn pháp lý.

Hướng dẫn liên quan

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