Use Cases

Обработка CAPTCHA для мониторинга аукционной площадки

Скрипт мониторинга аукциона обычно останавливает не блокировка IP, а reCAPTCHA v2 — она всплывает при быстром поиске, повторных просмотрах карточки лота и частом обновлении страницы со ставками. Ниже — рабочая схема: где именно аукционные площадки показывают CAPTCHA, как решать её через API CaptchaAI прямо в коде мониторинга и как реже её провоцировать.

Где на аукционных площадках появляется CAPTCHA

Действие Тип CAPTCHA Когда срабатывает
Поиск / листинг /browse reCAPTCHA v2 Частые последовательные запросы поиска
Просмотр карточки лота reCAPTCHA v2 Много запросов с одного IP подряд
История ставок reCAPTCHA v2 Повторная загрузка страницы деталей
Просмотр категорий Cloudflare Turnstile Скорость навигации, похожая на бота
Страницы с оповещением о цене reCAPTCHA v2 Частое обновление страницы

Похожая картина встречается и на нишевых площадках для коллекционных лотов вроде Мешок.ру: reCAPTCHA v2 включается почти по тому же сценарию — поиск, карточка лота, затем история ставок. Если пишете монитор под конкретную площадку, сначала пройдите этот путь вручную и зафиксируйте, на каком шаге появляется проверка — это экономит часы отладки.

Как выбрать частоту опроса и снизить частоту CAPTCHA

Частота опроса и ожидаемый уровень CAPTCHA

Частота проверки напрямую влияет на то, как часто площадка покажет CAPTCHA. Ориентир по сценариям:

Частота проверки Сценарий Ожидаемая частота CAPTCHA
Каждые 30 секунд Ставки в последнюю минуту Высокая — используйте прокси
Каждые 5 минут Активное отслеживание аукциона Умеренная
Каждые 15 минут Список наблюдения Низкая
Раз в час Долгосрочный анализ цен Минимальная

Как снизить частоту CAPTCHA до того, как она появится

  • Переиспользуйте cookies сессии — сохраняет аутентифицированное состояние между запросами.
  • Распределяйте запросы через прокси — снижает нагрузку с одного IP.
  • Рандомизируйте интервалы запросов — запросы не идут строго по расписанию.
  • Используйте авторизованные аккаунты — порог срабатывания CAPTCHA у них ниже.

Скрипт мониторинга на Python: решение reCAPTCHA v2 в фоне

Класс ниже инкапсулирует три задачи мониторинга — поиск объявлений, проверку карточки лота и отслеживание истории ставок по расписанию. Как только _has_captcha находит признак reCAPTCHA в ответе, скрипт вытаскивает sitekey, отправляет его в CaptchaAI и повторяет запрос уже с токеном:

import requests
import time
import re
from datetime import datetime

