Use Cases

Мониторинг билетов на события с обработкой CAPTCHA

Билетные площадки — Ticketmaster, StubHub, AXS, локальные концертные и спортивные сайты — закрывают страницы наличия мест проверкой reCAPTCHA v2 или Cloudflare Turnstile, и обычный парсер вместо свежих данных о местах быстро упирается в капчу. Ниже — рабочий процесс, который решает CAPTCHA через API CaptchaAI на лету и присылает алерт, как только статус билета меняется.

Что делает скрипт из этого руководства:

  • Опрашивает страницы конкретных мероприятий с заданным интервалом.
  • Распознаёт reCAPTCHA v2 и Cloudflare Turnstile по маркерам в HTML и решает их через CaptchaAI.
  • Сравнивает новый статус наличия мест с предыдущим и шлёт алерт только при реальном изменении.
  • Переживает временную недоступность сайта и одиночные сетевые сбои без падения всего процесса.

Как устроен пайплайн мониторинга

Configure events → Check availability → CAPTCHA?
                                           ↓ Yes
                                      Solve via CaptchaAI → Retry
                                           ↓ No
                                      Parse availability → Changed?
                                                             ↓ Yes
                                                         Send alert

Каждое мероприятие проверяется независимо: если CAPTCHA нет, скрипт сразу переходит к разбору страницы; если она появилась, сначала решает её через CaptchaAI и только потом повторяет запрос.


Что понадобится для запуска

  • API-ключ CaptchaAI — получить можно на captchaai.com.
  • Python 3.8+ с библиотекой requests.
  • Резидентный прокси — не обязателен, но заметно снижает частоту CAPTCHA на билетных площадках.
pip install requests

Функция для решения CAPTCHA через CaptchaAI

import requests
import time

API_KEY = "YOUR_API_KEY"


def solve_captcha(method, params):
    """Generic CaptchaAI solver for any supported method."""
    params["key"] = API_KEY
    params["json"] = 1

    submit = requests.post("https://ocr.captchaai.com/in.php", data=params).json()

    if submit.get("status") != 1:
        raise RuntimeError(f"Submit error: {submit.get('request')}")

    task_id = submit["request"]
    initial_wait = 10 if method == "turnstile" else 20
    time.sleep(initial_wait)

    for _ in range(30):
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY, "action": "get", "id": task_id, "json": 1
        }).json()
        if result.get("status") == 1:
            return result["request"]
        if result.get("request") != "CAPCHA_NOT_READY":
            raise RuntimeError(f"Solve error: {result['request']}")
        time.sleep(5)
    raise TimeoutError("Solve timed out")

method определяет решатель CaptchaAI: userrecaptcha для reCAPTCHA v2, turnstile для Cloudflare Turnstile. Пауза перед первым опросом res.php короче для Turnstile — он в среднем решается быстрее, чем reCAPTCHA v2. Если задач много и очередь на стороне CaptchaAI выросла, CAPCHA_NOT_READY будет приходить дольше — цикл из 30 попыток с шагом в 5 секунд оставляет запас почти в 2,5 минуты, прежде чем сорваться в TimeoutError.


Класс TicketMonitor: логика проверки

from datetime import datetime
import json


class TicketMonitor:
    def __init__(self, proxy=None):
        self.session = requests.Session()
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
        })
        if proxy:
            self.session.proxies = {
                "http": f"http://{proxy}",
                "https": f"http://{proxy}"
            }
        self.last_status = {}

    def check_event(self, event):
        """Check ticket availability for an event, solving CAPTCHAs if needed."""
        url = event["url"]
        response = self.session.get(url)

        # Handle CAPTCHA if detected
        if "g-recaptcha" in response.text or "recaptcha" in response.text:
            sitekey = self._extract_sitekey(response.text)
            if sitekey:
                token = solve_captcha("userrecaptcha", {
                    "method": "userrecaptcha",
                    "googlekey": sitekey,
                    "pageurl": url
                })
                response = self.session.post(url, data={
                    "g-recaptcha-response": token
                })

        elif "cf-turnstile" in response.text:
            sitekey = self._extract_turnstile_key(response.text)
            if sitekey:
                token = solve_captcha("turnstile", {
                    "method": "turnstile",
                    "sitekey": sitekey,
                    "pageurl": url
                })
                response = self.session.post(url, data={
                    "cf-turnstile-response": token
                })

        # Parse availability
        availability = self._parse_availability(response.text, event)

        # Check for changes
        event_key = event["name"]
        if event_key in self.last_status:
            if availability != self.last_status[event_key]:
                self._send_alert(event, availability)

        self.last_status[event_key] = availability
        return availability

    def _extract_sitekey(self, html):
        if 'data-sitekey="' in html:
            start = html.index('data-sitekey="') + 14
            end = html.index('"', start)
            return html[start:end]
        return None

    def _extract_turnstile_key(self, html):
        if 'data-sitekey="' in html:
            start = html.index('data-sitekey="') + 14
            end = html.index('"', start)
            return html[start:end]
        return None

    def _parse_availability(self, html, event):
        """Parse ticket availability. Customize per ticketing site."""
        available = "temporarily unavailable" not in html.lower()
        return {
            "event": event["name"],
            "available": available,
            "checked_at": datetime.now().isoformat()
        }

    def _send_alert(self, event, availability):
        """Send availability change notification."""
        status = "AVAILABLE" if availability["available"] else "temporarily unavailable"
        print(f"[ALERT] {event['name']}: {status}")

    def monitor_all(self, events):
        """Check all events and return results."""
        results = []
        for event in events:
            try:
                result = self.check_event(event)
                results.append(result)
                print(f"[OK] {event['name']}: {'available' if result['available'] else 'temporarily unavailable'}")
            except Exception as e:
                print(f"[ERROR] {event['name']}: {e}")
        return results


