Use Cases

Collecte de données de santé derrière les murs CAPTCHA

Les portails de santé publics — annuaires de prestataires, bases de prix des médicaments, registres d'essais cliniques — placent un CAPTCHA devant leur moteur de recherche pour bloquer l'accès automatisé. La réponse tient en deux temps : envoyez le CAPTCHA à un service de résolution comme CaptchaAI, puis rejouez le token obtenu dans le formulaire de recherche. Ce guide montre comment monter cette collecte de données de santé proprement, en restant du bon côté du RGPD et en ne touchant qu'à des données non nominatives.


Où le CAPTCHA bloque la collecte de données de santé

Repérer le type de défi avant d'écrire une ligne de code évite de choisir la mauvaise méthode de résolution.

Source Type de CAPTCHA Données visées Cas d'usage
Annuaires de prestataires (NPI) CAPTCHA image Recherche médecin / établissement Adéquation du réseau de soins
Portails de prix des médicaments reCAPTCHA v2 Tarifs des médicaments Transparence tarifaire
Registres d'essais cliniques reCAPTCHA v2 Données d'essai, résultats Analyse de la recherche
Formulaires d'assurance reCAPTCHA v2 Listes de couverture des médicaments Comparaison des formulaires
Ordres et conseils professionnels CAPTCHA image Vérification d'inscription Contrôle des habilitations
Indicateurs qualité des hôpitaux Cloudflare Turnstile Indicateurs de qualité Analyse de performance

Deux familles couvrent la quasi-totalité de ces portails, et CaptchaAI prend en charge les deux avec une seule intégration :

  • reCAPTCHA v2 sur les formulaires de tarification et de recherche clinique ;
  • CAPTCHA image (OCR) sur les annuaires plus anciens, parfois doublé d'un Cloudflare Turnstile.

Confidentialité des données de santé et périmètre RGPD

Avant d'écrire le moindre scraper, fixez le périmètre. Le RGPD range les données de santé parmi les catégories particulières de l'article 9, avec une protection renforcée. La distinction opérationnelle est simple :

  • Vous pouvez collecter les données publiques et non nominatives : annuaires de médecins, tarifs de médicaments, fiches d'essais cliniques publiées.
  • Vous ne collectez pas ce qui identifie un patient : avis nominatifs, dossiers individuels, toute donnée rattachée à une personne.
Type de données Sensibilité Recommandation
Annuaires de prestataires Faible (information publique) Généralement sûr à collecter
Prix des médicaments Faible (tarif public) Autorisé à des fins de transparence
Métadonnées d'essais cliniques Faible (registres publics) Usage approprié pour la recherche
Avis de patients Moyenne Anonymiser avant toute analyse
Détails des contrats d'assurance Faible (tarifs publiés) Autorisé pour la comparaison

Ne collectez jamais de données de santé à caractère personnel. Restez sur les données ouvertes et non nominatives, minimisez ce que vous conservez et vérifiez vos obligations RGPD si vous exploitez ces jeux de données au sein de l'UE.


Extraire un annuaire de prestataires

L'annuaire est le cas le plus courant : vous soumettez une spécialité et une zone géographique, puis vous lisez la liste retournée. Le collecteur ci-dessous suit toujours la même séquence :

  1. charger la page de recherche du portail ;
  2. détecter le type de défi (reCAPTCHA v2 ou CAPTCHA image) ;
  3. envoyer le défi à CaptchaAI et attendre le token ;
  4. rejouer le token dans la requête POST ;
  5. parser la liste de prestataires retournée.

La même classe sert ensuite à la recherche de prix et au traitement par lots.

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:pass@residential.proxy.com: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",
)

Le token reCAPTCHA a une durée de vie courte : envoyez la requête POST dans les secondes qui suivent, sinon le portail le rejette. Pour couvrir plusieurs spécialités et villes, batch_provider_lookup enchaîne les recherches et exporte en CSV, avec un délai entre chaque requête pour ne pas déclencher la limitation de débit.


Récupérer les données d'essais cliniques

Les registres d'essais cliniques exposent des métadonnées publiques. La même mécanique s'applique — reCAPTCHA v2 sur le formulaire, token rejoué dans la requête — mais le parsing cible cette fois les champs propres à l'étude :

  • titre de l'étude et statut de recrutement ;
  • promoteur et phase clinique ;
  • effectif visé et sites participants.
def collect_clinical_trials(search_url, condition, sitekey):
    """Collect clinical trial data for a medical condition."""
    collector = HealthcareDataCollector(
        proxy="http://user:pass@residential.proxy.com: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

Dépannage

La plupart des échecs viennent d'un token expiré ou d'un rythme de requêtes trop soutenu. Voici les cas les plus fréquents et leur correctif.

Problème Cause Correctif
CAPTCHA image illisible Image de mauvaise qualité Relancez : une nouvelle image est générée
La recherche de prestataire renvoie une liste vide Le CAPTCHA a bloqué la requête Résolvez le CAPTCHA avant de soumettre
Le prix d'un médicament change selon la zone Tarification géolocalisée Alignez la localisation du proxy sur le code postal
La session expire sur plusieurs pages Délai d'expiration du portail Enchaînez les recherches rapidement
Requêtes par lots limitées en débit Trop de requêtes Ajoutez des délais de 5 à 10 s entre les appels

Trois réflexes limitent la plupart de ces incidents :

  • envoyez la requête POST dès la réception du token, sans traitement intermédiaire ;
  • faites tourner des proxys résidentiels alignés sur la zone géographique visée ;
  • gardez un délai de 5 à 10 s entre les recherches par lots.

Questions fréquentes

Quels types de CAPTCHA rencontre-t-on sur les portails de santé ?

Surtout du reCAPTCHA v2 sur les formulaires de prix et de recherche clinique, et du CAPTCHA image (OCR) sur les annuaires plus anciens ; certains classements d'hôpitaux passent à Cloudflare Turnstile. CaptchaAI prend en charge ces trois types.

CaptchaAI prend-il en charge hCaptcha pour ces portails ?

Non — hCaptcha n'est pas pris en charge. Pour le reCAPTCHA v2, le CAPTCHA image et Turnstile, l'API répond directement.

Le RGPD s'applique-t-il à la collecte de ces données ?

Les données publiques et non nominatives — annuaires, tarifs, registres d'essais — ne sont pas des données de santé personnelles au sens de l'article 9. Dès qu'une donnée permet d'identifier un patient, ne la collectez pas.

Comment éviter le blocage lors de recherches par lots ?

Espacez les requêtes de 5 à 10 secondes, faites tourner des proxys résidentiels et alignez la géolocalisation du proxy sur le code postal recherché pour rester sous le seuil de limitation de débit.


Guides connexes


Passez d'une collecte fragile à un flux fiable : récupérez votre clé CaptchaAI et automatisez vos recherches de prestataires et de prix.

Les commentaires sont désactivés pour cet article.