Qué es un servidor proxy en Python

El servidor proxy de Python permite enrutar solicitudes HTTP/S a través de una amplia red de IPs mediante código Python. Admite funciones como rotación de IP, persistencia de sesión y segmentación geográfica.
13 min de lectura
Python Proxy Server illustration with laptop and servers.

En este tutorial, aprenderás:

  • Qué es un servidor proxy en Python y cómo funciona
  • Los pasos necesarios para construir un servidor proxy HTTP en Python.
  • Las ventajas y desventajas de este enfoque.

¡Empecemos!

¿Qué Es un Servidor Proxy en Python?

Un Proxy de Python es una aplicación Python que actúa como intermediario entre clientes e Internet. Intercepta las solicitudes de los clientes, las reenvía a los servidores de destino y envía la respuesta de vuelta al cliente. Al hacerlo, enmascara la identidad del cliente ante los servidores de destino. 

Lee nuestro artículo para profundizar en qué es un servidor proxy y cómo funciona. 

Las capacidades de programación de sockets de Python facilitan la implementación de un servidor proxy básico, permitiendo a los usuarios inspeccionar, modificar o redirigir el tráfico de red. Los servidores proxy son excelentes para el almacenamiento en caché, mejorar el rendimiento y reforzar la seguridad en el scraping web.

Cómo Implementar un Servidor Proxy HTTP en Python

Sigue los pasos a continuación y aprende a construir un script de servidor proxy en Python.

Paso 1: Inicializa Tu Proyecto Python

Antes de comenzar, asegúrate de tener Python 3+ instalado en tu máquina. De lo contrario, descarga el instalador, ejecútalo y sigue el asistente de instalación.

A continuación, usa los siguientes comandos para crear una carpeta python-http-proxy-server e inicializar un proyecto Python con un entorno virtual dentro: 

mkdir python-http-proxy-server

cd python-http-proxy-server

python -m venv env

Abre la carpeta python-http-proxy-server en tu IDE de Python y crea un archivo proxy_server.py vacío.

¡Genial! Ya tienes todo lo necesario para construir un servidor proxy HTTP en Python.

Paso 2: Inicializa un Socket de Entrada

Primero, necesitas crear un servidor de socket web para aceptar las solicitudes entrantes. Si no estás familiarizado con ese concepto, un socket es una abstracción de programación de bajo nivel que permite el flujo bidireccional de datos entre un cliente y un servidor. En el contexto de un servidor web, un socket de servidor se usa para escuchar las conexiones entrantes de los clientes. 

Usa las siguientes líneas para crear un servidor web basado en sockets en Python:

port = 8888
# vincula el servidor proxy a una dirección y puerto específicos
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# acepta hasta 10 conexiones simultáneas
server.bind(('127.0.0.1', port))
server.listen(10)

Esto inicializa un servidor de socket entrante y lo vincula a la dirección local http://127.0.0.1:8888. Luego, habilita al servidor para aceptar conexiones con el método listen().

Nota: Puedes cambiar el número del puerto que debe escuchar el proxy web. También puedes modificar el script para leer esa información desde la línea de comandos para mayor flexibilidad. 

socket proviene de la Biblioteca Estándar de Python. Así que tendrás la siguiente importación al inicio de tu script:

import socket

Para monitorear que el servidor proxy de Python se ha iniciado correctamente, registra este mensaje:

 print(f"Proxy server listening on port {port}...")

Paso 3: Acepta las Solicitudes del Cliente

Cuando un cliente se conecta al servidor proxy, este necesita crear un nuevo socket para gestionar la comunicación con ese cliente específico. Así es como puedes hacerlo en Python:

# escucha las solicitudes entrantes

while True:

    client_socket, addr = server.accept()

    print(f"Accepted connection from {addr[0]}:{addr[1]}")

    # crea un hilo para gestionar la solicitud del cliente

    client_handler = threading.Thread(target=handle_client_request, args=(client_socket,))

    client_handler.start()

Para gestionar múltiples solicitudes de clientes simultáneamente, debes usar multihilos como se muestra arriba. No olvides importar threading de la Biblioteca Estándar de Python:

