Каталоги поставщиков, порталы цен на лекарства и реестры клинических исследований в здравоохранении почти всегда защищены 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 уходит в тот же запрос, для которого он был получен — просроченный токен портал тоже воспримет как повод показать капчу заново.
Похожие руководства
- Автоматизация государственных порталов с CAPTCHA
- Сбор данных для академических исследований
- Ротация резидентных прокси при парсинге
Получите API-ключ CaptchaAI и подключите решение CAPTCHA к парсеру каталогов поставщиков, цен на лекарства и клинических исследований.