Volver al blog

Proxies para la extracción de datos médicos: cómo recopilar información sin bloqueos

Descubre cómo extraer datos médicos de estudios clínicos, bases de datos de medicamentos y revistas médicas de forma segura, sin ser bloqueado.

📅9 de marzo de 2026
```html

El scraping de datos médicos es una tarea que requiere un enfoque especial en la selección de proxies. Los portales médicos, las bases de datos de ensayos clínicos y los recursos farmacéuticos utilizan sistemas avanzados de protección contra la recolección automatizada de datos. En este artículo, analizaremos cómo configurar correctamente proxies para un scraping seguro de información médica, evitar bloqueos y recopilar los datos necesarios de manera eficiente.

Por qué los sitios médicos bloquean el scraping

Los portales médicos y las bases de datos son especialmente sensibles a la recolección automatizada de información por varias razones. En primer lugar, muchos de ellos operan con fines comerciales y venden acceso a los datos a través de suscripciones de pago. El scraping automático puede violar los términos de uso y los acuerdos de licencia.

En segundo lugar, los datos médicos a menudo contienen información confidencial, protegida por la legislación (HIPAA en EE. UU., GDPR en Europa). Los propietarios de los recursos están obligados a controlar el acceso a tales datos y prevenir su distribución no autorizada. Por lo tanto, utilizan sistemas avanzados de protección:

  • Rate limiting — limitación del número de solicitudes desde una dirección IP en un período de tiempo (generalmente 10-50 solicitudes por minuto)
  • Fingerprinting — análisis de las características del navegador, encabezados HTTP, orden de carga de recursos
  • CAPTCHA — sistemas tipo reCAPTCHA v3 que se activan ante actividades sospechosas
  • Bloqueos de IP — bloqueo temporal o permanente de direcciones IP de centros de datos
  • Cloudflare y similares — protección contra bots a nivel de CDN

La tercera razón es la carga en los servidores. Las bases de datos médicas a menudo contienen millones de registros, y el scraping masivo puede crear una carga significativa en la infraestructura. Por lo tanto, los administradores luchan activamente contra la recolección automatizada de datos, rastreando patrones de comportamiento característicos de los bots: intervalos iguales entre solicitudes, navegación lineal de páginas, ausencia de JavaScript y cookies.

Importante: Antes de comenzar a hacer scraping de datos médicos, asegúrese de estudiar los términos de uso del sitio y la legislación aplicable. Algunos datos pueden estar protegidos por derechos de autor o contener información personal de pacientes. Asegúrese de que su actividad sea legal y no infrinja los derechos de terceros.

Qué tipo de proxy elegir para datos médicos

La elección del tipo de proxy es crucial para el éxito del scraping de datos médicos. Diferentes fuentes requieren diferentes enfoques. Analicemos los principales tipos de proxies y su aplicabilidad:

Tipo de proxy Ventajas Desventajas Cuándo usar
Proxies de centros de datos Alta velocidad (100+ Mbps), bajo costo, conexión estable Fácilmente detectables, a menudo bloqueados en sitios protegidos Bases de datos abiertas sin protección estricta (PubMed, OMS)
Proxies residenciales IPs reales de usuarios domésticos, bajo riesgo de bloqueo, pasan Cloudflare Mayor costo, velocidad variable, pueden ser inestables Bases comerciales protegidas (Elsevier, Springer), sitios con Cloudflare
Proxies móviles Máxima confianza (IP de operadores móviles), prácticamente no son bloqueados Los más caros, geografía limitada, pueden ser más lentos Recursos especialmente protegidos, cuando los proxies residenciales no ayudan
Proxies ISP Velocidad de centros de datos + confianza de residenciales, IPs estáticas Costo medio, disponibilidad limitada Scraping a largo plazo desde una IP, cuando se necesita estabilidad

Para la mayoría de las tareas de scraping de datos médicos, se recomienda utilizar proxies residenciales. Proporcionan un equilibrio óptimo entre costo y efectividad. Los proxies de centros de datos solo son adecuados para fuentes abiertas sin protección. Los proxies móviles deben utilizarse en casos extremos, cuando otros tipos no funcionan.

Recomendaciones para elegir según fuentes específicas

  • PubMed, PubMed Central — proxies de centros de datos son suficientes, pero con limitación de velocidad a 3 solicitudes por segundo
  • ClinicalTrials.gov — proxies de centros de datos, hay API oficial
  • Elsevier, Springer, Wiley — proxies residenciales son obligatorios, utilizan fingerprinting avanzado
  • DrugBank, RxList — proxies residenciales, protección activa contra scraping
  • Bases de datos de la FDA, EMA — proxies de centros de datos son adecuados, pero con velocidad de scraping lenta

Principales fuentes de datos médicos y su protección

Los datos médicos están distribuidos en múltiples fuentes, cada una con su propia especificidad y nivel de protección. Comprender estas características ayudará a configurar correctamente la estrategia de scraping.

Bases de datos gubernamentales abiertas

PubMed/PubMed Central — la mayor base de publicaciones médicas, contiene más de 35 millones de registros. La Biblioteca Nacional de Medicina de EE. UU. (NLM) proporciona una API oficial E-utilities, que es el método preferido para acceder a los datos. El scraping directo de la interfaz web es posible, pero está limitado a 3 solicitudes por segundo desde una IP. Superar el límite resulta en un bloqueo temporal de 24 horas.

ClinicalTrials.gov — base de datos de ensayos clínicos, contiene información sobre más de 400,000 estudios en 220 países. También proporciona API para acceso programático. La interfaz web está protegida por rate limiting — máximo 100 solicitudes en 5 minutos desde una IP. Se utiliza protección básica contra bots, pero sin Cloudflare.

Base de datos de medicamentos de la FDA — base de datos de medicamentos aprobados por la FDA. Acceso abierto a través de la interfaz web y API openFDA. Limitaciones: 240 solicitudes por minuto para usuarios anónimos, 1000 solicitudes por minuto con clave API. Los bloqueos son raros, pero pueden ocurrir con scraping agresivo.

Editoriales científicas comerciales

Elsevier (ScienceDirect) — uno de los mayores editores de literatura científica. Utiliza protección en múltiples niveles: Cloudflare, fingerprinting del navegador, análisis del comportamiento del usuario. Detecta patrones de descarga automática: acceso secuencial a artículos, ausencia de JavaScript, User-Agent atípicos. Al detectar scraping, bloquea la IP a nivel de cuenta y puede bloquear toda la institución. Es obligatorio utilizar proxies residenciales con rotación y emulación completa del navegador.

Springer Nature — protección similar, además rastrea la velocidad de desplazamiento de páginas y el movimiento del ratón. Utiliza machine learning para detectar bots. Se recomienda hacer scraping de no más de 10-15 artículos por hora desde una IP, con retrasos aleatorios entre solicitudes.

Wiley Online Library — protección menos agresiva, pero aún requiere el uso de proxies. Permite alrededor de 50 solicitudes por hora desde una IP sin bloqueo. Utiliza cookies de sesión para rastrear la actividad.

Bases de datos farmacéuticas

DrugBank — base de datos integral de medicamentos. La versión gratuita está limitada a la interfaz web, la comercial proporciona API y exportaciones de datos. La versión web está protegida por Cloudflare y rate limiting — máximo 20 solicitudes por minuto. Detecta la automatización por la ausencia de cookies y JavaScript.

RxList, Drugs.com — directorios populares de medicamentos para consumidores. Utilizan Cloudflare y luchan activamente contra el scraping. Bloquean las IP de centros de datos prácticamente de inmediato. Se requieren proxies residenciales y velocidad de scraping lenta (5-10 páginas por minuto).

Configuración de la rotación de IP para scraping a largo plazo

La rotación adecuada de direcciones IP es un factor clave para el éxito del scraping de datos médicos. Existen dos enfoques principales: rotación a nivel de solicitudes y rotación por tiempo.

Rotación a nivel de solicitudes

En este enfoque, cada solicitud se envía a través de una nueva dirección IP. Esto reduce al máximo el riesgo de bloqueo, pero puede causar problemas con sitios que rastrean sesiones a través de cookies. Es adecuado para el scraping de listas y catálogos, donde no se requiere mantener el estado de la sesión.

La mayoría de los proveedores de proxies residenciales ofrecen rotación automática a través de un endpoint especial. Por ejemplo, al usar un endpoint de proxy rotativo, cada nueva conexión TCP recibe una nueva IP. Esto funciona automáticamente con bibliotecas como requests en Python, ya que por defecto se crea una nueva conexión para cada solicitud.

Rotación por tiempo (sticky sessions)

Las sticky sessions permiten usar una dirección IP durante un período de tiempo determinado (generalmente 5-30 minutos), después del cual se produce un cambio automático. Esto es útil para sitios que requieren autenticación o que rastrean el estado de la sesión a través de cookies. Puede hacer scraping de varias páginas desde una IP, simulando el comportamiento de un usuario real, y luego la IP cambia automáticamente.

Para sitios médicos, se recomienda utilizar sticky sessions de 10-15 minutos de duración. Durante este tiempo, se pueden hacer scraping de 10-20 páginas (dependiendo de los retrasos), después de lo cual la IP cambia y comienza una "nueva sesión". Esto parece natural y reduce el riesgo de detección.

Tamaño del pool de direcciones IP

Para el scraping a largo plazo, el tamaño del pool de direcciones IP disponibles es importante. Si utiliza el mismo conjunto de 100 IP durante una semana, el sitio puede notar el patrón y bloquear todas esas direcciones. Los proxies residenciales generalmente proporcionan acceso a millones de IP, lo que prácticamente elimina la reutilización de la misma dirección.

Al usar proxies de centros de datos, se recomienda tener un pool de al menos 500-1000 IP para scraping de volumen medio (10,000-50,000 páginas al mes). Para scraping a gran escala (cientos de miles de páginas), es mejor usar proxies residenciales con sus enormes pools de IP.

Consejos de rotación para diferentes fuentes:

  • PubMed — la rotación no es obligatoria, con 1 IP es suficiente cumpliendo el rate limit
  • Editoriales comerciales — sticky sessions de 10-15 minutos, nueva IP cada 15-20 páginas
  • Bases de datos farmacéuticas — rotación en cada solicitud o sticky sessions de 5 minutos
  • Sitios con Cloudflare — las sticky sessions son obligatorias, la rotación a nivel de solicitudes no funciona

Ejemplos de código en Python para scraping con proxies

Veamos ejemplos prácticos de configuración de proxies para el scraping de datos médicos utilizando bibliotecas populares de Python. Comenzaremos con un ejemplo básico y lo iremos complicando.

Configuración básica con la biblioteca requests

import requests
from time import sleep
import random

# Configuración del proxy (reemplazar con sus datos)
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}'
}

# Encabezados para simular un navegador real
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'
}

# Ejemplo de solicitud a PubMed
url = "https://pubmed.ncbi.nlm.nih.gov/?term=diabetes"

try:
    response = requests.get(url, proxies=proxies, headers=headers, timeout=30)
    print(f"Código de estado: {response.status_code}")
    print(f"Longitud del contenido: {len(response.content)}")
    
    # Agregamos un retraso entre solicitudes (obligatorio para PubMed)
    sleep(random.uniform(1.0, 3.0))
    
except requests.exceptions.RequestException as e:
    print(f"Error: {e}")

Configuración avanzada con rotación y lógica de reintentos

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: lista de diccionarios con proxies
        [{'http': 'http://user:pass@host:port', 'https': '...'}, ...]
        """
        self.proxy_list = proxy_list
        self.current_index = 0
    
    def get_next_proxy(self):
        """Obtener el siguiente proxy de la lista"""
        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():
    """Crear una sesión con reintentos automáticos en caso de errores"""
    session = requests.Session()
    
    # Configuración de reintentos automáticos
    retry_strategy = Retry(
        total=3,  # máximo 3 intentos
        backoff_factor=1,  # retraso entre intentos: 1, 2, 4 segundos
        status_forcelist=[429, 500, 502, 503, 504],  # códigos para reintentar
        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):
    """Scraping de una lista de URL con rotación de proxies"""
    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:
        # Obtenemos un nuevo proxy para cada solicitud
        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"✓ Éxito: {url}")
            else:
                results.append({
                    'url': url,
                    'status': 'failed',
                    'error': f"Código de estado: {response.status_code}"
                })
                print(f"✗ Fallido: {url} (Estado: {response.status_code})")
        
        except requests.exceptions.RequestException as e:
            results.append({
                'url': url,
                'status': 'error',
                'error': str(e)
            })
            print(f"✗ Error: {url} ({e})")
        
        # Retraso aleatorio entre solicitudes (¡importante!)
        sleep(random.uniform(2.0, 5.0))
    
    return results

# Ejemplo de uso
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)

Uso de Selenium para sitios con JavaScript

Muchos sitios médicos modernos utilizan JavaScript para cargar contenido. En tales casos, se necesita un navegador sin cabeza:

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):
    """Crear Chrome WebDriver con proxy"""
    
    chrome_options = Options()
    
    # Modo sin cabeza (sin GUI)
    chrome_options.add_argument('--headless')
    chrome_options.add_argument('--no-sandbox')
    chrome_options.add_argument('--disable-dev-shm-usage')
    
    # Configuración del proxy
    chrome_options.add_argument(f'--proxy-server=http://{proxy_host}:{proxy_port}')
    
    # Desactivar la automatización (importante para eludir la detección)
    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)
    
    # Para proxies con autenticación, se necesita usar una extensión
    # o configurarlo a través de capabilities (opción más complicada)
    
    return driver

def scrape_with_selenium(url, driver):
    """Scraping de la página esperando la carga de JavaScript"""
    
    driver.get(url)
    
    # Esperar la carga del elemento (por ejemplo, resultados de búsqueda)
    try:
        wait = WebDriverWait(driver, 10)
        results = wait.until(
            EC.presence_of_element_located((By.CLASS_NAME, "results-article"))
        )
        
        # Extracción de datos
        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"Error esperando elementos: {e}")
        return []

# Ejemplo de uso
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"Título: {result['title']}")
        print(f"Autores: {result['authors']}\n")
        
finally:
    driver.quit()

Control de la velocidad de solicitudes y elusión de rate limiting

El rate limiting es una de las principales protecciones de los sitios médicos contra el scraping. La configuración adecuada de la velocidad de solicitudes es crucial para un scraping a largo plazo sin bloqueos.

Definición de una velocidad segura

El primer paso es determinar los límites de un sitio específico. Esto se puede hacer experimentalmente, aumentando gradualmente la velocidad de solicitudes hasta que aparezcan errores 429 (Too Many Requests) o bloqueos. Para la mayoría de los sitios médicos, los valores seguros son:

  • PubMed — máximo 3 solicitudes por segundo (recomendación oficial)
  • ClinicalTrials.gov — 20 solicitudes por minuto son seguras, hasta 100 en 5 minutos son aceptables
  • Editoriales comerciales — 10-15 solicitudes por hora desde una IP
  • Bases de datos farmacéuticas — 5-10 solicitudes por minuto

Implementación de un limitador de tasa en Python

import time
from collections import deque

class RateLimiter:
    def __init__(self, max_calls, period):
        """
        max_calls: número máximo de llamadas
        period: período de tiempo en segundos
        Por ejemplo: RateLimiter(3, 1) = 3 solicitudes por segundo
        """
        self.max_calls = max_calls
        self.period = period
        self.calls = deque()
    
    def __call__(self, func):
        """Decorador para limitar la velocidad de llamada a la función"""
        def wrapper(*args, **kwargs):
            now = time.time()
            
            # Eliminar llamadas antiguas fuera del período
            while self.calls and self.calls[0] < now - self.period:
                self.calls.popleft()
            
            # Si se alcanza el límite, esperar
            if len(self.calls) >= self.max_calls:
                sleep_time = self.period - (now - self.calls[0])
                if sleep_time > 0:
                    print(f"Límite de tasa alcanzado, durmiendo {sleep_time:.2f}s")
                    time.sleep(sleep_time)
                    # Limpiar después de esperar
                    self.calls.clear()
            
            # Registrar el tiempo de llamada
            self.calls.append(time.time())
            
            # Ejecutar la función
            return func(*args, **kwargs)
        
        return wrapper

# Ejemplo de uso
@RateLimiter(max_calls=3, period=1)  # 3 solicitudes por segundo
def fetch_pubmed_page(url):
    response = requests.get(url, headers=headers, proxies=proxies)
    return response

# Ahora la función cumple automáticamente con el rate limit
for i in range(10):
    result = fetch_pubmed_page(f"https://pubmed.ncbi.nlm.nih.gov/?term=test&page={i}")
    print(f"Página {i} obtenida")

Rate limiting adaptativo

Un enfoque más avanzado es cambiar la velocidad de manera adaptativa según las respuestas del servidor. Si recibimos errores 429 o 503, disminuimos automáticamente la velocidad:

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):
        """Esperar antes de la siguiente solicitud"""
        # Agregar aleatoriedad para naturalidad
        actual_delay = self.current_delay * random.uniform(0.8, 1.2)
        time.sleep(actual_delay)
    
    def on_success(self):
        """Se llama en caso de solicitud exitosa"""
        self.success_count += 1
        
        # Después de 10 solicitudes exitosas, aceleramos un poco
        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):
        """Se llama al recibir 429 o errores similares"""
        # Duplicar el retraso, pero no más que el máximo
        self.current_delay = min(
            self.current_delay * 2,
            self.max_delay
        )
        self.success_count = 0
        print(f"¡Límite de tasa alcanzado! Aumentando el retraso a {self.current_delay:.2f}s")
    
    def on_error(self):
        """Se llama en caso de otros errores"""
        # Aumentar un poco el retraso
        self.current_delay = min(
            self.current_delay * 1.5,
            self.max_delay
        )
        self.success_count = 0

# Ejemplo de uso
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()
            # Procesar datos
            
        elif response.status_code == 429:
            limiter.on_rate_limit()
            # Repetir más tarde
            
        else:
            limiter.on_error()
            
    except requests.exceptions.RequestException:
        limiter.on_error()

Encabezados correctos y User-Agent para sitios médicos

Los sitios médicos analizan los encabezados HTTP para detectar bots. Encabezados incorrectos o faltantes son una causa común de bloqueos incluso al usar proxies de calidad.

Encabezados obligatorios

El conjunto mínimo de encabezados que debe estar presente en cada solicitud:

headers = {
    # User-Agent — debe ser un navegador actual
    '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 — tipos de contenido que acepta el navegador
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
    
    # Accept-Language — idioma del usuario
    'Accept-Language': 'en-US,en;q=0.9',
    
    # Accept-Encoding — soporte para compresión
    'Accept-Encoding': 'gzip, deflate, br',
    
    # Connection — mantener la conexión
    'Connection': 'keep-alive',
    
    # Upgrade-Insecure-Requests — transición automática a HTTPS
    'Upgrade-Insecure-Requests': '1',
    
    # DNT — Do Not Track (opcional, pero añade realismo)
    'DNT': '1',
    
    # Encabezados Sec-Fetch-* (importantes para navegadores modernos)
    'Sec-Fetch-Dest': 'document',
    'Sec-Fetch-Mode': 'navigate',
    'Sec-Fetch-Site': 'none',
    'Sec-Fetch-User': '?1',
    
    # Cache-Control
    'Cache-Control': 'max-age=0'
}

Rotación de User-Agent

Usar el mismo User-Agent puede ser sospechoso. Se recomienda rotar entre varios navegadores actuales:

import random

USER_AGENTS = [
    # Chrome en 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 en 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 en Windows
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
    
    # Firefox en Mac
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0',
    
    # Safari en 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 en 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():
    """Obtener encabezados con un User-Agent aleatorio"""
    return {
        'User-Agent': random.choice(USER_AGENTS),
        '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',
        'Connection': 'keep-alive',
        'Upgrade-Insecure-Requests': '1',
        'DNT': '1'
    }

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

Referer y Origin para formularios

Al trabajar con formularios de búsqueda o enviar solicitudes POST, asegúrese de agregar los encabezados Referer y Origin:

# Para solicitudes POST a un formulario de búsqueda
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'
}

# Solicitud POST con datos del formulario
data = {
    'query': 'diabetes',
    'page': '1'
}

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

Problemas típicos y sus soluciones

Al hacer scraping de datos médicos, surgen problemas específicos. Analicemos los más comunes y cómo solucionarlos.

Problema: Cloudflare bloquea todas las solicitudes

Síntomas: Recibe una página con el texto "Checking your browser" o un error 403 Forbidden mencionando Cloudflare.

Solución:

  • Utilice proxies residenciales en lugar de centros de datos — Cloudflare bloquea las IP de centros de datos por defecto
  • Cambie a Selenium o Puppeteer — los navegadores sin cabeza pasan mejor las verificaciones de Cloudflare
  • Utilice la biblioteca cloudscraper para Python — elude automáticamente la protección básica de Cloudflare
  • Active cookies y JavaScript — Cloudflare verifica su presencia
  • Agregue fingerprinting TLS — use curl_cffi para simular un navegador real a nivel de TLS

Problema: Recibo error 429 Too Many Requests

Síntomas: Después de varias solicitudes exitosas, el servidor comienza a devolver 429.

Solución:

  • Aumente el retraso entre solicitudes — intente comenzar con 3-5 segundos
  • Active la rotación de IP — cada solicitud a través de una nueva IP elimina el rate limiting
  • Verifique el encabezado Retry-After en la respuesta 429 — indica cuántos segundos debe esperar
  • Utilice un retraso exponencial en los reintentos — 1s, 2s, 4s, 8s, etc.

Problema: Los proxies funcionan lentamente o se desconectan con frecuencia

Síntomas: Errores de timeout, carga de páginas muy lenta, desconexiones.

Solución:

  • Aumente el timeout en las solicitudes a 30-60 segundos — los proxies residenciales pueden ser más lentos
  • Utilice proxies geográficamente cercanos — si hace scraping de un sitio europeo, use IP europeas
  • Verifique la calidad del proveedor de proxies — los proxies baratos suelen ser inestables
  • Agregue lógica de reintentos — repita automáticamente la solicitud en caso de error de conexión
  • Utilice connection pooling — reutilice conexiones TCP a través de requests.Session()

Problema: El sitio requiere autenticación o suscripción

Síntomas: El acceso a los textos completos de los artículos está restringido, se requiere inicio de sesión.

Solución:

  • Utilice acceso institucional — muchas universidades y hospitales tienen suscripciones
  • Verifique la disponibilidad de versiones de acceso abierto — muchos artículos están disponibles gratuitamente a través de repositorios
  • Utilice API en lugar de scraping — algunas editoriales proporcionan API para investigadores
  • Haga scraping solo de metadatos (títulos, autores, resúmenes) — generalmente están disponibles gratuitamente

Problema: El contenido de JavaScript no se carga

Síntomas: En el HTML no hay datos necesarios, solo se ven spinners de carga o contenedores vacíos.

Solución:

  • Cambie a Selenium/Puppeteer — ejecutan JavaScript
  • Encuentre un endpoint API — abra DevTools en el navegador, pestaña Network, y busque solicitudes XHR con datos
  • Utilice requests-html — biblioteca que permite ejecutar JavaScript
```