Use Cases

Автоматизация сравнения котировок страховых компаний с обработкой CAPTCHA

Форма автостраховки чаще всего падает не из-за логики скрипта, а из-за двух вещей: reCAPTCHA v2 на шаге «Получить тариф» и разрыва многошаговой сессии при смене IP между этапами. Первую проблему решает CaptchaAI, вторую — правильно настроенная sticky-сессия прокси. Ниже — рабочая связка для QA-команд, которые тестируют страховые агрегаторы и сайты отдельных страховщиков.


Почему рвётся многошаговая сессия при смене IP

Расчёт страхового тарифа почти никогда не укладывается в один запрос — это всегда цепочка из нескольких шагов, и сервер страховщика привязывает всю сессию к IP-адресу первого запроса. Смена IP посередине потока не просто прерывает соединение — она обнуляет уже введённые данные, и форма откатывается к первому шагу:

  1. Личные данные (имя, дата рождения, индекс) — тот же IP
  2. Автомобиль/объект (год, марка, модель) — тот же IP
  3. Выбор покрытия (лимиты, франшиза) — тот же IP
  4. Решение CAPTCHA (токен) — тот же IP
  5. Показ котировки — тот же IP

Держите sticky-прокси-сессию с TTL 10–15 минут на весь страховой поток — это дешевле, чем потом разбирать, почему форма сбрасывалась на четвёртом шаге из десяти прогонов.

Для QA-команд из Восточной Европы и СНГ, которые тестируют интеграции американских страховых платформ по аутсорс-контракту, типичная ошибка — гонять весь прогон через один дата-центровый IP. Антибот-система страховщика быстро помечает такой трафик как подозрительный, и CAPTCHA начинает всплывать на каждом шаге вместо одного раза за сессию — резидентный прокси с привязкой к сессии снимает эту проблему.


Где именно страховщики ставят CAPTCHA на пути к котировке

Тип CAPTCHA и момент её появления зависят от вертикали:

Тип страховки CAPTCHA Точка срабатывания Сложность котировки
Автострахование reCAPTCHA v2 Отправка формы котировки Многоэтапная (автомобиль + водитель)
Медицинское страхование reCAPTCHA v2 Сравнение планов Многоэтапная (демографические данные)
Страхование жилья CAPTCHA-изображение Поиск адреса Средняя
Страхование жизни reCAPTCHA v2 Калькулятор тарифа Простая форма
Страхование путешествий reCAPTCHA v2 Invisible Запрос цены Простая форма
Страхование бизнеса reCAPTCHA v3 Контактная форма/котировка Длинная форма

Скрипт сбора котировок у нескольких страховщиков

Ниже — рабочий сборщик на Python: он проходит форму по шагам, решает reCAPTCHA v2 через CaptchaAI и сортирует полученные тарифы по цене.

import requests
import time
import re
import base64

CAPTCHAAI_KEY = "YOUR_API_KEY"
CAPTCHAAI_URL = "https://ocr.captchaai.com"


def solve_recaptcha(sitekey, pageurl):
    resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data={
        "key": CAPTCHAAI_KEY, "method": "userrecaptcha",
        "googlekey": sitekey, "pageurl": pageurl, "json": 1,
    })
    task_id = resp.json()["request"]
    for _ in range(60):
        time.sleep(5)
        result = requests.get(f"{CAPTCHAAI_URL}/res.php", params={
            "key": CAPTCHAAI_KEY, "action": "get",
            "id": task_id, "json": 1,
        })
        data = result.json()
        if data["request"] != "CAPCHA_NOT_READY":
            return data["request"]
    raise TimeoutError("Timeout")