class AuctionMonitor:
    def __init__(self, api_key):
        self.api_key = api_key
        self.session = requests.Session()
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
        })

    def search_listings(self, auction_url, query, category=None):
        """Search auction listings, solving CAPTCHAs when triggered."""
        params = {"q": query}
        if category:
            params["category"] = category

        response = self.session.get(
            f"{auction_url}/search", params=params
        )

        if self._has_captcha(response.text):
            site_key = self._extract_site_key(response.text)
            token = self._solve_recaptcha(site_key, f"{auction_url}/search")
            response = self.session.post(
                f"{auction_url}/search",
                data={**params, "g-recaptcha-response": token}
            )

        return self._parse_listings(response.text)

    def monitor_listing(self, auction_url, listing_id):
        """Get current bid and listing details."""
        url = f"{auction_url}/item/{listing_id}"
        response = self.session.get(url)

        if self._has_captcha(response.text):
            site_key = self._extract_site_key(response.text)
            token = self._solve_recaptcha(site_key, url)
            response = self.session.post(url, data={
                "g-recaptcha-response": token
            })

        return self._parse_listing_detail(response.text)

    def track_bids(self, auction_url, listing_ids, interval=60):
        """Track bid changes across multiple listings."""
        history = {lid: [] for lid in listing_ids}

        while True:
            for listing_id in listing_ids:
                try:
                    detail = self.monitor_listing(auction_url, listing_id)
                    previous = history[listing_id]

                    if previous and detail["current_bid"] != previous[-1]["current_bid"]:
                        print(f"Bid change on {listing_id}: "
                              f"${previous[-1]['current_bid']} → ${detail['current_bid']}")

                    history[listing_id].append(detail)
                except Exception as e:
                    print(f"Error checking {listing_id}: {e}")

            time.sleep(interval)

    def _has_captcha(self, html):
        return "g-recaptcha" in html or "recaptcha" in html.lower()

    def _extract_site_key(self, html):
        match = re.search(r'data-sitekey="([^"]+)"', html)
        if match:
            return match.group(1)
        match = re.search(r"sitekey['\"]?\s*[:=]\s*['\"]([^'\"]+)", html)
        if match:
            return match.group(1)
        raise ValueError("Could not find reCAPTCHA site key")

    def _solve_recaptcha(self, site_key, page_url):
        resp = requests.post("https://ocr.captchaai.com/in.php", data={
            "key": self.api_key,
            "method": "userrecaptcha",
            "googlekey": site_key,
            "pageurl": page_url,
            "json": 1
        })
        task_id = resp.json()["request"]

        for _ in range(60):
            time.sleep(3)
            result = requests.get("https://ocr.captchaai.com/res.php", params={
                "key": self.api_key,
                "action": "get",
                "id": task_id,
                "json": 1
            })
            data = result.json()
            if data["status"] == 1:
                return data["request"]

        raise TimeoutError("reCAPTCHA solve timed out")

    def _parse_listings(self, html):
        from bs4 import BeautifulSoup
        soup = BeautifulSoup(html, "html.parser")

        def text_or_none(node):
            return node.text.strip() if node and node.text else None

        def attr_or_none(node, attr):
            return node.get(attr) if node else None

        listings = []
        for item in soup.select(".listing-item, .auction-item"):
            listings.append({
                "title": text_or_none(item.select_one(".title")),
                "current_bid": text_or_none(item.select_one(".price, .bid")),
                "time_left": text_or_none(item.select_one(".time-left")),
                "url": attr_or_none(item.select_one("a"), "href")
            })
        return listings

    def _parse_listing_detail(self, html):
        from bs4 import BeautifulSoup
        soup = BeautifulSoup(html, "html.parser")

        def text_or_none(node):
            return node.text.strip() if node and node.text else None

        return {
            "title": text_or_none(soup.select_one("h1, .item-title")),
            "current_bid": text_or_none(soup.select_one(".current-bid, .price")),
            "bid_count": text_or_none(soup.select_one(".bid-count")),
            "time_left": text_or_none(soup.select_one(".time-remaining")),
            "checked_at": datetime.now().isoformat()
        }

# Usage
monitor = AuctionMonitor("YOUR_API_KEY")
listings = monitor.search_listings(
    "https://auctions.example.com",
    "vintage electronics",
    category="collectibles"
)

Оповещения о цене на JavaScript

Для веб-мониторов на Node.js та же логика оформляется как трекер со списком наблюдения: каждый лот проверяется отдельно, а при приближении ставки к заданному порогу формируется алерт.

class AuctionTracker {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.watchList = new Map();
  }

  addWatch(listingId, url, maxPrice) {
    this.watchList.set(listingId, { url, maxPrice, history: [] });
  }

  async checkAll() {
    const alerts = [];

    for (const [id, watch] of this.watchList) {
      try {
        const detail = await this.fetchListing(watch.url);
        watch.history.push(detail);

        const price = parseFloat(detail.currentBid.replace(/[^0-9.]/g, ''));
        if (price >= watch.maxPrice * 0.9) {
          alerts.push({
            listing: id,
            price,
            threshold: watch.maxPrice,
            message: `Price approaching limit: $${price} / $${watch.maxPrice}`
          });
        }
      } catch (error) {
        alerts.push({ listing: id, error: error.message });
      }
    }

    return alerts;
  }

  async fetchListing(url) {
    const response = await fetch(url);
    const html = await response.text();

    if (html.includes('g-recaptcha')) {
      return this.solveAndFetch(url, html);
    }

    return this.parseDetail(html);
  }

  async solveAndFetch(url, html) {
    const siteKeyMatch = html.match(/data-sitekey="([^"]+)"/);
    if (!siteKeyMatch) throw new Error('No reCAPTCHA site key found');

    const submitResp = await fetch('https://ocr.captchaai.com/in.php', {
      method: 'POST',
      body: new URLSearchParams({
        key: this.apiKey,
        method: 'userrecaptcha',
        googlekey: siteKeyMatch[1],
        pageurl: url,
        json: '1'
      })
    });

    const { request: taskId } = await submitResp.json();

    for (let i = 0; i < 60; i++) {
      await new Promise(r => setTimeout(r, 3000));
      const result = await fetch(
        `https://ocr.captchaai.com/res.php?key=${this.apiKey}&action=get&id=${taskId}&json=1`
      );
      const data = await result.json();
      if (data.status === 1) {
        // Resubmit with token
        const response = await fetch(url, {
          method: 'POST',
          body: new URLSearchParams({ 'g-recaptcha-response': data.request })
        });
        return this.parseDetail(await response.text());
      }
    }

    throw new Error('reCAPTCHA solve timed out');
  }

  parseDetail(html) {
    // Parse auction listing details from HTML
    return {
      currentBid: html.match(/current.?bid[^>]*>([^<]+)/i)?.[1]?.trim(),
      bidCount: html.match(/(\d+)\s*bids?/i)?.[1],
      timeLeft: html.match(/time.?(?:left|remaining)[^>]*>([^<]+)/i)?.[1]?.trim(),
      checkedAt: new Date().toISOString()
    };
  }
}