import threading

Como puedes ver, el servidor proxy gestiona las solicitudes entrantes mediante la función personalizada handle_client_request(). Mira cómo se define en los siguientes pasos.

Paso 4: Procesa las Solicitudes Entrantes

Una vez creado el socket del cliente, necesitas usarlo para:

  1. Leer los datos de las solicitudes entrantes.
  2. Extraer el host y el puerto del servidor de destino de esos datos.
  3. Usarlo para reenviar la solicitud del cliente al servidor de destino.
  4. Obtener la respuesta y reenviarla al cliente original.

En esta sección, centrémonos en los dos primeros pasos. Define la función handle_client_request() y úsala para leer los datos de la solicitud entrante:

def handle_client_request(client_socket):

    print("Received request:n")

    # lee los datos enviados por el cliente en la solicitud

    request = b''

    client_socket.setblocking(False)

    while True:

        try:

            # recibe datos del servidor web

            data = client_socket.recv(1024)

            request = request + data

            # Recibe datos del servidor de destino original

            print(f"{data.decode('utf-8')}")

        except:

            break

setblocking(False) configura el socket del cliente en modo no bloqueante. Luego, usa recv() para leer los datos entrantes y añadirlos a request en formato de bytes. Como no sabes el tamaño de los datos de la solicitud entrante, debes leerlos en fragmentos. En este caso, se ha especificado un fragmento de 1024 bytes. En modo no bloqueante, si recv() no encuentra datos, generará una excepción de error. Así, la instrucción except marca el fin de la operación.

Observa los mensajes registrados para hacer seguimiento de lo que hace el servidor proxy de Python.

Después de recuperar la solicitud entrante, necesitas extraer el host y el puerto del servidor de destino:

host, port = extract_host_port_from_request(request)

En particular, así es como se ve la función extract_host_port_from_request():

def extract_host_port_from_request(request):

    # obtiene el valor después de la cadena "Host:"

    host_string_start = request.find(b'Host: ') + len(b'Host: ')

    host_string_end = request.find(b'rn', host_string_start)

    host_string = request[host_string_start:host_string_end].decode('utf-8')

    webserver_pos = host_string.find("/")

    if webserver_pos == -1:

        webserver_pos = len(host_string)

    # si hay un puerto específico

    port_pos = host_string.find(":")

    # no se especifica puerto

    if port_pos == -1 or webserver_pos < port_pos:

        # puerto predeterminado

        port = 80

        host = host_string[:webserver_pos]

    else:

        # extrae el puerto específico de la cadena del host

        port = int((host_string[(port_pos + 1):])[:webserver_pos - port_pos - 1])

        host = host_string[:port_pos]

    return host, port

Para entender mejor qué hace, considera el ejemplo a continuación. Esto es lo que generalmente contiene la cadena codificada de una solicitud entrante:

GET http://example.com/your-page HTTP/1.1

Host: example.com

User-Agent: curl/8.4.0