def solve_image_captcha(image_bytes):
    img_b64 = base64.b64encode(image_bytes).decode()
    resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data={
        "key": CAPTCHAAI_KEY, "method": "base64",
        "body": img_b64, "json": 1,
    })
    task_id = resp.json()["request"]
    for _ in range(20):
        time.sleep(3)
        result = requests.get(f"{CAPTCHAAI_URL}/res.php", params={
            "key": CAPTCHAAI_KEY, "action": "get",
            "id": task_id, "json": 1,
        })
        data = result.json()
        if data["request"] != "CAPCHA_NOT_READY":
            return data["request"]
    raise TimeoutError("Timeout")


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

    def get_auto_quote(self, carrier_url, vehicle_data, driver_data, sitekey):
        """Get auto insurance quote from a single carrier."""
        # Step 1: Load quote page
        self.session.get(carrier_url)

        # Step 2: Submit vehicle info
        resp = self.session.post(f"{carrier_url}/vehicle", data=vehicle_data)

        # Step 3: Submit driver info
        resp = self.session.post(f"{carrier_url}/driver", data=driver_data)

        # Step 4: Solve CAPTCHA on quote page
        token = solve_recaptcha(sitekey, f"{carrier_url}/quote")

        # Step 5: Get quote
        resp = self.session.post(f"{carrier_url}/quote", data={
            "g-recaptcha-response": token,
        })

        if resp.status_code == 200:
            return self._parse_quote(resp.text)
        return None

    def compare_carriers(self, carriers, vehicle_data, driver_data):
        """Compare quotes across multiple carriers."""
        quotes = []

        for carrier in carriers:
            try:
                quote = self.get_auto_quote(
                    carrier_url=carrier["url"],
                    vehicle_data=vehicle_data,
                    driver_data=driver_data,
                    sitekey=carrier["sitekey"],
                )
                quotes.append({
                    "carrier": carrier["name"],
                    "status": "success",
                    "quote": quote,
                })
            except Exception as e:
                quotes.append({
                    "carrier": carrier["name"],
                    "status": "failed",
                    "error": str(e),
                })
            time.sleep(5)  # Delay between carriers

        # Sort by price
        successful = [q for q in quotes if q["status"] == "success" and q["quote"]]
        successful.sort(key=lambda x: x["quote"].get("monthly_premium", float("inf")))

        return {
            "quotes": quotes,
            "best_rate": successful[0] if successful else None,
            "total_compared": len(carriers),
            "successful": len(successful),
        }

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

        premium_el = soup.select_one(".premium, .monthly-rate, .quote-amount")
        coverage_el = soup.select_one(".coverage-summary, .plan-details")

        return {
            "monthly_premium": premium_el.get_text(strip=True) if premium_el else "",
            "coverage": coverage_el.get_text(strip=True) if coverage_el else "",
        }


# Usage
collector = InsuranceQuoteCollector(
    proxy="http://user-session-abc:[email protected]:5000"
)

vehicle = {
    "year": "2022",
    "make": "Toyota",
    "model": "Camry",
    "vin": "",
    "mileage": "15000",
}

driver = {
    "age": "35",
    "gender": "M",
    "zip": "90210",
    "driving_record": "clean",
}

carriers = [
    {"name": "Carrier A", "url": "https://carrier-a.example.com/auto", "sitekey": "6Lc_xxx1"},
    {"name": "Carrier B", "url": "https://carrier-b.example.com/auto", "sitekey": "6Lc_xxx2"},
    {"name": "Carrier C", "url": "https://carrier-c.example.com/auto", "sitekey": "6Lc_xxx3"},
]

comparison = collector.compare_carriers(carriers, vehicle, driver)
print(f"Best rate: {comparison['best_rate']}")

Сравнение тарифов на медицинском маркетплейсе

Логика для медицинских маркетплейсов та же самая — меняется только состав данных: вместо марки и модели автомобиля форма запрашивает демографические данные заявителя.