// Usage
const tracker = new AuctionTracker('YOUR_API_KEY');
tracker.addWatch('item-123', 'https://auctions.example.com/item/123', 500);
tracker.addWatch('item-456', 'https://auctions.example.com/item/456', 200);
const alerts = await tracker.checkAll();

Диагностика типичных проблем

CAPTCHA появляется на каждом запросе

Причина почти всегда одна — скрипт не сохраняет сессию между запросами, и площадка видит в каждом вызове нового анонимного посетителя. Переиспользуйте requests.Session() (или её аналог в вашем стеке) вместо создания нового клиента на каждый запрос.

Токен reCAPTCHA отклонён при отправке

Токен reCAPTCHA v2 живёт около 2 минут. Если между решением CAPTCHA и отправкой формы проходит больше времени — например, из-за очереди задач или медленного парсинга — токен успевает истечь. Решайте CAPTCHA непосредственно перед отправкой, а не заранее.

Страница листинга возвращает 0 результатов без ошибки

Часто это не баг парсера, а CAPTCHA, которая молча подменила содержимое страницы. Проверьте HTML на скрытые элементы CAPTCHA, прежде чем искать ошибку в селекторах.

IP заблокирован после серии CAPTCHA

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

Если проблема не уходит после этих шагов, добавьте логирование самого HTML-ответа при срабатывании _has_captcha — часто оказывается, что площадка отдаёт не reCAPTCHA v2, а Cloudflare Turnstile на конкретном разделе, и код молча ждёт не тот параметр в ответе.

Частые вопросы

Как часто можно опрашивать аукционную площадку, не провоцируя CAPTCHA?

Единого порога нет — он зависит от площадки и от того, авторизован ли запрос. Для отслеживания списка наблюдения безопасный ориентир — раз в 15 минут; для активных ставок в последние минуты интервал приходится сокращать до 30 секунд, и тогда CAPTCHA почти гарантирована — закладывайте решение через API CaptchaAI в сам скрипт, а не как аварийный обработчик ошибок.

Что делать, если CAPTCHA появляется на каждом запросе?

В большинстве случаев причина — отсутствие сохранения сессии между запросами: каждый новый requests.get() без переиспользования Session() выглядит для площадки как новый анонимный визит. Проверьте, что cookies сессии сохраняются между вызовами, и что user-agent не меняется от запроса к запросу.

Законно ли отслеживать публичные объявления аукциона?

Мониторинг публично доступных страниц (цены, ставки, сроки лота) сам по себе не нарушает закон, но сбор персональных данных — например, имён продавцов или контактов из карточек — подпадает под требования вроде 152-ФЗ «О персональных данных» для читателей из РФ и под аналогичные принципы GDPR для трансграничных проектов. Собирайте только те данные, на обработку которых у вас есть основание, и сверяйтесь с условиями использования конкретной площадки.

Можно ли отслеживать ставки в последние минуты аукциона в реальном времени?

Да, но с оговоркой: в последние минуты трафик резко растёт, и вместе с ним растёт частота CAPTCHA. Используйте браузерную автоматизацию с заранее авторизованной сессией, чтобы минимизировать число проверок, а оставшиеся CAPTCHA решайте через API CaptchaAI — скорость решения reCAPTCHA v2 достаточна, чтобы не пропустить финальные ставки.

Следующие шаги

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