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

Giám sát giá thương mại điện tử bằng cách xử lý CAPTCHA

Theo dõi giá đối thủ trên Amazon, Walmart hay Shopee mà không xử lý CAPTCHA thì sớm muộn dữ liệu cũng có lỗ hổng: trang trả về thử thách CAPTCHA thay vì giá, request thất bại âm thầm. CaptchaAI gắn thẳng vào pipeline scraping để giải CAPTCHA ngay khi nó xuất hiện, giữ hệ thống giám sát giá chạy liên tục thay vì dừng lại chờ can thiệp thủ công.

Ba điều cần nắm trước khi triển khai:

  • CAPTCHA xuất hiện theo tần suất request, không phải do lỗi cấu hình — nên coi đó là một bước xử lý bình thường trong pipeline, chứ không phải sự cố cần vá tạm.
  • Chi phí giải CAPTCHA gần như luôn thấp hơn chi phí bỏ lỡ một đợt giảm giá của đối thủ vì dữ liệu giá bị đứt quãng đúng lúc cần nhất.
  • Cùng một kiến trúc áp dụng được cho cả sàn quốc tế (Amazon, Walmart, eBay) lẫn sàn trong nước (Shopee, Lazada, Tiki, Sendo).

CAPTCHA chặn bot giám sát giá ở đâu?

Bot theo dõi giá gặp CAPTCHA trên hầu hết các sàn lớn:

Nền tảng Loại CAPTCHA Nguyên nhân kích hoạt
Amazon CAPTCHA hình ảnh, reCAPTCHA Tần suất request cao
Walmart Cloudflare Turnstile Phát hiện bot
eBay reCAPTCHA v2 Mẫu truy cập bất thường
Best Buy Cloudflare Challenge Toàn bộ traffic tự động
Cửa hàng Shopify reCAPTCHA v3 Tùy theo cấu hình từng cửa hàng

Team theo dõi giá trên Shopee, Lazada, Tiki hay Sendo cũng gặp nguyên tắc tương tự: coi CAPTCHA là một bước trong luồng scrape, không phải sự cố xử lý riêng.

Bỏ qua CAPTCHA, job giám sát vẫn "chạy xong" nhưng để lại khoảng trống giá âm thầm — khó phát hiện hơn một lỗi crash rõ ràng, vì log không báo lỗi gì cả.

Chi phí giải CAPTCHA cho giám sát giá tốn bao nhiêu?

Khối lượng CAPTCHA/ngày Chi phí ước tính/ngày
50 sản phẩm, mỗi 30 phút ~2.400 ~$2-5
200 sản phẩm, mỗi 15 phút ~19.200 ~$15-30
1.000 sản phẩm, mỗi giờ ~24.000 ~$20-40

Đây là mức trần nếu mọi lần tải trang đều gặp CAPTCHA. Thực tế không phải request nào cũng bị chặn, nên chi phí thường thấp hơn 50–70% so với bảng trên.

Ví dụ: giám sát giá đợt sale song số trên Shopee và Tiki

Đợt sale song số (11/11, 12/12) trên Shopee, Lazada, Tiki là lúc tần suất request của team theo dõi giá tăng vọt, vì cần cập nhật gần như real-time để phản ứng kịp các đợt giảm giá theo giờ của đối thủ. Đây cũng là lúc CAPTCHA xuất hiện dày đặc nhất trong năm: traffic toàn sàn tăng đột biến khiến ngưỡng phát hiện bot bị hạ xuống chung, không riêng gì bot giám sát của bạn.

Cách xử lý thực tế trong khung giờ cao điểm này: giữ nguyên lịch polling 15–30 phút như ngày thường, chỉ tăng số sản phẩm theo dõi và số worker chạy song song (xem kiến trúc bên dưới), để CaptchaAI giải CAPTCHA ngay khi worker gặp thử thách thay vì tạm dừng job hoặc bỏ qua sản phẩm đó. Với vài trăm sản phẩm theo dõi trong khung giờ cao điểm, chi phí giải CAPTCHA vẫn nằm trong bảng ước tính ở trên — phần tăng thêm chủ yếu đến từ số lượng request, không phải đơn giá mỗi lần giải.

Kiến trúc hệ thống

Scheduler (every 30 min)
    → URL Queue
        → Scraper Workers (5-10 concurrent)
            → Fetch page
            → CAPTCHA detected?
                → Yes → CaptchaAI → Solve → Retry page
                → No → Parse prices
            → Store in database
        → Alert on price changes