# Usage
events = [
    {
        "name": "Concert - Madison Square Garden - Aug 15",
        "url": "https://example-tickets.com/event/12345"
    },
    {
        "name": "Basketball Finals - Game 7",
        "url": "https://example-tickets.com/event/67890"
    }
]

monitor = TicketMonitor(proxy="user:[email protected]:8080")
results = monitor.monitor_all(events)

for r in results:
    print(json.dumps(r, indent=2))

check_event определяет тип защиты по маркерам в HTML, решает CAPTCHA нужным методом и повторяет запрос уже с токеном. _parse_availability и _send_alert — два метода, которые почти всегда приходится переписывать под конкретную площадку: у каждого билетного сайта своя вёрстка страницы наличия мест и свой формат текста о распроданных местах.

Ожидаемый результат:

[OK] Concert - Madison Square Garden - Aug 15: available
[OK] Basketball Finals - Game 7: temporarily unavailable

Запуск по расписанию

  1. Разверните скрипт на сервере, который работает постоянно — VPS или небольшой инстанс в облаке подойдёт, отдельный выделенный сервер не нужен.
  2. Добавьте задачу в cron с нужным интервалом, например раз в 15 минут (пример ниже), и направьте вывод в файл лога — так проще разбирать причины пропущенных алертов постфактум.
  3. Для горячих событий сократите интервал до 2–5 минут, но учитывайте, что частые проверки провоцируют CAPTCHA чаще.
# Check every 15 minutes
*/15 * * * * cd /path/to/project && python ticket_monitor.py >> /var/log/tickets.log 2>&1

Сервер в европейском дата-центре или в Казахстане при площадке, хостящейся в США, добавляет пару секунд сетевой задержки ещё до решения CAPTCHA — закладывайте запас по таймауту запроса, особенно при интервале короче 5 минут.


Типичные проблемы и их решения

Проблема Причина Решение
CAPTCHA почти на каждой проверке Один и тот же IP, сессия не сохраняется Сохраняйте cookies, чередуйте резидентные прокси
Блокировка после нескольких проверок Сработало ограничение частоты запросов Увеличьте интервал между проверками, ротируйте прокси
Неверный статус наличия мест Изменилась структура страницы Обновите метод _parse_availability
Медленное решение CAPTCHA Высокая нагрузка на решатель Добавьте повтор с экспоненциальной задержкой

Часто задаваемые вопросы

Как часто проверять билеты, не нарываясь на капчу на каждом шаге?

Раз в 10–30 минут — разумный интервал для фонового мониторинга без резкого роста CAPTCHA. Для горячих событий (распродажа стартовала, известна дата продажи) можно уйти на 2–5 минут, но закладывайте, что доля запросов с CAPTCHA вырастет пропорционально частоте проверки.

Сколько потоков CaptchaAI нужно, чтобы следить за десятком мероприятий одновременно?

Ориентир по тарифам CaptchaAI:

  • BASIC ($15/мес, 5 потоков) — 5–10 площадок с проверкой раз в 10–15 минут.
  • STANDARD ($30/мес, 15 потоков) — больше площадок или интервал короче 5 минут.

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

Токен принят, но сайт снова показывает CAPTCHA — что не так?

Две типичные причины:

  1. Сессия не сохраняется между запросом страницы и отправкой токена — используйте один requests.Session() на весь цикл проверки, а не новую сессию на каждый вызов.
  2. Токен успевает устареть, пока скрипт ждёт между in.php и res.php — увеличьте число попыток опроса, а не паузу между ними.

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

_send_alert в примере просто печатает сообщение в консоль — на практике его заменяют вызовом Telegram-бота, Slack-вебхука или push-уведомления с теми же данными о событии. Логику решения CAPTCHA трогать не нужно, меняется только способ доставки алерта.

Чем мониторинг очереди/зала ожидания отличается от мониторинга CAPTCHA?

Это разные механизмы:

  • Зал ожидания — отдельная страница очереди перед основным сайтом; монитор должен её распознавать и либо ждать, либо повторять запрос через интервал.
  • CaptchaAI решает саму CAPTCHA, которая обычно появляется уже после выхода из очереди, а не очередь ожидания как таковую.

Связанные руководства


Подключите CaptchaAI к своему монитору

Начните отслеживать наличие билетов на captchaai.com — API-ключ выдаётся сразу после регистрации, а решатель встраивается в уже существующий скрипт без переписывания логики парсинга страниц.

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