Accept: */*

Proxy-Connection: Keep-Alive

extract_host_port_from_request() extrae el host y el puerto del servidor web del campo “Host:”. En este caso, host es example.com y port es 80 (ya que no se ha especificado un puerto específico). 

Paso 5: Reenvía la Solicitud del Cliente y Gestiona la Respuesta

Dado el host y el puerto de destino, ahora debes reenviar la solicitud del cliente al servidor de destino. En handle_client_request(), crea un nuevo socket web y úsalo para enviar la solicitud original al destino deseado:

# crea un socket para conectarse al servidor de destino original

destination_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# conéctate al servidor de destino

destination_socket.connect((host, port))

# envía la solicitud original

destination_socket.sendall(request)

Luego, prepárate para recibir la respuesta del servidor y propagarla al cliente original:

# lee los datos recibidos del servidor

# un fragmento a la vez y envíalos al cliente

print("Received response:n")

while True:

    # recibe datos del servidor web

    data = destination_socket.recv(1024)

    # Recibe datos del servidor de destino original

    print(f"{data.decode('utf-8')}")

    # no hay más datos para enviar

    if len(data) > 0:

        # envía de vuelta al cliente

        client_socket.sendall(data)

    else:

        break

Nuevamente, debes trabajar un fragmento a la vez ya que no sabes el tamaño de la respuesta. Cuando data está vacío, no hay más datos que recibir y puedes terminar la operación.

No olvides cerrar los dos sockets que definiste en la función:

# cierra los sockets

destination_socket.close()

client_socket.close()

¡Genial! Acabas de crear un servidor proxy HTTP en Python. ¡Hora de ver el código completo, lanzarlo y verificar que funciona como se espera!

Paso 6: Júntalo Todo

Este es el código final de tu script de servidor proxy en Python:

import socket

import threading

def handle_client_request(client_socket):

    print("Received request:n")

    # lee los datos enviados por el cliente en la solicitud

    request = b''

    client_socket.setblocking(False)

    while True:

        try:

            # recibe datos del servidor web

            data = client_socket.recv(1024)

            request = request + data

            # Recibe datos del servidor de destino original

            print(f"{data.decode('utf-8')}")

        except:

            break

    # extrae el host y el puerto del servidor web desde la solicitud

    host, port = extract_host_port_from_request(request)

    # crea un socket para conectarse al servidor de destino original

    destination_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # conéctate al servidor de destino

    destination_socket.connect((host, port))

    # envía la solicitud original

    destination_socket.sendall(request)

    # lee los datos recibidos del servidor

    # un fragmento a la vez y envíalos al cliente

    print("Received response:n")

    while True:

        # recibe datos del servidor web

        data = destination_socket.recv(1024)

        # Recibe datos del servidor de destino original

        print(f"{data.decode('utf-8')}")

        # no hay más datos para enviar

        if len(data) > 0:

            # envía de vuelta al cliente

            client_socket.sendall(data)

        else:

            break

    # cierra los sockets

    destination_socket.close()

    client_socket.close()

def extract_host_port_from_request(request):

    # obtiene el valor después de la cadena "Host:"

    host_string_start = request.find(b'Host: ') + len(b'Host: ')

    host_string_end = request.find(b'rn', host_string_start)

    host_string = request[host_string_start:host_string_end].decode('utf-8')

    webserver_pos = host_string.find("/")

    if webserver_pos == -1:

        webserver_pos = len(host_string)

    # si hay un puerto específico

    port_pos = host_string.find(":")

    # no se especifica puerto

    if port_pos == -1 or webserver_pos < port_pos:

        # puerto predeterminado

        port = 80

        host = host_string[:webserver_pos]

    else:

        # extrae el puerto específico de la cadena del host

        port = int((host_string[(port_pos + 1):])[:webserver_pos - port_pos - 1])

        host = host_string[:port_pos]

    return host, port

def start_proxy_server():

    port = 8888

    # vincula el servidor proxy a una dirección y puerto específicos

    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    server.bind(('127.0.0.1', port))

    # acepta hasta 10 conexiones simultáneas

    server.listen(10)

    print(f"Proxy server listening on port {port}...")

    # escucha las solicitudes entrantes

    while True:

        client_socket, addr = server.accept()

        print(f"Accepted connection from {addr[0]}:{addr[1]}")

        # crea un hilo para gestionar la solicitud del cliente

        client_handler = threading.Thread(target=handle_client_request, args=(client_socket,))

        client_handler.start()

if __name__ == "__main__":

    start_proxy_server()

Lánzalo con este comando:

python proxy_server.py

Deberías ver el siguiente mensaje en la terminal:

Proxy server listening on port 8888...

Para asegurarte de que el servidor funciona, ejecuta una solicitud proxy con cURL. Lee nuestra guía para aprender más sobre cómo usar cURL con un proxy.

Abre una nueva terminal y ejecuta:

curl --proxy "http://127.0.0.1:8888" "http://httpbin.org/ip"

Eso haría una solicitud GET al destino http://httpbin.org/ip a través del servidor proxy http://127.0.0.1:8888.

Deberías obtener algo como:

{

  "origin": "45.12.80.183"

}

Esa es la IP del servidor proxy. ¿Por qué? Porque el endpoint /ip del proyecto HTTPBin devuelve la IP desde la cual proviene la solicitud. Si estás ejecutando el servidor localmente, “origin” corresponderá a tu IP. 

Nota: El servidor proxy de Python construido aquí solo funciona con destinos HTTP. Extenderlo para gestionar conexiones HTTPS es bastante complicado.

Ahora, explora el registro escrito por tu aplicación de servidor proxy en Python. Debería contener:

Received request:

GET http://httpbin.org/ip HTTP/1.1

Host: httpbin.org

User-Agent: curl/8.4.0

Accept: */*

