Use Cases

Сбор медицинских данных за стенами CAPTCHA

Каталоги поставщиков, порталы цен на лекарства и реестры клинических исследований в здравоохранении почти всегда защищены CAPTCHA на уровне формы поиска — чаще reCAPTCHA v2, реже CAPTCHA-изображение. Логика сбора данных простая: решаете CAPTCHA через API вроде CaptchaAI, подставляете полученный токен в тот же POST-запрос, которым портал ищет данные, и получаете обычный HTML-ответ вместо капчи. Ниже — рабочий парсер на Python для каталогов поставщиков, цен на лекарства и реестров клинических исследований, а также разбор того, какие данные в здравоохранении безопасно собирать и как не наткнуться на PHI.


Где на порталах здравоохранения встречается CAPTCHA

Источник Тип CAPTCHA Данные Сценарий использования
Справочники поставщиков (NPI) CAPTCHA-изображение Поиск врача или медучреждения Оценка адекватности сети
Порталы цен на лекарства reCAPTCHA v2 Цены на лекарства Прозрачность цен
Реестры клинических исследований reCAPTCHA v2 Данные испытаний, результаты Анализ исследований
Страховые формуляры reCAPTCHA v2 Списки покрытия лекарств Сравнение формуляров
Государственные лицензионные комиссии CAPTCHA-изображение Проверка лицензии Проверка учётных данных
Рейтинги качества больниц Cloudflare Turnstile Показатели качества Анализ эффективности

Что можно собирать без риска, а что — нет

Прежде чем писать парсер, стоит развести данные по чувствительности. Публичные каталоги поставщиков, цены на лекарства и метаданные клинических исследований — это открытая информация, которую регуляторы сознательно держат доступной: например, правило CMS о прозрачности цен в США прямо поощряет публикацию цен на лекарства и услуги. Совсем другое дело — отзывы пациентов и любые записи, которые можно привязать к конкретному человеку: с ними в США работает HIPAA (Health Insurance Portability and Accountability Act), а для команд, которые обрабатывают такие данные из СНГ или ЕС, дополнительно действуют требования 152-ФЗ «О персональных данных» или GDPR — собирайте только те сведения, которые вправе обрабатывать. Практическое правило простое: агрегированные, обезличенные и изначально публичные данные — можно; записи, которые идентифицируют конкретного пациента, — нельзя.

Тип данных Чувствительность Рекомендация
Каталоги поставщиков Низкая (публичная информация) Собирать в целом безопасно
Цены на лекарства Низкая (публичные цены) Разрешено для целей прозрачности
Метаданные клинических исследований Низкая (государственные реестры) Подходит для исследовательских целей
Отзывы пациентов Средняя Обезличивайте перед анализом
Детали страхового плана Низкая (опубликованные тарифы) Разрешено для сравнения

Важно. Никогда не собирайте защищённую медицинскую информацию (PHI) — фамилии, номера полисов или другие данные, которые можно привязать к конкретному пациенту. Работайте только с общедоступными, обезличенными сведениями, не относящимися к конкретному пациенту.


Парсер каталога поставщиков: reCAPTCHA v2 и CAPTCHA-изображение

Класс HealthcareDataCollector ниже ищет провайдеров по специализации и городу, решает reCAPTCHA v2 через пару googlekey/pageurl либо CAPTCHA-изображение через base64, а затем разбирает карточки результатов через BeautifulSoup. Если вы перебираете 10 специализаций в 5 городах — это 50 последовательных поисков за проход; для такого объёма хватает BASIC ($15/мес, 5 потоков). Когда нужно вести несколько городов параллельно, разумнее взять STANDARD ($30/мес, 15 потоков): предсказуемая помесячная стоимость по потокам, а не оплата за каждое решение, обычно удобнее для фрилансеров и небольших агентств, которые выставляют счета клиентам в долларах.

import requests
import time
import re
import base64
from bs4 import BeautifulSoup
import csv

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 HealthcareDataCollector:
    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 search_providers(self, portal_url, specialty, location, sitekey=None):
        """Search provider directory with CAPTCHA handling."""
        resp = self.session.get(portal_url, timeout=30)

        data = {"specialty": specialty, "location": location}

        # Handle CAPTCHA
        if sitekey:
            token = solve_recaptcha(sitekey, portal_url)
            data["g-recaptcha-response"] = token
        else:
            captcha_img = re.search(r'src="(/captcha[^"]+)"', resp.text)
            if captcha_img:
                img_url = portal_url.rstrip("/") + captcha_img.group(1)
                img = self.session.get(img_url)
                data["captcha"] = solve_image_captcha(img.content)

        resp = self.session.post(portal_url, data=data)
        return self._parse_providers(resp.text)

    def lookup_drug_prices(self, pricing_url, drug_name, zip_code, sitekey):
        """Look up drug prices with CAPTCHA solving."""
        # Load search page
        self.session.get(pricing_url)

        # Solve CAPTCHA
        token = solve_recaptcha(sitekey, pricing_url)

        resp = self.session.post(pricing_url, data={
            "drug": drug_name,
            "zip": zip_code,
            "g-recaptcha-response": token,
        })

        if resp.status_code == 200:
            return self._parse_prices(resp.text)
        return []

    def batch_provider_lookup(self, portal_url, specialties, locations, output_file):
        """Batch search across specialties and locations."""
        all_providers = []

        for specialty in specialties:
            for location in locations:
                try:
                    providers = self.search_providers(
                        portal_url, specialty, location,
                    )
                    for p in providers:
                        p["specialty_search"] = specialty
                        p["location_search"] = location
                    all_providers.extend(providers)
                    print(f"{specialty} / {location}: {len(providers)} providers")
                    time.sleep(5)
                except Exception as e:
                    print(f"Error: {specialty} / {location}: {e}")

        # Export
        if all_providers:
            keys = all_providers[0].keys()
            with open(output_file, "w", newline="", encoding="utf-8") as f:
                writer = csv.DictWriter(f, fieldnames=keys)
                writer.writeheader()
                writer.writerows(all_providers)

        return all_providers

    def _parse_providers(self, html):
        soup = BeautifulSoup(html, "html.parser")
        providers = []
        for card in soup.select(".provider-card, .doctor-result, tr.provider"):
            providers.append({
                "name": self._text(card, ".name, .provider-name"),
                "specialty": self._text(card, ".specialty"),
                "address": self._text(card, ".address"),
                "phone": self._text(card, ".phone"),
                "accepting": self._text(card, ".accepting-patients"),
            })
        return providers

    def _parse_prices(self, html):
        soup = BeautifulSoup(html, "html.parser")
        prices = []
        for row in soup.select(".pharmacy-row, .price-result"):
            prices.append({
                "pharmacy": self._text(row, ".pharmacy-name"),
                "price": self._text(row, ".price, .drug-price"),
                "quantity": self._text(row, ".quantity"),
            })
        return prices

    def _text(self, el, selector):
        found = el.select_one(selector)
        return found.get_text(strip=True) if found else ""


