بازگشت به وبلاگ

پراکسی برای استخراج داده‌های پزشکی: چگونه اطلاعات را بدون مسدودیت جمع‌آوری کنیم

بیاموزید چگونه به طور ایمن داده‌های پزشکی را از تحقیقات بالینی، پایگاه‌های داده داروها و مجلات پزشکی استخراج کنید بدون اینکه مسدود شوید.

📅۱۸ اسفند ۱۴۰۴
```html

پارس کردن داده‌های پزشکی — وظیفه‌ای است که نیاز به رویکرد خاصی در انتخاب پروکسی دارد. پورتال‌های پزشکی، پایگاه‌های داده تحقیقات بالینی و منابع دارویی از سیستم‌های پیشرفته‌ای برای جلوگیری از جمع‌آوری خودکار داده‌ها استفاده می‌کنند. در این مقاله بررسی می‌کنیم که چگونه پروکسی را به‌درستی برای پارس کردن ایمن اطلاعات پزشکی تنظیم کنیم، از مسدود شدن جلوگیری کنیم و داده‌های مورد نیاز را به‌طور مؤثر جمع‌آوری کنیم.

چرا سایت‌های پزشکی پارس کردن را مسدود می‌کنند

پورتال‌های پزشکی و پایگاه‌های داده به‌ویژه در برابر جمع‌آوری خودکار اطلاعات حساس هستند به چند دلیل. اولاً، بسیاری از آن‌ها به‌صورت تجاری فعالیت می‌کنند و دسترسی به داده‌ها را از طریق اشتراک‌های پولی می‌فروشند. پارس کردن خودکار می‌تواند شرایط استفاده و توافق‌نامه‌های مجوز را نقض کند.

ثانیاً، داده‌های پزشکی اغلب حاوی اطلاعات محرمانه‌ای هستند که تحت قوانین (HIPAA در ایالات متحده، GDPR در اروپا) محافظت می‌شوند. مالکان منابع موظف به کنترل دسترسی به این داده‌ها و جلوگیری از انتشار غیرمجاز آن‌ها هستند. بنابراین، آن‌ها از سیستم‌های پیشرفته‌ای برای حفاظت استفاده می‌کنند:

  • محدودیت نرخ — محدود کردن تعداد درخواست‌ها از یک آدرس IP در واحد زمان (معمولاً ۱۰-۵۰ درخواست در دقیقه)
  • فینگرپرینتینگ — تجزیه و تحلیل ویژگی‌های مرورگر، زیرعنوان‌های HTTP، ترتیب بارگذاری منابع
  • CAPTCHA — سیستم‌های نوع reCAPTCHA v3 که در صورت فعالیت مشکوک فعال می‌شوند
  • مسدودسازی IP — مسدودسازی موقت یا دائمی آدرس‌های IP مراکز داده
  • Cloudflare و مشابه‌ها — حفاظت در برابر ربات‌ها در سطح CDN

دلیل سوم — بار روی سرورها. پایگاه‌های داده پزشکی اغلب حاوی میلیون‌ها رکورد هستند و پارس کردن انبوه می‌تواند بار قابل توجهی بر زیرساخت ایجاد کند. بنابراین، مدیران به‌طور فعال با جمع‌آوری خودکار داده‌ها مبارزه می‌کنند و الگوهای رفتاری خاص ربات‌ها را ردیابی می‌کنند: فواصل یکسان بین درخواست‌ها، عبور خطی از صفحات، عدم وجود JavaScript و کوکی‌ها.

مهم: قبل از شروع به پارس کردن داده‌های پزشکی، حتماً شرایط استفاده از سایت و قوانین قابل اجرا را مطالعه کنید. برخی داده‌ها ممکن است تحت حق نشر محافظت شوند یا حاوی اطلاعات شخصی بیماران باشند. اطمینان حاصل کنید که فعالیت شما قانونی است و حقوق اشخاص ثالث را نقض نمی‌کند.

کدام نوع پروکسی برای داده‌های پزشکی انتخاب کنیم

انتخاب نوع پروکسی برای پارس کردن موفق داده‌های پزشکی بسیار مهم است. منابع مختلف نیاز به رویکردهای متفاوتی دارند. بیایید انواع اصلی پروکسی و کاربرد آن‌ها را بررسی کنیم:

نوع پروکسی مزایا معایب چه زمانی استفاده کنیم
پروکسی مراکز داده سرعت بالا (بیش از ۱۰۰ مگابیت بر ثانیه)، هزینه پایین، اتصال پایدار به راحتی شناسایی می‌شوند، اغلب در سایت‌های محافظت‌شده مسدود می‌شوند پایگاه‌های داده باز بدون حفاظت سختگیرانه (PubMed، WHO)
پروکسی‌های مسکونی IP‌های واقعی کاربران خانگی، ریسک مسدود شدن پایین، از Cloudflare عبور می‌کنند هزینه بالاتر، سرعت متغیر، ممکن است ناپایدار باشند پایگاه‌های تجاری محافظت‌شده (Elsevier، Springer)، سایت‌های دارای Cloudflare
پروکسی‌های موبایل اعتماد حداکثری (IP‌های اپراتورهای موبایل)، تقریباً مسدود نمی‌شوند گران‌ترین‌ها، جغرافیای محدود، ممکن است کندتر باشند منابع بسیار محافظت‌شده، زمانی که پروکسی‌های مسکونی کمک نمی‌کنند
پروکسی‌های ISP سرعت مراکز داده + اعتماد پروکسی‌های مسکونی، IP‌های ثابت هزینه متوسط، دسترسی محدود پارس کردن بلندمدت از یک IP، زمانی که ثبات نیاز است

برای اکثر وظایف پارس کردن داده‌های پزشکی، توصیه می‌شود از پروکسی‌های مسکونی استفاده کنید. آن‌ها تعادل بهینه‌ای بین هزینه و کارایی را فراهم می‌کنند. پروکسی‌های مراکز داده فقط برای منابع باز بدون حفاظت مناسب هستند. پروکسی‌های موبایل باید در موارد خاص استفاده شوند، زمانی که سایر انواع کار نمی‌کنند.

توصیه‌ها برای انتخاب منابع خاص

  • PubMed، PubMed Central — پروکسی‌های مراکز داده کافی هستند، اما با محدودیت سرعت تا ۳ درخواست در ثانیه
  • ClinicalTrials.gov — پروکسی‌های مراکز داده، دارای API رسمی
  • Elsevier، Springer، Wiley — پروکسی‌های مسکونی الزامی هستند، از فینگرپرینتینگ پیشرفته استفاده می‌کنند
  • DrugBank، RxList — پروکسی‌های مسکونی، حفاظت فعال در برابر پارس کردن
  • پایگاه‌های داده FDA، EMA — پروکسی‌های مراکز داده مناسب هستند، اما با سرعت پارس پایین

منابع اصلی داده‌های پزشکی و حفاظت آن‌ها

داده‌های پزشکی در میان منابع مختلفی توزیع شده‌اند که هر کدام ویژگی‌ها و سطح حفاظت خاص خود را دارند. درک این ویژگی‌ها به شما کمک می‌کند تا استراتژی پارس کردن را به‌درستی تنظیم کنید.

پایگاه‌های داده دولتی باز

PubMed/PubMed Central — بزرگ‌ترین پایگاه داده‌های پزشکی، حاوی بیش از ۳۵ میلیون رکورد. کتابخانه ملی پزشکی ایالات متحده (NLM) API رسمی E-utilities را ارائه می‌دهد که روش ترجیحی دسترسی به داده‌ها است. پارس کردن مستقیم از رابط وب ممکن است، اما محدود به ۳ درخواست در ثانیه از یک IP است. تجاوز از حد مجاز منجر به مسدود شدن موقت به مدت ۲۴ ساعت می‌شود.

ClinicalTrials.gov — پایگاه داده تحقیقات بالینی، حاوی اطلاعات در مورد بیش از ۴۰۰,۰۰۰ تحقیق در ۲۲۰ کشور. همچنین API برای دسترسی برنامه‌نویسی ارائه می‌دهد. رابط وب تحت حفاظت محدودیت نرخ است — حداکثر ۱۰۰ درخواست در ۵ دقیقه از یک IP. از حفاظت پایه‌ای در برابر ربات‌ها استفاده می‌شود، اما بدون Cloudflare.

پایگاه داده‌های داروهای FDA — پایگاه داده داروهای تأیید شده توسط FDA. دسترسی باز از طریق رابط وب و API openFDA. محدودیت‌ها: ۲۴۰ درخواست در دقیقه برای کاربران ناشناس، ۱۰۰۰ درخواست در دقیقه با کلید API. مسدود شدن‌ها نادر است، اما در صورت پارس کردن تهاجمی ممکن است رخ دهد.

انتشارات علمی تجاری

Elsevier (ScienceDirect) — یکی از بزرگ‌ترین ناشران ادبیات علمی. از حفاظت چندلایه استفاده می‌کند: Cloudflare، فینگرپرینتینگ مرورگر، تجزیه و تحلیل رفتار کاربر. الگوهای دانلود خودکار را شناسایی می‌کند: دسترسی متوالی به مقالات، عدم وجود JavaScript، User-Agent‌های غیرمعمول. در صورت شناسایی پارس کردن، IP را در سطح حساب کاربری مسدود می‌کند و ممکن است کل مؤسسه را مسدود کند. استفاده از پروکسی‌های مسکونی با چرخش و شبیه‌سازی کامل مرورگر الزامی است.

Springer Nature — حفاظت مشابهی دارد، علاوه بر این سرعت اسکرول صفحات و حرکات ماوس را ردیابی می‌کند. از یادگیری ماشین برای شناسایی ربات‌ها استفاده می‌کند. توصیه می‌شود بیش از ۱۰-۱۵ مقاله در ساعت از یک IP پارس کنید، با تأخیرهای تصادفی بین درخواست‌ها.

Wiley Online Library — حفاظت کمتری دارد، اما هنوز هم نیاز به استفاده از پروکسی دارد. حدود ۵۰ درخواست در ساعت از یک IP بدون مسدود شدن مجاز است. از کوکی‌های جلسه برای ردیابی فعالیت استفاده می‌کند.

پایگاه‌های داده دارویی

DrugBank — پایگاه داده جامع داروها. نسخه رایگان محدود به رابط وب است، نسخه تجاری API و خروجی داده‌ها را ارائه می‌دهد. نسخه وب تحت حفاظت Cloudflare و محدودیت نرخ است — حداکثر ۲۰ درخواست در دقیقه. خودکارسازی را از طریق عدم وجود کوکی‌ها و JavaScript شناسایی می‌کند.

RxList، Drugs.com — دایرکتوری‌های محبوب دارو برای مصرف‌کنندگان. از Cloudflare استفاده می‌کنند و به‌طور فعال با پارس کردن مبارزه می‌کنند. IP‌های مراکز داده تقریباً بلافاصله مسدود می‌شوند. نیاز به پروکسی‌های مسکونی و سرعت پارس کند (۵-۱۰ صفحه در دقیقه) دارند.

تنظیم چرخش IP برای پارس کردن بلندمدت

چرخش صحیح آدرس‌های IP — عامل کلیدی موفقیت در پارس کردن داده‌های پزشکی است. دو رویکرد اصلی وجود دارد: چرخش در سطح درخواست‌ها و چرخش بر اساس زمان.

چرخش در سطح درخواست‌ها

در این رویکرد، هر درخواست از طریق یک آدرس IP جدید ارسال می‌شود. این به حداکثر کاهش ریسک مسدود شدن کمک می‌کند، اما ممکن است با سایت‌هایی که سشن‌ها را از طریق کوکی‌ها ردیابی می‌کنند، مشکلاتی ایجاد کند. این روش برای پارس کردن فهرست‌ها و دایرکتوری‌ها مناسب است، جایی که نیاز به حفظ وضعیت سشن نیست.

اکثر ارائه‌دهندگان پروکسی‌های مسکونی چرخش خودکار را از طریق نقطه پایانی خاص ارائه می‌دهند. به‌عنوان مثال، هنگام استفاده از نقطه پایانی پروکسی چرخشی، هر اتصال TCP جدید یک IP جدید دریافت می‌کند. این به‌طور خودکار با کتابخانه‌هایی مانند requests در Python کار می‌کند، زیرا به‌طور پیش‌فرض یک اتصال جدید برای هر درخواست ایجاد می‌شود.

چرخش بر اساس زمان (سشن‌های چسبنده)

سشن‌های چسبنده به شما این امکان را می‌دهند که از یک آدرس IP به مدت معین (معمولاً ۵-۳۰ دقیقه) استفاده کنید، پس از آن به‌طور خودکار تغییر می‌کند. این برای سایت‌هایی که نیاز به احراز هویت دارند یا وضعیت سشن را از طریق کوکی‌ها ردیابی می‌کنند، مفید است. می‌توانید چندین صفحه را از یک IP پارس کنید و رفتار یک کاربر واقعی را شبیه‌سازی کنید، سپس IP به‌طور خودکار تغییر می‌کند.

برای سایت‌های پزشکی، توصیه می‌شود از سشن‌های چسبنده با مدت زمان ۱۰-۱۵ دقیقه استفاده کنید. در این مدت می‌توانید ۱۰-۲۰ صفحه را پارس کنید (بسته به تأخیرها)، پس از آن IP تغییر می‌کند و شما "یک سشن جدید" را آغاز می‌کنید. این طبیعی به نظر می‌رسد و ریسک شناسایی را کاهش می‌دهد.

اندازه استخر آدرس‌های IP

برای پارس کردن بلندمدت، اندازه استخر آدرس‌های IP در دسترس اهمیت دارد. اگر از یک مجموعه ۱۰۰ IP به مدت یک هفته استفاده کنید، سایت ممکن است الگو را شناسایی کرده و همه این آدرس‌ها را مسدود کند. پروکسی‌های مسکونی معمولاً دسترسی به میلیون‌ها IP را فراهم می‌کنند که تقریباً استفاده مجدد از یک آدرس را غیرممکن می‌کند.

هنگام استفاده از پروکسی‌های مراکز داده، توصیه می‌شود حداقل استخر ۵۰۰-۱۰۰۰ IP برای پارس کردن حجم متوسط (۱۰,۰۰۰-۵۰,۰۰۰ صفحه در ماه) داشته باشید. برای پارس کردن در مقیاس بزرگ (صدها هزار صفحه) بهتر است از پروکسی‌های مسکونی با استخرهای بزرگ IP استفاده کنید.

نکته‌ای در مورد چرخش برای منابع مختلف:

  • PubMed — چرخش ضروری نیست، کافی است ۱ IP با رعایت محدودیت نرخ داشته باشید
  • انتشارات تجاری — سشن‌های چسبنده ۱۰-۱۵ دقیقه، IP جدید هر ۱۵-۲۰ صفحه
  • پایگاه‌های داده دارویی — چرخش برای هر درخواست یا سشن‌های چسبنده ۵ دقیقه
  • سایت‌های دارای 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,  # حداکثر ۳ تلاش
        backoff_factor=1,  # تأخیر بین تلاش‌ها: ۱، ۲، ۴ ثانیه
        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': 'موفق',
                    'content_length': len(response.content)
                })
                print(f"✓ موفق: {url}")
            else:
                results.append({
                    'url': url,
                    'status': 'ناموفق',
                    'error': f"کد وضعیت: {response.status_code}"
                })
                print(f"✗ ناموفق: {url} (وضعیت: {response.status_code})")
        
        except requests.exceptions.RequestException as e:
            results.append({
                'url': url,
                'status': 'خطا',
                '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)

استفاده از Selenium برای سایت‌های دارای JavaScript

بسیاری از سایت‌های پزشکی مدرن از JavaScript برای بارگذاری محتوا استفاده می‌کنند. در این موارد، یک مرورگر headless لازم است:

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()
    
    # حالت Headless (بدون 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()

کنترل سرعت درخواست‌ها و دور زدن محدودیت نرخ

محدودیت نرخ — یکی از اصلی‌ترین حفاظت‌های سایت‌های پزشکی در برابر پارس کردن است. تنظیم صحیح سرعت درخواست‌ها برای پارس کردن بلندمدت بدون مسدود شدن بسیار مهم است.

تعیین سرعت ایمن

اولین قدم — تعیین محدودیت‌های سایت خاص است. این کار را می‌توان به‌صورت تجربی انجام داد، با افزایش تدریجی سرعت درخواست‌ها تا زمانی که خطاهای ۴۲۹ (بسیاری از درخواست‌ها) یا مسدود شدن ظاهر شوند. برای اکثر سایت‌های پزشکی، مقادیر ایمن عبارتند از:

  • PubMed — حداکثر ۳ درخواست در ثانیه (توصیه رسمی)
  • ClinicalTrials.gov — ۲۰ درخواست در دقیقه ایمن است، تا ۱۰۰ در ۵ دقیقه مجاز است
  • انتشارات تجاری — ۱۰-۱۵ درخواست در ساعت از یک IP
  • پایگاه‌های داده دارویی — ۵-۱۰ درخواست در دقیقه

پیاده‌سازی محدودکننده نرخ در Python

import time
from collections import deque

class RateLimiter:
    def __init__(self, max_calls, period):
        """
        max_calls: حداکثر تعداد فراخوانی‌ها
        period: دوره زمانی به ثانیه
        به‌عنوان مثال: RateLimiter(3, 1) = ۳ درخواست در ثانیه
        """
        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)  # ۳ درخواست در ثانیه
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} بارگذاری شد")

محدودیت نرخ تطبیقی

رویکرد پیشرفته‌تر — تغییر سرعت به‌صورت تطبیقی بر اساس پاسخ‌های سرور. اگر خطاهای ۴۲۹ یا ۵۰۳ دریافت کردیم، به‌طور خودکار سرعت را کاهش می‌دهیم:

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
        
        # پس از ۱۰ درخواست موفق، کمی سرعت می‌گیریم
        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):
        """هنگامی که خطای ۴۲۹ یا مشابه دریافت می‌شود فراخوانی می‌شود"""
        # تأخیر را دو برابر می‌کنیم، اما بیشتر از حداکثر
        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 = [
    # Chrome در Windows
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
    
    # Chrome در Mac
    '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',
    
    # Firefox در Windows
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
    
    # Firefox در Mac
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0',
    
    # Safari در Mac
    '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',
    
    # Edge در Windows
    '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 همه درخواست‌ها را مسدود می‌کند

علائم: صفحه‌ای با متن "در حال بررسی مرورگر شما" یا خطای ۴۰۳ Forbidden با اشاره به Cloudflare دریافت می‌کنید.

راه‌حل:

  • از پروکسی‌های مسکونی به جای مراکز داده استفاده کنید — Cloudflare به‌طور پیش‌فرض IP‌های مراکز داده را مسدود می‌کند
  • به Selenium یا Puppeteer سوئیچ کنید — مرورگرهای headless بهتر از بررسی‌های Cloudflare عبور می‌کنند
  • از کتابخانه cloudscraper برای Python استفاده کنید — این کتابخانه به‌طور خودکار از حفاظت پایه‌ای Cloudflare عبور می‌کند
  • کوکی‌ها و JavaScript را فعال کنید — Cloudflare وجود آن‌ها را بررسی می‌کند
  • فینگرپرینتینگ TLS را اضافه کنید — از curl_cffi برای شبیه‌سازی مرورگر واقعی در سطح TLS استفاده کنید

مشکل: خطای ۴۲۹ Too Many Requests دریافت می‌کنم

علائم: پس از چند درخواست موفق، سرور شروع به بازگشت ۴۲۹ می‌کند.

راه‌حل:

  • تأخیر بین درخواست‌ها را افزایش دهید — سعی کنید از ۳-۵ ثانیه شروع کنید
  • چرخش IP را فعال کنید — هر درخواست از طریق یک IP جدید محدودیت نرخ را برمی‌دارد
  • زیرعنوان Retry-After را در پاسخ ۴۲۹ بررسی کنید — این نشان می‌دهد که چند ثانیه باید منتظر بمانید
  • از تأخیر نمایی در هنگام تکرار استفاده کنید — ۱ ثانیه، ۲ ثانیه، ۴ ثانیه، ۸ ثانیه و غیره.

مشکل: پروکسی‌ها به آرامی کار می‌کنند یا اغلب قطع می‌شوند

علائم: خطاهای Timeout، بارگذاری صفحات بسیار طولانی، قطع اتصال.

راه‌حل:

  • Timeout در درخواست‌ها را به ۳۰-۶۰ ثانیه افزایش دهید — پروکسی‌های مسکونی ممکن است کندتر باشند
  • از پروکسی‌های جغرافیایی نزدیک استفاده کنید — اگر سایت اروپایی را پارس می‌کنید، از IP‌های اروپایی استفاده کنید
  • کیفیت ارائه‌دهنده پروکسی را بررسی کنید — پروکسی‌های ارزان اغلب ناپایدار هستند
  • منطق تکرار را اضافه کنید — به‌طور خودکار درخواست را در صورت خطای اتصال تکرار کنید
  • از connection pooling استفاده کنید — TCP-اتصالات را از طریق requests.Session() دوباره استفاده کنید

مشکل: سایت نیاز به احراز هویت یا اشتراک دارد

علائم: دسترسی به متن کامل مقالات محدود است، نیاز به ورود دارد.

راه‌حل:

  • از دسترسی مؤسسه‌ای استفاده کنید — بسیاری از دانشگاه‌ها و بیمارستان‌ها اشتراک دارند
  • وجود نسخه‌های Open Access را بررسی کنید — بسیاری از مقالات به‌طور رایگان از طریق مخازن در دسترس هستند
  • از API به جای پارس کردن استفاده کنید — برخی ناشران API برای محققان ارائه می‌دهند
  • فقط متاداده‌ها (زیرعنوان‌ها، نویسندگان، خلاصه‌ها) را پارس کنید — آن‌ها معمولاً به‌طور رایگان در دسترس هستند

مشکل: محتوای JavaScript بارگذاری نمی‌شود

علائم: در HTML داده‌های مورد نیاز وجود ندارد، فقط چرخ‌دنده‌های بارگذاری یا کانتینرهای خالی دیده می‌شوند.

راه‌حل:

  • به Selenium/Puppeteer سوئیچ کنید — آن‌ها JavaScript را اجرا می‌کنند
  • نقطه پایانی API را پیدا کنید — DevTools را در مرورگر باز کنید، برگه Network، و درخواست‌های XHR با داده‌ها را پیدا کنید
  • از requests-html استفاده کنید — کتابخانه‌ای با قابلیت‌های پیشرفته
```