Билетные площадки — 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
Запуск по расписанию
- Разверните скрипт на сервере, который работает постоянно — VPS или небольшой инстанс в облаке подойдёт, отдельный выделенный сервер не нужен.
- Добавьте задачу в cron с нужным интервалом, например раз в 15 минут (пример ниже), и направьте вывод в файл лога — так проще разбирать причины пропущенных алертов постфактум.
- Для горячих событий сократите интервал до 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 — что не так?
Две типичные причины:
- Сессия не сохраняется между запросом страницы и отправкой токена — используйте один
requests.Session()на весь цикл проверки, а не новую сессию на каждый вызов. - Токен успевает устареть, пока скрипт ждёт между
in.phpиres.php— увеличьте число попыток опроса, а не паузу между ними.
Как узнавать об изменении статуса без постоянного присмотра за скриптом?
_send_alert в примере просто печатает сообщение в консоль — на практике его заменяют вызовом Telegram-бота, Slack-вебхука или push-уведомления с теми же данными о событии. Логику решения CAPTCHA трогать не нужно, меняется только способ доставки алерта.
Чем мониторинг очереди/зала ожидания отличается от мониторинга CAPTCHA?
Это разные механизмы:
- Зал ожидания — отдельная страница очереди перед основным сайтом; монитор должен её распознавать и либо ждать, либо повторять запрос через интервал.
- CaptchaAI решает саму CAPTCHA, которая обычно появляется уже после выхода из очереди, а не очередь ожидания как таковую.
Связанные руководства
- Как решить reCAPTCHA v2 с помощью API
- Как решить Cloudflare Turnstile с помощью API
- QA-проверка ticketing workflow с помощью решения CAPTCHA
- Реализация логики повтора с помощью CaptchaAI
Подключите CaptchaAI к своему монитору
Начните отслеживать наличие билетов на captchaai.com — API-ключ выдаётся сразу после регистрации, а решатель встраивается в уже существующий скрипт без переписывания логики парсинга страниц.