Ba lớp trong kiến trúc này chịu trách nhiệm riêng biệt:

  • Scheduler đẩy URL cần kiểm tra vào queue theo chu kỳ cố định, không tự fetch trang.
  • Scraper worker (5-10 tiến trình song song) fetch trang, gọi CaptchaAI khi gặp thử thách, rồi parse giá.
  • Storage + alert ghi kết quả vào database và bắn cảnh báo khi giá đổi, tách biệt khỏi vòng lặp fetch/giải CAPTCHA để một job chậm không làm nghẽn job khác.

Code mẫu: gắn CaptchaAI vào worker scrape

Hai bản dưới đây làm cùng một việc: phát hiện reCAPTCHA/Turnstile, gửi task tới in.php, polling res.php để lấy token, rồi dùng token gửi lại request gốc.

Python

import requests
import time
import re
import json
import os
from datetime import datetime

API_KEY = os.environ["CAPTCHAAI_API_KEY"]
BASE_URL = "https://ocr.captchaai.com"

def solve_captcha(method, params):
    params["key"] = API_KEY
    params["method"] = method

    resp = requests.get(f"{BASE_URL}/in.php", params=params)
    if not resp.text.startswith("OK|"):
        raise Exception(f"Submit failed: {resp.text}")

    task_id = resp.text.split("|")[1]

    for _ in range(60):
        time.sleep(5)
        result = requests.get(f"{BASE_URL}/res.php", params={
            "key": API_KEY, "action": "get", "id": task_id,
        })
        if result.text == "CAPCHA_NOT_READY":
            continue
        if result.text.startswith("OK|"):
            return result.text.split("|", 1)[1]
        raise Exception(f"Solve failed: {result.text}")

    raise TimeoutError("CAPTCHA solve timed out")

def fetch_with_captcha(url, session):
    """Fetch a page, solving CAPTCHAs if encountered."""
    resp = session.get(url)

    # Check for reCAPTCHA
    match = re.search(r'data-sitekey=["\']([A-Za-z0-9_-]+)["\']', resp.text)
    if match:
        site_key = match.group(1)
        token = solve_captcha("userrecaptcha", {
            "googlekey": site_key,
            "pageurl": url,
        })
        resp = session.post(url, data={"g-recaptcha-response": token})

    # Check for Turnstile
    match = re.search(
        r'class="cf-turnstile"[^>]*data-sitekey=["\']([^"\']+)', resp.text
    )
    if match:
        site_key = match.group(1)
        token = solve_captcha("turnstile", {
            "sitekey": site_key,
            "pageurl": url,
        })
        resp = session.post(url, data={"cf-turnstile-response": token})

    return resp

def extract_price(html, selectors):
    """Extract price from HTML using regex patterns."""
    for pattern in selectors:
        match = re.search(pattern, html)
        if match:
            price_str = match.group(1).replace(",", "")
            return float(price_str)
    return None

def monitor_prices(products):
    """Monitor prices for a list of products."""
    session = requests.Session()
    session.headers["User-Agent"] = (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "AppleWebKit/537.36 Chrome/120.0.0.0"
    )

    results = []
    for product in products:
        try:
            resp = fetch_with_captcha(product["url"], session)
            price = extract_price(resp.text, product["selectors"])

            results.append({
                "name": product["name"],
                "url": product["url"],
                "price": price,
                "timestamp": datetime.utcnow().isoformat(),
                "status": "ok",
            })
            print(f"  {product['name']}: ${price}")

        except Exception as e:
            results.append({
                "name": product["name"],
                "url": product["url"],
                "price": None,
                "timestamp": datetime.utcnow().isoformat(),
                "status": f"error: {e}",
            })
            print(f"  {product['name']}: ERROR - {e}")

    return results

# Define products to monitor
products = [
    {
        "name": "Wireless Headphones",
        "url": "https://example.com/product/headphones",
        "selectors": [
            r'class="price"[^>]*>\$?([\d,]+\.?\d*)',
            r'itemprop="price" content="([\d.]+)"',
        ],
    },
    {
        "name": "Bluetooth Speaker",
        "url": "https://example.com/product/speaker",
        "selectors": [
            r'class="price"[^>]*>\$?([\d,]+\.?\d*)',
        ],
    },
]

print("Starting price check...")
results = monitor_prices(products)

