Use Cases

验证码墙背后的医疗保健数据收集

医生目录、药品定价库、临床试验注册系统大多是公开数据,但自动化采集常卡在验证码这一步。研究机构和健康科技团队(含港新地区团队)访问 NPI 医生目录、ClinicalTrials.gov 时遇到的是 reCAPTCHA v2 与图片验证码,而非国内常见的 GeeTest(极验)——Google 服务不稳定时挑战框也常加载失败。本文用 Python 演示如何用 CaptchaAI 处理这几类验证码。


医生目录采集实战

采集流程说明

HealthcareDataCollector 封装了 reCAPTCHA v2 与图片验证码的识别逻辑。

批量导出

batch_provider_lookup 遍历科室和地区组合,自动导出 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",
)

验证码还会出现在哪些医疗数据源

  • 医生/机构目录 (NPI):图片验证码 · 查询结果 · 网络覆盖分析
  • 药品定价门户:reCAPTCHA v2 · 药品价格 · 价格透明研究
  • 临床试验注册系统:reCAPTCHA v2 · 试验数据 · 科研分析
  • 保险处方集网站:reCAPTCHA v2 · 承保清单 · 处方比较
  • 州执照委员会:图片验证码 · 资质核验 · 背调
  • 医院质量评级网站:Cloudflare Turnstile · 质量指标 · 绩效分析

临床试验数据批量采集

collect_clinical_trials 复用同一套 reCAPTCHA v2 识别逻辑,批量拉取招募中的试验数据。

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

数据隐私、合规边界与故障排查

数据类型 敏感度 处理建议
医生/机构目录 低(公开信息) 可安全采集
药品定价 低(公开定价) 符合透明度要求
临床试验元数据 低(公共信息) 适合科研用途
患者评价 中等 先做匿名化处理
保险计划详情 低(公开费率) 可用于方案比较

重要提示: 切勿采集受保护的健康信息 (PHI),只处理公开、非患者特定数据——数据安全法与个人信息保护法 (PIPL) 下标准同样如此。

常见故障排查

  • 图片验证码识别不出来:图片质量差 → 重试生成新图片
  • 医生目录搜索返回空结果:验证码未解决就提交 → 先解决验证码再提交
  • 药品价格因地区不同:地理定价 → 对齐查询邮编与出口地区
  • 多页会话中途过期:门户会话超时 → 尽快完成整套搜索
  • 批量查询被限流:请求过密 → 每次请求间加 5–10 秒

常见问题

采集医疗保健定价数据合规吗?

监管方向鼓励定价透明,公开的医生目录与药品定价数据通常可合规采集,红线是患者个人信息。

医生目录、药品定价、临床试验这几类验证码,CaptchaAI 都能处理吗?

能。这几类门户常见 reCAPTCHA v2 和图片验证码;若目标站点用 Cloudflare Turnstile,替换求解方法即可。

批量抓取多个科室、地区时,怎么避免被限流?

控制并发、请求间加延迟,让访问出口与查询地区对齐;batch_provider_lookup 的 5 秒延迟只是起点。


相关指南


把医疗数据采集从手工核对变成稳定流程——获取你的 CaptchaAI API Key,自动化医生目录与定价查询。

该文章已禁用评论。