ブログに戻る

医療データ解析のためのプロキシ:ブロックなしで情報を収集する方法

医療データを安全に解析する方法を学びましょう。臨床研究、薬のデータベース、医学雑誌からのデータをブロックされずに取得する方法です。

📅2026年3月9日
```html

医療データのパースは、プロキシの選択に特別なアプローチを必要とするタスクです。医療ポータル、臨床研究データベース、製薬リソースは、データの自動収集に対する高度な保護システムを使用しています。この記事では、安全に医療情報をパースするためのプロキシの正しい設定方法、ブロックを回避する方法、必要なデータを効率的に収集する方法を解説します。

なぜ医療サイトはパースをブロックするのか

医療ポータルやデータベースは、いくつかの理由から自動情報収集に対して特に敏感です。第一に、多くのサイトは商業ベースで運営されており、データへのアクセスを有料サブスクリプションを通じて販売しています。自動パースは利用規約やライセンス契約に違反する可能性があります。

第二に、医療データにはしばしば法律で保護された機密情報が含まれています(米国のHIPAA、欧州のGDPR)。リソースの所有者は、そのようなデータへのアクセスを制御し、無断での配布を防ぐ義務があります。そのため、彼らは高度な保護システムを使用しています:

  • レートリミティング — 一つのIPアドレスからの一定時間内のリクエスト数の制限(通常は1分間に10〜50リクエスト)
  • フィンガープリンティング — ブラウザの特性、HTTPヘッダー、リソースの読み込み順序の分析
  • CAPTCHA — 疑わしい活動時に作動するreCAPTCHA v3のようなシステム
  • IPブロック — データセンターのIPアドレスの一時的または永久的なブロック
  • Cloudflareおよび類似サービス — CDNレベルでのボットからの保護

第三の理由は、サーバーへの負荷です。医療データベースはしばしば数百万件のレコードを含み、大量のパースはインフラに大きな負荷をかける可能性があります。そのため、管理者は自動データ収集と戦うために、ボットに特有の行動パターンを追跡しています:リクエスト間の同じ間隔、ページの線形な巡回、JavaScriptやクッキーの不在。

重要: 医療データのパースを開始する前に、必ずサイトの利用規約と適用される法律を確認してください。一部のデータは著作権で保護されているか、患者の個人情報を含む可能性があります。あなたの活動が合法であり、第三者の権利を侵害しないことを確認してください。

医療データに適したプロキシの種類

プロキシの種類の選択は、医療データのパースの成功にとって非常に重要です。異なるソースは異なるアプローチを必要とします。主なプロキシの種類とその適用性を見てみましょう:

プロキシの種類 利点 欠点 使用するタイミング
データセンターのプロキシ 高速(100Mbps以上)、低コスト、安定した接続 検出されやすく、保護されたサイトでは頻繁にブロックされる 厳しい保護がないオープンデータベース(PubMed、WHO)
レジデンシャルプロキシ 実際の家庭ユーザーのIP、ブロックのリスクが低い、Cloudflareを通過する コストが高く、速度が変動する、安定性に欠けることがある 保護された商業データベース(Elsevier、Springer)、Cloudflareを使用しているサイト
モバイルプロキシ 最大の信頼性(モバイルオペレーターのIP)、ほとんどブロックされない 最も高価で、地理的制限があり、遅くなることがある 特に保護されたリソース、レジデンシャルプロキシが効果がないとき
ISPプロキシ データセンターの速度 + レジデンシャルの信頼性、静的IP 平均的なコスト、限られた入手可能性 安定性が必要な場合の単一IPからの長期的なパース

医療データのパースのほとんどのタスクには、レジデンシャルプロキシの使用が推奨されます。コストと効率の最適なバランスを提供します。データセンターのプロキシは、保護のないオープンソースにのみ適しています。モバイルプロキシは、他のタイプが機能しない場合の最終手段として使用すべきです。

特定のソースに対する選択の推奨

  • PubMed、PubMed Central — データセンターのプロキシで十分ですが、1秒間に3リクエストの制限があります
  • ClinicalTrials.gov — データセンターのプロキシ、公式APIがあります
  • Elsevier、Springer、Wiley — レジデンシャルプロキシが必須で、高度なフィンガープリンティングを使用しています
  • DrugBank、RxList — レジデンシャルプロキシ、パースに対する積極的な保護があります
  • FDA、EMAデータベース — データセンターのプロキシが適していますが、パース速度は遅いです

主な医療データのソースとその保護

医療データは多くのソースに分散しており、それぞれに特有の仕様と保護レベルがあります。これらの特性を理解することで、パース戦略を適切に設定できます。

オープンな政府データベース

PubMed/PubMed Central — 最大の医療出版物データベースで、3500万件以上のレコードを含みます。米国国立医学図書館(NLM)は、データへのアクセスのための公式API E-utilitiesを提供しています。ウェブインターフェースの直接パースは可能ですが、1つのIPからのリクエストは1秒間に3回に制限されています。制限を超えると、24時間の一時的なブロックが発生します。

ClinicalTrials.gov — 臨床研究のデータベースで、220か国で40万件以上の研究情報を含みます。また、プログラムによるアクセスのためのAPIも提供しています。ウェブインターフェースはレートリミティングで保護されており、1つのIPから5分間に最大100リクエストが可能です。基本的なボット保護が使用されていますが、Cloudflareは使用されていません。

FDA医薬品データベース — FDAによって承認された医薬品のデータベースです。ウェブインターフェースとAPI openFDAを通じてオープンアクセスがあります。制限:匿名ユーザーには1分間に240リクエスト、APIキーを使用する場合は1分間に1000リクエストが許可されています。ブロックは稀ですが、攻撃的なパースの場合は可能性があります。

商業的な学術出版社

Elsevier (ScienceDirect) — 科学文献の最大の出版社の一つです。Cloudflare、ブラウザのフィンガープリンティング、ユーザーの行動分析などの多層的な保護を使用しています。自動ダウンロードのパターンを検出し、記事への連続アクセス、JavaScriptの不在、非典型的なUser-Agentを検出します。パースが検出されると、アカウントレベルでIPをブロックし、全機関をブロックする可能性があります。完全なブラウザのエミュレーションとローテーションを伴うレジデンシャルプロキシの使用が必須です。

Springer Nature — 同様の保護があり、ページのスクロール速度やマウスの動きを追跡します。ボット検出のために機械学習を使用しています。1つのIPから1時間に10〜15記事をパースすることが推奨されており、リクエスト間にランダムな遅延を設けることが重要です。

Wiley Online Library — 保護はあまり攻撃的ではありませんが、プロキシの使用が必要です。1つのIPから1時間に約50リクエストを許可し、ブロックは発生しません。アクティビティを追跡するためにセッションクッキーを使用しています。

製薬データベース

DrugBank — 医薬品に関する包括的なデータベースです。無料版はウェブインターフェースに制限されており、商業版はAPIとデータのエクスポートを提供します。ウェブ版はCloudflareとレートリミティングで保護されており、最大20リクエスト/分です。クッキーやJavaScriptの不在で自動化を検出します。

RxList、Drugs.com — 消費者向けの人気のある医薬品ガイドです。Cloudflareを使用しており、パースに対して積極的に対抗しています。データセンターのIPをほぼ瞬時にブロックします。レジデンシャルプロキシと遅いパース速度(1分間に5〜10ページ)が必要です。

長期的なパースのためのIPローテーションの設定

IPアドレスの正しいローテーションは、医療データのパースの成功にとって重要な要素です。主に2つのアプローチがあります:リクエストレベルでのローテーションと時間によるローテーションです。

リクエストレベルでのローテーション

このアプローチでは、各リクエストが新しいIPアドレスを通じて送信されます。これにより、ブロックのリスクが最小限に抑えられますが、クッキーを通じてセッションを追跡するサイトでは問題が発生する可能性があります。セッションの状態を維持する必要がないリストやカタログのパースに適しています。

ほとんどのレジデンシャルプロキシプロバイダーは、特別なエンドポイントを介して自動ローテーションを提供します。たとえば、ローテーティングプロキシエンドポイントを使用する場合、各新しいTCP接続は新しいIPを取得します。これは、Pythonのrequestsのようなライブラリで自動的に機能します。デフォルトでは、各リクエストのために新しい接続が作成されます。

時間によるローテーション(スティッキーセッション)

スティッキーセッションでは、特定の時間(通常は5〜30分)にわたって1つのIPアドレスを使用でき、その後自動的に変更されます。これは、認証を必要とするサイトやクッキーを通じてセッションの状態を追跡するサイトに便利です。1つのIPから複数のページをパースし、実際のユーザーの行動を模倣し、その後IPが自動的に変更されます。

医療サイトには、10〜15分のスティッキーセッションの使用が推奨されます。この時間内に10〜20ページをパースでき(遅延に応じて)、その後IPが変更され、「新しいセッション」が開始されます。これは自然に見え、検出リスクを低減します。

IPアドレスプールのサイズ

長期的なパースには、利用可能なIPアドレスプールのサイズが重要です。1週間にわたって同じ100のIPセットを使用していると、サイトはパターンに気づき、すべてのアドレスをブロックする可能性があります。レジデンシャルプロキシは通常、数百万のIPへのアクセスを提供し、同じアドレスの再利用をほぼ排除します。

データセンターのプロキシを使用する場合は、平均的なボリューム(1ヶ月に10,000〜50,000ページ)のパースのために、最低でも500〜1000のIPプールを持つことをお勧めします。大規模なパース(数十万ページ)には、巨大なIPプールを持つレジデンシャルプロキシを使用するのが最適です。

異なるソースに対するローテーションのアドバイス:

  • PubMed — ローテーションは必須ではなく、レート制限を守るために1つのIPで十分
  • 商業出版社 — スティッキーセッション10〜15分、新しいIPは15〜20ページごと
  • 製薬データベース — 各リクエストごとのローテーションまたはスティッキーセッション5分
  • Cloudflareを使用しているサイト — スティッキーセッションは必須で、リクエストレベルでのローテーションは機能しません

プロキシを使用したパースのためのPythonコード例

人気のあるPythonライブラリを使用して医療データのパースのためのプロキシ設定の実践的な例を見てみましょう。基本的な例から始めて、徐々に複雑にしていきます。

requestsライブラリを使用した基本設定

import requests
from time import sleep
import random

# プロキシの設定(あなたのデータに置き換えてください)
PROXY_HOST = "proxy.example.com"
PROXY_PORT = "8080"
PROXY_USER = "username"
PROXY_PASS = "password"

proxies = {
    'http': f'http://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}',
    'https': f'http://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}'
}

# 実際のブラウザを模倣するためのヘッダー
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
    'Accept-Language': 'en-US,en;q=0.9',
    'Accept-Encoding': 'gzip, deflate, br',
    'DNT': '1',
    'Connection': 'keep-alive',
    'Upgrade-Insecure-Requests': '1'
}

# PubMedへのリクエストの例
url = "https://pubmed.ncbi.nlm.nih.gov/?term=diabetes"

try:
    response = requests.get(url, proxies=proxies, headers=headers, timeout=30)
    print(f"ステータスコード: {response.status_code}")
    print(f"コンテンツの長さ: {len(response.content)}")
    
    # リクエスト間の遅延を追加(PubMedには必須)
    sleep(random.uniform(1.0, 3.0))
    
except requests.exceptions.RequestException as e:
    print(f"エラー: {e}")

ローテーションとリトライロジックを使用した高度な設定

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from time import sleep
import random

class ProxyRotator:
    def __init__(self, proxy_list):
        """
        proxy_list: プロキシの辞書のリスト
        [{'http': 'http://user:pass@host:port', 'https': '...'}, ...]
        """
        self.proxy_list = proxy_list
        self.current_index = 0
    
    def get_next_proxy(self):
        """リストから次のプロキシを取得する"""
        proxy = self.proxy_list[self.current_index]
        self.current_index = (self.current_index + 1) % len(self.proxy_list)
        return proxy

def create_session_with_retries():
    """エラー時に自動的にリトライするセッションを作成する"""
    session = requests.Session()
    
    # 自動リトライの設定
    retry_strategy = Retry(
        total=3,  # 最大3回の試行
        backoff_factor=1,  # 試行間の遅延:1、2、4秒
        status_forcelist=[429, 500, 502, 503, 504],  # リトライするコード
        allowed_methods=["GET", "POST"]
    )
    
    adapter = HTTPAdapter(max_retries=retry_strategy)
    session.mount("http://", adapter)
    session.mount("https://", adapter)
    
    return session

def scrape_with_rotation(urls, proxy_rotator):
    """プロキシのローテーションを使用してURLのリストをパースする"""
    session = create_session_with_retries()
    results = []
    
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Language': 'en-US,en;q=0.9',
    }
    
    for url in urls:
        # 各リクエストのために新しいプロキシを取得する
        proxy = proxy_rotator.get_next_proxy()
        
        try:
            response = session.get(
                url, 
                proxies=proxy, 
                headers=headers, 
                timeout=30
            )
            
            if response.status_code == 200:
                results.append({
                    'url': url,
                    'status': 'success',
                    'content_length': len(response.content)
                })
                print(f"✓ 成功: {url}")
            else:
                results.append({
                    'url': url,
                    'status': 'failed',
                    'error': f"ステータスコード: {response.status_code}"
                })
                print(f"✗ 失敗: {url} (ステータス: {response.status_code})")
        
        except requests.exceptions.RequestException as e:
            results.append({
                'url': url,
                'status': 'error',
                'error': str(e)
            })
            print(f"✗ エラー: {url} ({e})")
        
        # リクエスト間のランダムな遅延(重要!)
        sleep(random.uniform(2.0, 5.0))
    
    return results

# 使用例
proxy_list = [
    {
        'http': 'http://user1:pass1@proxy1.example.com:8080',
        'https': 'http://user1:pass1@proxy1.example.com:8080'
    },
    {
        'http': 'http://user2:pass2@proxy2.example.com:8080',
        'https': 'http://user2:pass2@proxy2.example.com:8080'
    }
]

rotator = ProxyRotator(proxy_list)

urls_to_scrape = [
    "https://pubmed.ncbi.nlm.nih.gov/?term=diabetes",
    "https://pubmed.ncbi.nlm.nih.gov/?term=cancer",
    "https://pubmed.ncbi.nlm.nih.gov/?term=covid"
]

results = scrape_with_rotation(urls_to_scrape, rotator)

JavaScriptを使用したサイトのためのSeleniumの使用

多くの現代の医療サイトは、コンテンツを読み込むためにJavaScriptを使用しています。このような場合、ヘッドレスブラウザが必要です:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time

def create_proxy_driver(proxy_host, proxy_port, proxy_user, proxy_pass):
    """プロキシを使用したChrome WebDriverを作成する"""
    
    chrome_options = Options()
    
    # ヘッドレスモード(GUIなし)
    chrome_options.add_argument('--headless')
    chrome_options.add_argument('--no-sandbox')
    chrome_options.add_argument('--disable-dev-shm-usage')
    
    # プロキシの設定
    chrome_options.add_argument(f'--proxy-server=http://{proxy_host}:{proxy_port}')
    
    # 自動化の無効化(検出を回避するために重要)
    chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
    chrome_options.add_experimental_option('useAutomationExtension', False)
    
    # User-Agent
    chrome_options.add_argument('--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36')
    
    driver = webdriver.Chrome(options=chrome_options)
    
    # 認証付きプロキシの場合は拡張機能を使用する必要があります
    # またはcapabilitiesを介して設定する(より複雑なオプション)
    
    return driver

def scrape_with_selenium(url, driver):
    """JavaScriptの読み込みを待ってページをパースする"""
    
    driver.get(url)
    
    # 要素の読み込みを待つ(例えば、検索結果)
    try:
        wait = WebDriverWait(driver, 10)
        results = wait.until(
            EC.presence_of_element_located((By.CLASS_NAME, "results-article"))
        )
        
        # データの抽出
        articles = driver.find_elements(By.CLASS_NAME, "results-article")
        
        data = []
        for article in articles:
            try:
                title = article.find_element(By.CLASS_NAME, "docsum-title").text
                authors = article.find_element(By.CLASS_NAME, "docsum-authors").text
                
                data.append({
                    'title': title,
                    'authors': authors
                })
            except:
                continue
        
        return data
        
    except Exception as e:
        print(f"要素の待機中にエラーが発生しました: {e}")
        return []

# 使用例
proxy_host = "proxy.example.com"
proxy_port = "8080"
proxy_user = "username"
proxy_pass = "password"

driver = create_proxy_driver(proxy_host, proxy_port, proxy_user, proxy_pass)

try:
    url = "https://pubmed.ncbi.nlm.nih.gov/?term=diabetes"
    results = scrape_with_selenium(url, driver)
    
    for result in results:
        print(f"タイトル: {result['title']}")
        print(f"著者: {result['authors']}\n")
        
finally:
    driver.quit()

リクエスト速度の制御とレートリミティングの回避

レートリミティングは、医療サイトがパースを防ぐための主要な保護手段の一つです。リクエスト速度の正しい設定は、ブロックなしでの長期的なパースにとって非常に重要です。

安全な速度の定義

最初のステップは、特定のサイトの制限を特定することです。これは、リクエスト速度を徐々に増加させて、429(リクエストが多すぎます)やブロックのエラーが発生するまで実験的に行うことができます。ほとんどの医療サイトにおける安全な値は:

  • PubMed — 最大1秒間に3リクエスト(公式の推奨)
  • ClinicalTrials.gov — 1分間に20リクエストが安全で、5分間に最大100リクエストが許可されます
  • 商業出版社 — 1つのIPから1時間に10〜15リクエスト
  • 製薬データベース — 1分間に5〜10リクエスト

Pythonでのレートリミッターの実装

import time
from collections import deque

class RateLimiter:
    def __init__(self, max_calls, period):
        """
        max_calls: 最大呼び出し回数
        period: 秒単位の時間期間
        例:RateLimiter(3, 1) = 1秒間に3リクエスト
        """
        self.max_calls = max_calls
        self.period = period
        self.calls = deque()
    
    def __call__(self, func):
        """関数の呼び出し速度を制限するデコレーター"""
        def wrapper(*args, **kwargs):
            now = time.time()
            
            # 期間外の古い呼び出しを削除
            while self.calls and self.calls[0] < now - self.period:
                self.calls.popleft()
            
            # 制限に達した場合、待機
            if len(self.calls) >= self.max_calls:
                sleep_time = self.period - (now - self.calls[0])
                if sleep_time > 0:
                    print(f"レート制限に達しました。{sleep_time:.2f}s待機します。")
                    time.sleep(sleep_time)
                    # 待機後にクリア
                    self.calls.clear()
            
            # 呼び出しの時間を記録
            self.calls.append(time.time())
            
            # 関数を実行
            return func(*args, **kwargs)
        
        return wrapper

# 使用例
@RateLimiter(max_calls=3, period=1)  # 1秒間に3リクエスト
def fetch_pubmed_page(url):
    response = requests.get(url, headers=headers, proxies=proxies)
    return response

# これで関数は自動的にレート制限を守ります
for i in range(10):
    result = fetch_pubmed_page(f"https://pubmed.ncbi.nlm.nih.gov/?term=test&page={i}")
    print(f"ページ {i} を取得しました")

適応型レートリミティング

より高度なアプローチは、サーバーの応答に応じて速度を適応的に変更することです。429や503のエラーが発生した場合、速度を自動的に下げます:

import time
import random

class AdaptiveRateLimiter:
    def __init__(self, initial_delay=1.0, max_delay=60.0):
        self.current_delay = initial_delay
        self.initial_delay = initial_delay
        self.max_delay = max_delay
        self.success_count = 0
    
    def wait(self):
        """次のリクエストまで待機"""
        # 自然さを加えるためにランダム性を追加
        actual_delay = self.current_delay * random.uniform(0.8, 1.2)
        time.sleep(actual_delay)
    
    def on_success(self):
        """成功したリクエスト時に呼び出される"""
        self.success_count += 1
        
        # 10回の成功したリクエスト後に少し速度を上げる
        if self.success_count >= 10:
            self.current_delay = max(
                self.initial_delay,
                self.current_delay * 0.9
            )
            self.success_count = 0
    
    def on_rate_limit(self):
        """429や類似のエラーが発生した場合に呼び出される"""
        # 遅延を倍増しますが、最大値を超えないようにします
        self.current_delay = min(
            self.current_delay * 2,
            self.max_delay
        )
        self.success_count = 0
        print(f"レート制限に達しました!遅延を{self.current_delay:.2f}sに増加します。")
    
    def on_error(self):
        """他のエラーが発生した場合に呼び出される"""
        # 遅延を少し増やします
        self.current_delay = min(
            self.current_delay * 1.5,
            self.max_delay
        )
        self.success_count = 0

# 使用例
limiter = AdaptiveRateLimiter(initial_delay=2.0, max_delay=30.0)

for url in urls_to_scrape:
    limiter.wait()
    
    try:
        response = requests.get(url, proxies=proxies, headers=headers)
        
        if response.status_code == 200:
            limiter.on_success()
            # データを処理
            
        elif response.status_code == 429:
            limiter.on_rate_limit()
            # 後で再試行
            
        else:
            limiter.on_error()
            
    except requests.exceptions.RequestException:
        limiter.on_error()

医療サイトのための正しいヘッダーとUser-Agent

医療サイトは、ボットを検出するためにHTTPヘッダーを分析します。不適切または欠落しているヘッダーは、高品質のプロキシを使用している場合でも、ブロックの一般的な原因です。

必須ヘッダー

各リクエストに必ず含めるべき最小限のヘッダーセット:

headers = {
    # User-Agent — 現在のブラウザを必ず指定
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
    
    # Accept — ブラウザが受け入れるコンテンツのタイプ
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
    
    # Accept-Language — ユーザーの言語
    'Accept-Language': 'en-US,en;q=0.9',
    
    # Accept-Encoding — 圧縮のサポート
    'Accept-Encoding': 'gzip, deflate, br',
    
    # Connection — 接続の維持
    'Connection': 'keep-alive',
    
    # Upgrade-Insecure-Requests — HTTPSへの自動的な切り替え
    'Upgrade-Insecure-Requests': '1',
    
    # DNT — Do Not Track(オプションですが、リアリズムを追加します)
    'DNT': '1',
    
    # Sec-Fetch-* ヘッダー(現代のブラウザにとって重要)
    'Sec-Fetch-Dest': 'document',
    'Sec-Fetch-Mode': 'navigate',
    'Sec-Fetch-Site': 'none',
    'Sec-Fetch-User': '?1',
    
    # Cache-Control
    'Cache-Control': 'max-age=0'
}

User-Agentのローテーション

同じUser-Agentを使用することは疑わしい場合があります。複数の最新のブラウザ間でのローテーションを推奨します:

import random

USER_AGENTS = [
    # WindowsのChrome
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
    
    # MacのChrome
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
    
    # WindowsのFirefox
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
    
    # MacのFirefox
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0',
    
    # MacのSafari
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15',
    
    # WindowsのEdge
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0'
]

def get_random_headers():
    """ランダムなUser-Agentを持つヘッダーを取得する"""
    return {
        'User-Agent': random.choice(USER_AGENTS),
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Language': 'en-US,en;q=0.9',
        'Accept-Encoding': 'gzip, deflate, br',
        'Connection': 'keep-alive',
        'Upgrade-Insecure-Requests': '1',
        'DNT': '1'
    }

# 使用例
for url in urls:
    headers = get_random_headers()
    response = requests.get(url, headers=headers, proxies=proxies)

フォーム用のRefererとOrigin

検索フォームやPOSTリクエストを送信する場合は、必ずRefererとOriginヘッダーを追加してください:

# 検索フォームへのPOSTリクエスト用
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'en-US,en;q=0.9',
    'Accept-Encoding': 'gzip, deflate, br',
    'Content-Type': 'application/x-www-form-urlencoded',
    'Origin': 'https://example.com',
    'Referer': 'https://example.com/search',
    'Connection': 'keep-alive'
}

# フォームデータを持つPOSTリクエスト
data = {
    'query': 'diabetes',
    'page': '1'
}

response = requests.post(
    'https://example.com/search',
    headers=headers,
    data=data,
    proxies=proxies
)

典型的な問題とその解決策

医療データのパース中に特有の問題が発生します。最も一般的な問題とその解決策を見てみましょう。

問題:Cloudflareがすべてのリクエストをブロックする

症状: "Checking your browser"というテキストのページや、Cloudflareに言及した403 Forbiddenエラーが表示されます。

解決策:

  • データセンターの代わりにレジデンシャルプロキシを使用してください — CloudflareはデフォルトでデータセンターのIPをブロックします
  • SeleniumやPuppeteerに切り替えてください — ヘッドレスブラウザはCloudflareのチェックをよりよく通過します
  • Python用のcloudscraperライブラリを使用してください — 基本的なCloudflareの保護を自動的に回避します
  • クッキーとJavaScriptを有効にしてください — Cloudflareはそれらの存在を確認します
  • TLSフィンガープリンティングを追加してください — curl_cffiを使用して、TLSレベルで本物のブラウザを模倣します

問題:429 Too Many Requestsエラーが発生する

症状: 数回の成功したリクエストの後、サーバーが429を返し始めます。

解決策:

  • リクエスト間の遅延を増やしてください — 3〜5秒から始めてみてください
  • IPのローテーションを有効にしてください — 各リクエストを新しいIPで送信することでレートリミティングを解除します
  • 429の応答に含まれるRetry-Afterヘッダーを確認してください — 何秒待つべきかを示します
  • リトライ時に指数的な遅延を使用してください — 1秒、2秒、4秒、8秒など

問題:プロキシが遅いまたは頻繁に切断される

症状: タイムアウトエラー、ページの読み込みが非常に遅い、接続が切れる。

解決策:

  • リクエストのタイムアウトを30〜60秒に増やしてください — レジデンシャルプロキシは遅くなることがあります
  • 地理的に近いプロキシを使用してください — ヨーロッパのサイトをパースする場合は、ヨーロッパのIPを使用してください
  • プロキシプロバイダーの品質を確認してください — 安価なプロキシはしばしば不安定です
  • リトライロジックを追加してください — 接続エラー時に自動的にリクエストを再試行します
  • コネクションプーリングを使用してください — requests.Session()を介してTCP接続を再利用します

問題:サイトが認証またはサブスクリプションを要求する

症状: 記事の完全なテキストへのアクセスが制限され、ログインが必要です。

解決策:

  • 機関アクセスを使用してください — 多くの大学や病院はサブスクリプションを持っています
  • オープンアクセス版の有無を確認してください — 多くの記事はリポジトリを通じて無料で利用可能です
  • パースの代わりにAPIを使用してください — 一部の出版社は研究者向けにAPIを提供しています
  • メタデータ(タイトル、著者、要約)のみをパースしてください — これらは通常無料で利用可能です

問題:JavaScriptコンテンツが読み込まれない

症状: HTMLに必要なデータがなく、読み込みスピナーや空のコンテナのみが表示されます。

解決策:

  • Selenium/Puppeteerに切り替えてください — これらはJavaScriptを実行します
  • APIエンドポイントを見つけてください — ブラウザでDevToolsを開き、Networkタブでデータを持つXHRリクエストを見つけます
  • requests-htmlを使用してください — JavaScriptを実行できるライブラリです
```