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
schedulekhi 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.