Proxy-Connection: Keep-Alive

Received response:

HTTP/1.1 200 OK

Date: Thu, 14 Dec 2023 14:02:08 GMT

Content-Type: application/json

Content-Length: 31

Connection: keep-alive

Server: gunicorn/19.9.0

Access-Control-Allow-Origin: *

Access-Control-Allow-Credentials: true

{

  "origin": "45.12.80.183"

}

Esto te indica que el servidor proxy recibió la solicitud en el formato especificado por el protocolo HTTP. Luego, la reenvió al servidor de destino, registró los datos de la respuesta y envió la respuesta de vuelta al cliente. ¿Por qué estamos seguros de esto? ¡Porque las IPs en “origin” son las mismas!

¡Felicidades! ¡Acabas de aprender a construir un servidor proxy HTTP en Python!

Ventajas y Desventajas de Usar un Servidor Proxy Python Personalizado

Ahora que sabes cómo implementar un servidor proxy en Python, estás listo para ver los beneficios y limitaciones de este enfoque.

Ventajas:

  • Control total: Con un script Python personalizado como este, tienes control total sobre lo que hace tu servidor proxy. ¡Sin actividades sospechosas ni filtración de datos!
  • Personalización: El servidor proxy puede ampliarse para incluir funciones útiles como el registro y el almacenamiento en caché de solicitudes para mejorar el rendimiento.

Desventajas:

  • Costos de infraestructura: Configurar una arquitectura de servidor proxy no es fácil y cuesta mucho dinero en términos de hardware o servicios VPS.
  • Difícil de mantener: Eres responsable de mantener la arquitectura del proxy, especialmente su escalabilidad y disponibilidad. Esta es una tarea que solo administradores de sistemas experimentados pueden abordar.
  • Poco fiable: El principal problema de esta solución es que la IP de salida del servidor proxy nunca cambia. Como resultado, las tecnologías anti-bot podrán bloquear la IP y evitar que el servidor acceda a las solicitudes deseadas. En otras palabras, el proxy eventualmente dejará de funcionar.

Estas limitaciones y desventajas son demasiado graves para usar un servidor proxy de Python personalizado en un escenario de producción. ¿La solución? ¡Un proveedor de proxy confiable como Bright Data! Crea una cuenta, verifica tu identidad, obtén un proxy y úsalo en tu lenguaje de programación favorito. Por ejemplo, integra un proxy en tu script de Python con requests.

Nuestra enorme red de proxies incluye millones de servidores proxy rápidos, confiables y seguros en todo el mundo. Descubre por qué somos el mejor proveedor de servidores proxy.

Conclusión

En esta guía, aprendiste qué es un servidor proxy y cómo funciona en Python. En detalle, aprendiste a construir uno desde cero usando sockets web. Ahora te has convertido en un maestro de los proxies en Python. El principal problema de este enfoque es que la IP de salida estática de tu servidor proxy eventualmente te hará bloquear. ¡Evita eso con los proxies rotativos de Bright Data!

Bright Data controla los mejores servidores proxy del mundo, sirviendo a empresas Fortune 500 y a 50,000+ clientes. Su oferta incluye una amplia gama de tipos de proxy:

Esa red de proxies confiable, rápida y global es también la base de varios servicios de scraping web para recuperar datos de cualquier sitio sin esfuerzo.