Use Cases

Pengumpulan Data Healthcare di Balik CAPTCHA

Direktori penyedia layanan kesehatan, portal harga obat, dan registrasi uji klinis yang bersifat publik umumnya boleh dikumpulkan untuk riset dan analitik — CAPTCHA di baliknya adalah mekanisme anti-bot, bukan larangan hukum. Tantangannya murni teknis: reCAPTCHA v2 mendominasi portal modern, image CAPTCHA masih bertahan di direktori lama, dan sesekali Cloudflare Turnstile muncul di halaman penilaian rumah sakit.

Artikel ini memberi Anda dua scraper Python siap pakai — direktori penyedia bergaya NPI dan registrasi uji klinis — plus tabel kepatuhan data yang wajib dicek sebelum menjalankan keduanya dalam skala besar.


Di Mana CAPTCHA Menghambat Data Kesehatan

Sumber Data Jenis CAPTCHA Data yang Diambil Kegunaan
Direktori penyedia (NPI) Image CAPTCHA Pencarian dokter/fasilitas Kecukupan jaringan
Portal harga obat reCAPTCHA v2 Harga obat Transparansi harga
Registrasi uji klinis reCAPTCHA v2 Data dan hasil uji coba Analisis riset
Formularium asuransi reCAPTCHA v2 Daftar cakupan obat Perbandingan formularium
Badan perizinan negara bagian Image CAPTCHA Verifikasi lisensi Pemeriksaan kredensial
Penilaian kualitas rumah sakit Cloudflare Turnstile Metrik kualitas Analisis kinerja

Pola yang sama berlaku untuk direktori fasilitas kesehatan publik di pasar lain — misalnya direktori faskes ala BPJS Kesehatan di Indonesia atau layanan pembanding harga obat ala GoodRx di AS — karena keduanya menghadapi tekanan bot scraping yang sama dan memasang CAPTCHA sebagai pengaman lini pertama.


Kepatuhan Data Sebelum Mulai Scraping

Jenis Data Sensitivitas Rekomendasi
Direktori penyedia Rendah (info publik) Umumnya aman dikumpulkan
Harga obat Rendah (harga publik) Diizinkan untuk transparansi
Metadata uji klinis Rendah (registrasi publik) Sesuai untuk penggunaan riset
Ulasan pasien Sedang Anonimkan sebelum dianalisis
Detail paket asuransi Rendah (tarif dipublikasikan) Diizinkan untuk perbandingan

Penting: jangan pernah mengumpulkan protected health information (PHI) — data yang mengidentifikasi pasien individu, seperti nomor rekam medis atau riwayat perawatan personal. Fokus hanya pada data publik yang tidak spesifik pasien.

Di Indonesia, UU Pelindungan Data Pribadi (UU 27/2022) mengatur pemrosesan data pribadi, bukan data agregat yang sudah dipublikasikan resmi oleh lembaga seperti direktori faskes atau portal transparansi harga. Prinsipnya sejalan dengan CMS Price Transparency Rule di AS yang justru mendorong keterbukaan harga obat: kumpulkan data yang memang sudah publik, dan hindari apa pun yang menyentuh identitas pasien individu. Ini bukan nasihat hukum — untuk kasus spesifik, cek dengan tim legal Anda.


Bangun Scraper Direktori Penyedia dengan Python

Kelas HealthcareDataCollector di bawah menangani dua pola sekaligus dalam satu alur: reCAPTCHA v2 untuk portal modern (search_providers, lookup_drug_prices) dan image CAPTCHA sebagai fallback otomatis ketika sitekey tidak tersedia — kondisi umum di direktori legacy yang belum migrasi. batch_provider_lookup melakukan loop kombinasi specialty x location lalu langsung mengekspor hasilnya ke CSV.

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",
)

Kumpulkan Data Registrasi Uji Klinis

Alur untuk registrasi uji klinis lebih sederhana karena hampir selalu memakai reCAPTCHA v2 saja, tanpa fallback image CAPTCHA: kirim kondisi medis dan status recruiting, selesaikan reCAPTCHA v2 di search_url, lalu parse daftar studi yang cocok.

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

Mengatasi Kendala Umum saat Scraping Data Kesehatan

Kendala Penyebab Solusi
Image CAPTCHA tidak terbaca Kualitas gambar rendah Coba ulang — gambar baru otomatis di-generate
Pencarian penyedia kembali kosong CAPTCHA memblokir submit Selesaikan CAPTCHA sebelum mengirim form
Harga obat berbeda per lokasi Harga berbasis geografis Cocokkan lokasi proxy dengan kode pos
Sesi berakhir di tengah pencarian multi-halaman Timeout portal Percepat alur pencarian, kurangi jeda antar halaman
Rate limit saat batch lookup Terlalu banyak request beruntun Tambahkan jeda 5–10 detik antar request
Latensi tinggi dari region jauh Scraper dan portal berada di region berbeda Deploy dekat target (mis. AWS ap-southeast-1 Singapura atau ap-southeast-3 Jakarta) atau naikkan timeout

Pertanyaan Umum seputar Scraping Data Healthcare

Berapa lama waktu penyelesaian reCAPTCHA v2 saat scraping direktori penyedia dalam volume besar?

reCAPTCHA v2 biasanya selesai di bawah 60 detik dengan tingkat keberhasilan tinggi pada tipe yang didukung, sementara image CAPTCHA di direktori legacy jauh lebih cepat — di bawah 0.5 detik. Total waktu batch_provider_lookup lebih ditentukan oleh jumlah kombinasi specialty dan location yang Anda loop, bukan oleh solver itu sendiri — beri jeda antar request dan sesuaikan jumlah thread dengan paket CaptchaAI Anda.

Apakah CaptchaAI bisa menangani Cloudflare Turnstile di portal penilaian kualitas rumah sakit?

Ya. Cloudflare Turnstile didukung penuh dan biasanya clear di bawah 10 detik. Kirim sitekey dan page URL ke endpoint yang sama seperti reCAPTCHA v2, cukup ganti method menjadi turnstile sesuai dokumentasi API.

Apakah UU PDP membatasi pengumpulan direktori penyedia dan harga obat publik?

UU Pelindungan Data Pribadi (UU 27/2022) mengatur data pribadi, bukan data agregat yang sudah dipublikasikan resmi. Direktori penyedia, harga obat publik, dan metadata uji klinis umumnya aman dikumpulkan selama Anda tidak menyimpan data pasien individu. Ini bukan nasihat hukum — untuk kasus spesifik, konsultasikan tim legal Anda.

Apa bedanya menangani image CAPTCHA di direktori NPI dengan reCAPTCHA v2 di portal harga obat?

Image CAPTCHA mengharuskan Anda mengunduh gambar lalu mengirim base64-nya ke solve_image_captcha, sedangkan reCAPTCHA v2 cukup mengirim sitekey dan page URL ke solve_recaptcha. Direktori lama yang belum migrasi biasanya masih pakai image CAPTCHA; portal harga obat modern hampir selalu sudah pindah ke reCAPTCHA v2.

Bisakah satu scraper menangani reCAPTCHA v2 dan image CAPTCHA sekaligus?

Bisa — lihat search_providers pada contoh kode: fungsi ini mengecek dulu apakah sitekey tersedia, lalu memilih solve_recaptcha atau solve_image_captcha secara otomatis. Anda tidak perlu scraper terpisah untuk tiap jenis portal.


Panduan Terkait


Kumpulkan data healthcare secara efisien — dapatkan API key CaptchaAI dan jalankan scraper direktori penyedia serta harga obat tanpa terhambat CAPTCHA.

Komentar dinonaktifkan untuk artikel ini.