Use Cases

Мониторинг цен в электронной коммерции с обработкой CAPTCHA

Карточка товара на Amazon отдаёт reCAPTCHA уже после третьего запроса подряд — и стабильный конвейер мониторинга цен вдруг начинает возвращать пустые значения вместо чисел. Рано или поздно защита срабатывает на любом скрапере, который регулярно опрашивает страницы товаров, и без автоматического решения CAPTCHA в базе появляются дыры вместо цен. CaptchaAI закрывает этот разрыв: распознаёт защиту на лету и возвращает токен, по которому скрапер получает страницу с реальной ценой.

Где мониторинг цен упирается в CAPTCHA

Боты мониторинга натыкаются на защиту у всех крупных ритейлеров:

Платформа Тип CAPTCHA Триггер
Amazon Image CAPTCHA, reCAPTCHA Высокий объём запросов
Walmart Cloudflare Turnstile Обнаружение автоматизации
eBay reCAPTCHA v2 Подозрительные паттерны поведения
Best Buy Cloudflare Challenge Весь автоматизированный трафик
Магазины на Shopify reCAPTCHA v3 Зависит от настроек магазина

Те же принципы работают и за пределами западных площадок: Wildberries, Ozon и AliExpress, как правило, используют похожие механизмы защиты. Без обработки CAPTCHA конвейер не падает с ошибкой — он молча перестаёт обновлять цены, и вы замечаете это только когда алерты не приходят неделями.

Признаки того, что конвейер уже упёрся в защиту, а не просто вернул пустую страницу:

  • Селектор цены стабильно возвращает None при HTTP-статусе 200 — страница загрузилась, но это не карточка товара.
  • В логах воркера растёт доля повторных запросов к одним и тем же URL без итогового результата.
  • Число новых записей в таблице цен резко падает при неизменном списке товаров в очереди.

Сколько это стоит

Бюджет зависит от частоты опроса каталога:

Объём CAPTCHA в день Ориентировочная стоимость в день
50 товаров каждые 30 мин. ~2 400 ~$2–5
200 товаров каждые 15 мин. ~19 200 ~$15–30
1000 товаров ежечасно ~24 000 ~$20–40

Не каждая загрузка упирается в CAPTCHA, так что расходы часто на 50–70 % ниже табличных. Тарифы CaptchaAI считаются по потокам, а не по решениям: для точечного мониторинга хватает BASIC ($15/мес, 5 потоков), для крупного каталога — ADVANCE ($90/мес, 50 потоков) или PREMIUM ($170/мес, 100 потоков).

Ориентир по выбору тарифа под нагрузку:

  • До 50 товаров с редким опросом — BASIC ($15/мес, 5 потоков) обычно берёт задачу с запасом.
  • 100–300 товаров с опросом каждые 15–30 минут — смотрите в сторону STANDARD ($30/мес, 15 потоков).
  • Каталог на 1000+ товаров с ежечасным опросом — PREMIUM ($170/мес, 100 потоков) даёт запас по параллельности воркеров.

Архитектура конвейера

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

Планировщик раскладывает товары по очереди, воркеры разбирают её параллельно, а решение CAPTCHA включается только там, где защита реально сработала.

Совет: не пытайтесь закрыть весь каталог одним пулом воркеров. При росте нагрузки проще поднять второй пул с отдельной очередью, чем наращивать конкурентность внутри одного — так один медленный решённый CAPTCHA не тормозит остальную очередь.

Код: где именно подключается CaptchaAI

Что проверить перед первым запуском

  • Переменная окружения CAPTCHAAI_API_KEY подставляется в оба скрипта — не хардкодьте ключ прямо в код.
  • Селекторы цены в selectors актуальны для текущей вёрстки карточки товара: маркетплейсы меняют её без предупреждения, и это самая частая причина пустого price при живом ответе сайта.
  • fetch_with_captcha проверяет reCAPTCHA и Turnstile по очереди — по логам видно, какой из блоков сработал на конкретном URL.

Монитор цен (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, ""));
}

Расписание проверок

Запускайте проверки каждые 30 минут через cron:

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

Или через библиотеку Python schedule, если хочется держать планировщик внутри самого процесса:

import schedule

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

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

Слишком частый опрос — самый быстрый способ получить CAPTCHA на каждом втором запросе. 30 минут — рабочий компромисс для большинства каталогов.

Ориентир по интервалу опроса:

  • Ценовые войны, дефицитный товар — каждые 10–15 минут, закладывайте больший бюджет на решение CAPTCHA.
  • Обычный каталог без резких скачков спроса — 30 минут, вариант по умолчанию.
  • Долгосрочный трекинг тренда цены — раз в 2–4 часа достаточно и почти не упирается в защиту.

Частые вопросы о мониторинге цен

Какой тариф CaptchaAI выбрать для мониторинга каталога?

Отталкивайтесь от числа воркеров, а не товаров: 5–10 запросов обычно укладываются в BASIC (5 потоков) или STANDARD ($30/мес, 15 потоков).

Как снизить риск блокировки при частом мониторинге?

Ротируйте прокси и User-Agent, распределяйте запросы по времени вместо пакетной отправки — решение CAPTCHA снимает блокировку, но не отменяет смысл вести себя как обычный трафик.

Нужно ли учитывать закон о персональных данных при сборе цен конкурентов?

Публичные цены сами по себе под 152-ФЗ и GDPR не подпадают. Вопрос возникает, если вместе с ценой вы начинаете собирать данные о пользователях — отзывы с именами, аккаунты. Собирайте только то, что нужно для мониторинга цен.

Что делать, если price пустой, хотя страница вернула 200?

Сначала проверьте лог: если fetch_with_captcha вообще не находил data-sitekey, значит защита не сработала и дело в селекторе — вёрстка карточки поменялась. Если токен решён, а цена всё равно пустая, сверьте регулярку в selectors с актуальным HTML страницы.

Нужен ли прокси вместе с CaptchaAI, или решения CAPTCHA достаточно?

Это разные задачи. Прокси снижают шанс, что защита вообще покажется при высокой частоте запросов с одного IP. CaptchaAI решает саму защиту, когда она уже показалась — вне зависимости от того, ротируете вы адреса или нет. Для стабильного конвейера обычно нужны оба слоя, а не один вместо другого.

Что читать дальше

Комментарии для этой статьи отключены.