def compare_health_plans(marketplace_url, demographics, sitekey):
    """Compare health insurance plans on marketplace sites."""
    collector = InsuranceQuoteCollector(
        proxy="http://user-session-xyz:[email protected]:5000"
    )

    # Load marketplace
    collector.session.get(marketplace_url)

    # Submit demographics
    collector.session.post(f"{marketplace_url}/demographics", data=demographics)

    # Solve CAPTCHA for plan results
    token = solve_recaptcha(sitekey, f"{marketplace_url}/plans")

    resp = collector.session.post(f"{marketplace_url}/plans", data={
        "g-recaptcha-response": token,
    })

    if resp.status_code == 200:
        from bs4 import BeautifulSoup
        soup = BeautifulSoup(resp.text, "html.parser")
        plans = []
        for card in soup.select(".plan-card, .insurance-plan"):
            plans.append({
                "name": card.select_one(".plan-name").get_text(strip=True) if card.select_one(".plan-name") else "",
                "premium": card.select_one(".premium").get_text(strip=True) if card.select_one(".premium") else "",
                "deductible": card.select_one(".deductible").get_text(strip=True) if card.select_one(".deductible") else "",
            })
        return plans

    return []

Типичные сбои и их устранение

Проблема Причина Решение
Форма котировки сбрасывается на середине потока IP менялся между шагами Использовать sticky-прокси-сессию
«Невозможно предоставить котировку» Не прошла валидация данных Проверить обязательные поля
CAPTCHA появляется повторно Первый токен истёк до отправки Решить CAPTCHA заново сразу же
Тариф отличается от расчёта вручную Разошлись cookies или сессия Начать чистую сессию
Rate limiter блокирует запросы Слишком много котировок с одного IP Снизить темп, менять IP между страховщиками

Если тестовый прогон логирует персональные данные заявителя — ФИО, дату рождения, адрес, — собирайте только то, на обработку чего у вас есть основание. Для команд, работающих с данными в РФ, это требование 152-ФЗ «О персональных данных»; для трансграничных проектов уместна аналогичная дисциплина в духе GDPR.


Чек-лист перед первым прогоном

Прежде чем гонять полный список страховщиков, проверьте эти пять пунктов — это экономит часы отладки на типичных кейсах из раздела FAQ ниже:

  • sitekey и pageurl сняты с реальной отрисовки виджета, а не с адреса iframe
  • sticky-прокси-сессия держит один IP на весь поток, TTL ≥ 10 минут
  • обязательные поля формы (индекс, дата рождения, VIN) прошли локальную валидацию до отправки
  • лог прогона не хранит персональные данные заявителя дольше, чем нужно для проверки
  • частота запросов на одного страховщика не превышает 20–50 котировок в сутки

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

Как извлечь sitekey и pageurl, если форма котировки открывается в iframe или подгружается через AJAX?

Откройте DevTools → Network и найдите запрос к домену Google (recaptcha/api2/anchor или api.js) — параметр k в его URL и есть sitekey. В качестве pageurl берите адрес страницы, где виджет реально отрисован, а не адрес iframe.

Что делать, если после решения reCAPTCHA страховщик всё равно отклоняет заявку с ошибкой валидации?

Почти всегда дело не в CAPTCHA, а в несовпадении данных формы — проверьте формат индекса, даты рождения и VIN. Решённый токен CAPTCHA не спасает от отклонения по некорректным данным.

Почему сессия обрывается, если IP меняется между вводом данных и решением CAPTCHA?

Сервер страховщика привязывает весь многошаговый поток к IP-адресу первого запроса — это защита от подмены данных между шагами. Смена IP посередине читается сервером как новая сессия, и уже введённые данные теряются.

Сколько котировок в день можно безопасно собирать с одного IP?

На практике 20–50 котировок на одного страховщика в сутки с паузами между запросами. Выше этого порога чаще срабатывают rate limiter и CAPTCHA начинает появляться на каждом шаге, а не один раз за сессию.

Какие данные из формы котировки стоит логировать при тестировании?

Логируйте только то, что нужно для проверки самого потока: код ответа, факт решения CAPTCHA, итоговый тариф. Персональные данные тестового заявителя (ФИО, дату рождения, адрес) лучше не хранить дольше, чем требует сама проверка.


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


Автоматизируйте сравнение котировок у нескольких страховщиков — получите API-ключ CaptchaAI и подключите решение reCAPTCHA к своему пайплайну.

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