Volver al blog

Por Qué un Proxy Funciona en el Navegador y No en el Código: Análisis Completo del Problema

Proxy funciona bien en el navegador, ¿pero el script devuelve errores? Analizamos causas comunes y ofrecemos soluciones listas para Python, Node.js y cURL.

📅11 de diciembre de 2025
```html

Por qué el proxy funciona en el navegador, pero no en el código: un análisis completo

Situación clásica: configuras un proxy en el navegador, abres un sitio web y todo funciona. Ejecutas un script con el mismo proxy y obtienes un error de conexión, tiempo de espera agotado o un bloqueo. Analizamos por qué sucede esto y cómo solucionarlo.

En qué se diferencia una solicitud del navegador de una solicitud desde el código

Cuando abres un sitio web a través de un proxy en el navegador, suceden muchas más cosas que solo una solicitud HTTP. El navegador automáticamente:

  • Envía el conjunto completo de encabezados (User-Agent, Accept, Accept-Language, Accept-Encoding)
  • Realiza el handshake TLS con un conjunto de cifrados correcto
  • Maneja redireccionamientos y cookies
  • Ejecuta JavaScript y carga recursos dependientes
  • Almacena en caché las respuestas DNS y los certificados

Una solicitud mínima desde el código se ve muy diferente para el servidor: parece un robot, no un humano. Incluso si el proxy funciona correctamente, el sitio de destino puede estar bloqueando específicamente tu script.

Problemas con la autenticación del proxy

La causa más común es la transmisión incorrecta del nombre de usuario y la contraseña. El navegador muestra una ventana emergente para ingresar las credenciales, mientras que en el código esto debe hacerse explícitamente.

Formato de URL incorrecto

Un error frecuente es omitir el esquema o no codificar correctamente los caracteres especiales:

# Incorrecto
proxy = "user:pass@proxy.example.com:8080"

# Correcto
proxy = "http://user:pass@proxy.example.com:8080"

# Si la contraseña contiene caracteres especiales (@, :, /)
from urllib.parse import quote
password = quote("p@ss:word/123", safe="")
proxy = f"http://user:{password}@proxy.example.com:8080"

Autenticación por IP vs. nombre de usuario/contraseña

Algunos proveedores de proxy utilizan una lista blanca (whitelist) basada en la dirección IP. El navegador en tu computadora funciona porque tu IP está en la lista blanca. Sin embargo, el script en el servidor no funciona porque el servidor tiene una IP diferente.

Verifica en el panel de control del proveedor qué método de autenticación se utiliza y qué IPs están añadidas a la lista blanca.

Incompatibilidad de protocolos HTTP/HTTPS/SOCKS

El navegador a menudo determina el tipo de proxy automáticamente. En el código, debes especificarlo explícitamente, y un error en el protocolo conduce a un fallo silencioso.

Tipo de Proxy Esquema en la URL Características
Proxy HTTP http:// Funciona para HTTP y HTTPS a través de CONNECT
Proxy HTTPS https:// Conexión cifrada con el proxy
SOCKS4 Proxy socks4:// Sin autenticación, solo IPv4
SOCKS5 Proxy socks5:// Con autenticación, UDP, IPv6
SOCKS5h Proxy socks5h:// Resolución DNS a través del proxy

Es fundamental: si tienes un proxy SOCKS5, pero especificas http://, la conexión no se establecerá. La biblioteca intentará comunicarse usando el protocolo HTTP con un servidor SOCKS.

Encabezados faltantes y huella digital (fingerprint)

Incluso si el proxy funciona correctamente, el sitio de destino puede bloquear la solicitud debido a encabezados sospechosos. Compara:

Solicitud desde el navegador

GET /api/data HTTP/1.1
Host: example.com
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,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1

Solicitud predeterminada con requests

GET /api/data HTTP/1.1
Host: example.com
User-Agent: python-requests/2.28.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive

La diferencia es obvia. Un sitio con protección anti-bot detectará instantáneamente que la solicitud no proviene de un navegador.

Conjunto mínimo de encabezados para disfrazarse

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/avif,image/webp,image/apng,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.9",
    "Accept-Encoding": "gzip, deflate, br",
    "Connection": "keep-alive",
    "Upgrade-Insecure-Requests": "1",
    "Sec-Fetch-Dest": "document",
    "Sec-Fetch-Mode": "navigate",
    "Sec-Fetch-Site": "none",
    "Sec-Fetch-User": "?1",
    "Cache-Control": "max-age=0"
}

Certificados SSL y verificación

El navegador tiene un almacén de certificados raíz incorporado y puede manejar varias configuraciones SSL. En el código pueden surgir problemas:

Error SSL: CERTIFICATE_VERIFY_FAILED

Algunos proxies utilizan sus propios certificados para inspeccionar el tráfico. Es posible que el navegador tenga este certificado como de confianza, pero tu script no.

# Solución temporal para depuración (NO para producción!)
import requests
response = requests.get(url, proxies=proxies, verify=False)

# Solución correcta: especificar la ruta al certificado
response = requests.get(url, proxies=proxies, verify="/path/to/proxy-ca.crt")

Importante: Desactivar la verificación SSL (verify=False) hace que la conexión sea vulnerable a ataques MITM. Úsalo solo para depuración en un entorno seguro.

Huella digital TLS (TLS Fingerprint)

Los sistemas anti-bot avanzados analizan la huella digital TLS: el orden y el conjunto de cifrados al establecer la conexión. Python requests utiliza un conjunto estándar que difiere del del navegador.

Para evitar esto, utiliza bibliotecas con huella digital TLS personalizada:

# Instalación: pip install curl-cffi
from curl_cffi import requests

response = requests.get(
    url,
    proxies={"https": proxy},
    impersonate="chrome120"  # Imita la huella digital TLS de Chrome 120
)

Fugas de DNS y resolución

Otro problema no obvio es la resolución DNS. Al usar un proxy HTTP, la solicitud DNS puede ir directamente desde tu máquina, saltándose el proxy.

Cómo afecta esto al funcionamiento

  • El sitio ve el resolvedor DNS real, no el proxy
  • La geolocalización se determina incorrectamente
  • Algunos sitios bloquean la inconsistencia entre la IP y la región DNS

Solución para SOCKS5

Utiliza el esquema socks5h:// en lugar de socks5://; la "h" significa que la resolución DNS se realizará en el lado del proxy:

# DNS se resuelve localmente (fuga!)
proxy = "socks5://user:pass@proxy.example.com:1080"

# DNS se resuelve a través del proxy (correcto)
proxy = "socks5h://user:pass@proxy.example.com:1080"

Ejemplos funcionales para Python, Node.js y cURL

Python con requests

import requests
from urllib.parse import quote

# Datos del proxy
proxy_host = "proxy.example.com"
proxy_port = "8080"
proxy_user = "username"
proxy_pass = quote("p@ssword!", safe="")  # Codificar caracteres especiales

# Formar la URL del proxy
proxy_url = f"http://{proxy_user}:{proxy_pass}@{proxy_host}:{proxy_port}"

proxies = {
    "http": proxy_url,
    "https": proxy_url
}

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,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.9",
    "Accept-Encoding": "gzip, deflate, br",
}

try:
    response = requests.get(
        "https://httpbin.org/ip",
        proxies=proxies,
        headers=headers,
        timeout=30
    )
    print(f"Status: {response.status_code}")
    print(f"IP: {response.json()}")
except requests.exceptions.ProxyError as e:
    print(f"Error de proxy: {e}")
except requests.exceptions.ConnectTimeout:
    print("Tiempo de espera agotado al conectar al proxy")

Python con aiohttp (asíncrono)

import aiohttp
import asyncio

async def fetch_with_proxy():
    proxy_url = "http://user:pass@proxy.example.com:8080"
    
    async with aiohttp.ClientSession() as session:
        async with session.get(
            "https://httpbin.org/ip",
            proxy=proxy_url,
            headers={"User-Agent": "Mozilla/5.0..."}
        ) as response:
            return await response.json()

result = asyncio.run(fetch_with_proxy())
print(result)

Node.js con axios

const axios = require('axios');
const HttpsProxyAgent = require('https-proxy-agent');

const proxyUrl = 'http://user:pass@proxy.example.com:8080';
const agent = new HttpsProxyAgent(proxyUrl);

axios.get('https://httpbin.org/ip', {
    httpsAgent: agent,
    headers: {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...'
    }
})
.then(response => console.log(response.data))
.catch(error => console.error('Error:', error.message));

Node.js con node-fetch y SOCKS

const fetch = require('node-fetch');
const { SocksProxyAgent } = require('socks-proxy-agent');

const agent = new SocksProxyAgent('socks5://user:pass@proxy.example.com:1080');

fetch('https://httpbin.org/ip', { agent })
    .then(res => res.json())
    .then(data => console.log(data));

cURL

# Proxy HTTP
curl -x "http://user:pass@proxy.example.com:8080" \
     -H "User-Agent: Mozilla/5.0..." \
     https://httpbin.org/ip

# Proxy SOCKS5 con DNS a través del proxy
curl --socks5-hostname "proxy.example.com:1080" \
     --proxy-user "user:pass" \
     https://httpbin.org/ip

# Depuración: mostrar todo el proceso de conexión
curl -v -x "http://user:pass@proxy.example.com:8080" \
     https://httpbin.org/ip

Lista de verificación de diagnóstico

Si el proxy no funciona en el código, comprueba lo siguiente en orden:

  1. Formato de la URL del proxy: ¿Tiene esquema (http://, socks5://)?
  2. Caracteres especiales en la contraseña: ¿Están codificados mediante codificación URL?
  3. Tipo de proxy: ¿Coincide el protocolo especificado con el real?
  4. Autenticación: ¿Es por IP o por usuario/contraseña? ¿Está la IP del servidor en la lista blanca?
  5. Encabezados: ¿Se ha añadido el User-Agent y otros encabezados del navegador?
  6. SSL: ¿Hay errores de certificado?
  7. DNS: ¿Se utiliza socks5h:// para la resolución a través del proxy?
  8. Tiempos de espera: ¿Hay suficiente tiempo para la conexión (especialmente para proxies residenciales)?

Conclusión

La diferencia entre el navegador y el código reside en los detalles: encabezados, protocolos, SSL, DNS. El navegador oculta esta complejidad, mientras que en el código cada aspecto debe configurarse explícitamente. Comienza verificando el formato de la URL y la autenticación, luego añade los encabezados del navegador; esto soluciona el 90% de los problemas.

Para tareas de raspado (scraping) y automatización donde la estabilidad y una baja tasa de bloqueo son cruciales, los proxies residenciales funcionan bien; puedes obtener más información sobre ellos en proxycove.com.

```