# Usage
collector = HealthcareDataCollector(
    proxy="http://user:[email protected]:5000"
)

# Provider search
providers = collector.search_providers(
    portal_url="https://provider-directory.example.com/search",
    specialty="Cardiology",
    location="New York, NY",
)

# Drug pricing
prices = collector.lookup_drug_prices(
    pricing_url="https://drug-prices.example.com/compare",
    drug_name="atorvastatin",
    zip_code="10001",
    sitekey="6Lc_xxxxxxx",
)

Сбор данных клинических исследований

Функция collect_clinical_trials переиспользует тот же collector и просто меняет форму поиска — вместо специализации и локации в запрос уходят диагноз (condition) и статус recruiting. Пригодится, когда нужно регулярно проверять реестр на появление новых испытаний по конкретному диагнозу, не открывая портал вручную.

def collect_clinical_trials(search_url, condition, sitekey):
    """Collect clinical trial data for a medical condition."""
    collector = HealthcareDataCollector(
        proxy="http://user:[email protected]:5000"
    )

    token = solve_recaptcha(sitekey, search_url)
    resp = collector.session.post(search_url, data={
        "condition": condition,
        "status": "recruiting",
        "g-recaptcha-response": token,
    })

    if resp.status_code != 200:
        return []

    soup = BeautifulSoup(resp.text, "html.parser")
    trials = []
    for item in soup.select(".trial-item, .study-result"):
        trials.append({
            "title": collector._text(item, ".title, h3"),
            "status": collector._text(item, ".status"),
            "sponsor": collector._text(item, ".sponsor"),
            "phase": collector._text(item, ".phase"),
            "enrollment": collector._text(item, ".enrollment"),
            "location": collector._text(item, ".location"),
        })

    return trials

Типичные проблемы при сборе данных здравоохранения

Большинство сбоев в этом сценарии сводится к пяти причинам:

Проблема Причина Решение
CAPTCHA-изображение не распознаётся Низкое качество картинки Запросите капчу заново — сервер сгенерирует новое изображение
Поиск поставщика возвращает пустой результат CAPTCHA заблокировала запрос Решите CAPTCHA до отправки формы поиска
Цена на препарат отличается по регионам Геозависимое ценообразование Совместите регион прокси с почтовым индексом запроса
Сессия обрывается на многостраничном результате Тайм-аут портала Проходите поиск быстрее, не растягивайте сессию
Ограничение частоты запросов при пакетном поиске Слишком много запросов подряд Добавьте паузы 5–10 секунд между запросами

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

Какие типы CAPTCHA чаще всего стоят на порталах здравоохранения?

Чаще всего это reCAPTCHA v2 — на порталах цен на лекарства, страховых формулярах и реестрах клинических исследований. CAPTCHA-изображение обычно встречается на справочниках поставщиков NPI и государственных лицензионных комиссиях, а Cloudflare Turnstile — на рейтингах качества больниц.

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

Для последовательного перебора специализаций и городов достаточно BASIC (5 потоков, $15/мес). Если нужно вести несколько городов одновременно и не ждать очереди, переходите на STANDARD (15 потоков, $30/мес) или ADVANCE (50 потоков, $90/мес) — для крупных ежедневных выгрузок по всем штатам.

Законен ли сбор публичных данных о ценах на лекарства и поставщиках?

Да, если данные изначально публичны. Правило CMS о прозрачности цен в США прямо поощряет доступность цен на лекарства и услуги, а справочники поставщиков и реестры клинических исследований — открытые государственные ресурсы. На этом принципе построены целые сервисы вроде GoodRx.

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

Обычно это ограничение частоты запросов, а не защита конкретно от парсинга. Добавьте паузы 5–10 секунд между запросами, не запускайте десятки специализаций и городов одновременно с одного IP и убедитесь, что токен из solve_recaptcha уходит в тот же запрос, для которого он был получен — просроченный токен портал тоже воспримет как повод показать капчу заново.


Похожие руководства


Получите API-ключ CaptchaAI и подключите решение CAPTCHA к парсеру каталогов поставщиков, цен на лекарства и клинических исследований.

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