# Save results
with open("prices.json", "w") as f:
    json.dump(results, f, indent=2)

Node.js

const axios = require("axios");
const cheerio = require("cheerio");

const API_KEY = process.env.CAPTCHAAI_API_KEY;

async function solveCaptcha(method, params) {
  params.key = API_KEY;
  params.method = method;

  const submit = await axios.get("https://ocr.captchaai.com/in.php", {
    params,
  });
  const taskId = String(submit.data).split("|")[1];

  for (let i = 0; i < 60; i++) {
    await new Promise((r) => setTimeout(r, 5000));
    const poll = await axios.get("https://ocr.captchaai.com/res.php", {
      params: { key: API_KEY, action: "get", id: taskId },
    });
    const text = String(poll.data);
    if (text === "CAPCHA_NOT_READY") continue;
    if (text.startsWith("OK|")) return text.split("|").slice(1).join("|");
    throw new Error(text);
  }
  throw new Error("Timeout");
}

async function monitorPrice(url) {
  const resp = await axios.get(url);
  const $ = cheerio.load(resp.data);

  // Check for reCAPTCHA
  const siteKey = $(".g-recaptcha").attr("data-sitekey");
  if (siteKey) {
    const token = await solveCaptcha("userrecaptcha", {
      googlekey: siteKey,
      pageurl: url,
    });
    // Re-fetch with token
    const formResp = await axios.post(url, { "g-recaptcha-response": token });
    return cheerio.load(formResp.data);
  }

  const price = $('[itemprop="price"]').attr("content") || $(".price").text();
  return parseFloat(price.replace(/[^0-9.]/g, ""));
}

Lên lịch chạy job định kỳ

Chạy kiểm tra cứ sau 30 phút bằng cron:

# crontab -e
*/30 * * * * cd /opt/monitor && python price_monitor.py >> /var/log/prices.log 2>&1

Hoặc sử dụng thư viện schedule của Python:

import schedule

schedule.every(30).minutes.do(lambda: monitor_prices(products))

while True:
    schedule.run_pending()
    time.sleep(60)

Chọn cách nào tùy vào nơi job chạy:

  • Dùng cron khi worker chạy như một script độc lập, muốn hệ điều hành quản lý lịch và tự khởi động lại nếu server reboot.
  • Dùng schedule khi muốn gộp lịch chạy vào cùng một process Python dài hạn, ví dụ chạy chung với một service khác đang mở sẵn.

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

Giám sát bao nhiêu sản phẩm thì cần xử lý CAPTCHA?

Ngay từ vài chục sản phẩm theo dõi mỗi 15–30 phút, Walmart hay Best Buy đã có thể chặn bằng Turnstile hoặc Challenge. Quy mô càng lớn, tỷ lệ gặp CAPTCHA càng cao — nên xử lý ngay từ đầu thay vì đợi bị chặn mới thêm vào.

Theo dõi giá trên Shopee, Lazada, Tiki có cần giải CAPTCHA không?

Tùy thời điểm và tần suất request, nhưng nguyên tắc giống các sàn quốc tế: coi CAPTCHA là một bước trong luồng scrape, không phải sự cố cần vá tạm.

Nên dùng polling hay callback cho hệ thống giám sát giá?

Với job chạy theo lịch như cron mỗi 15–30 phút trong bài này, polling res.php đơn giản, dễ debug và đủ nhanh — không cần mở thêm endpoint nhận webhook. Callback hợp lý hơn khi giám sát liên tục hàng chục nghìn sản phẩm, worker chạy suốt ngày và cần nhận token ngay khi có thay vì chờ vòng polling tiếp theo.

Giải CAPTCHA rồi mà vẫn bị chặn thì xử lý thế nào?

Giải CAPTCHA xử lý thử thách hiện tại, nhưng traffic bất thường trong cùng phiên vẫn có thể bị đánh dấu lại ở lần request kế tiếp. Giãn đều thời gian giữa các request, giữ số worker song song ở mức hợp lý so với quy mô danh mục sản phẩm, và dùng User-Agent hợp lệ thay vì dồn dập nhiều request cùng lúc từ cùng một nguồn.

Có thể theo dõi giá trên nhiều loại tiền tệ không?

Có. Phân tích ký hiệu tiền tệ cùng với giá trị số trong response. CaptchaAI giải CAPTCHA độc lập với ngôn ngữ và khu vực của trang đích.

Hướng dẫn liên quan

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