En este tutorial, aprenderás:
- Qué es un servidor proxy en Python y cómo funciona.
- Los pasos que hay que seguir para crear un servidor proxy HTTP en Python.
- Las ventajas y los inconvenientes de este modelo.
¡Vamos allá!
¿Qué es un servidor proxy en Python?
Un servidor proxy en Python es una aplicación de Python que funciona como intermediaria entre los clientes e internet. Intercepta las peticiones de los clientes, las reenvía a los servidores de destino y devuelve la respuesta al cliente. De esta forma, la identidad del cliente queda oculta en los servidores de destino.
Lee este artículo para saber más sobre 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 y esto permite que los usuarios puedan inspeccionar, modificar o redirigir el tráfico de red. Los servidores proxy son ideales para almacenar en caché, para mejorar el rendimiento y para aumentar la seguridad en lo que respecta al raspado web.
Cómo implementar un servidor proxy HTTP en Python
Sigue los pasos que aparecen a continuación y aprende a crear una secuencia de comandos de servidor proxy en Python.
Paso 1: crear tu proyecto de Python
Antes de empezar, comprueba que tienes Python 3+ instalado en tu ordenador. Si no lo tienes, descarga el instalador, ábrelo y sigue las instrucciones de instalación.
A continuación, utiliza los siguientes comandos para crear una carpeta python-http-proxy-server y un proyecto de Python que incluya un entorno virtual:
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.
¡Estupendo! Ya tienes todo lo que necesitas para crear un servidor proxy HTTP en Python.
Paso 2: crear un socket entrante
Primero tienes que crear un servidor WebSocket para aceptar las peticiones de entrada. Si no conoces bien este concepto, un socket es una abstracción de programación de nivel bajo que permite que haya un flujo de datos bidireccional entre un cliente y un servidor. Con respecto a los servidores web, se usa un socket de servidor para escuchar las conexiones entrantes de los clientes.
Utiliza las siguientes líneas para crear un servidor web basado en sockets en Python:
port = 8888
# bind the proxy server to a specific address and port
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# accept up to 10 simultaneous connections
server.bind(('127.0.0.1', port))
server.listen(10)
Así se crea un servidor de socket entrante y se vincula a la dirección local http://127.0.0.1:8888. Esto permite que el servidor acepte conexiones con el método listen () .
Nota: puedes cambiar el número del puerto que tiene que escuchar el proxy web. También puedes modificar la secuencia de comandos para leer esa información desde la línea de comandos y así tener la máxima flexibilidad.
El socket proviene de la biblioteca estándar de Python. Por lo tanto, tendrás que importar lo siguiente en la parte superior de tu secuencia de comandos:
import socket
Para comprobar que el servidor proxy en Python se ha iniciado correctamente, registra este mensaje:
print(f"Proxy server listening on port {port}...")
Paso 3: aceptar las peticiones de los clientes
Cuando un cliente se conecta al servidor proxy, hay que crear un socket nuevo para gestionar la comunicación con ese cliente concreto. Puedes hacerlo en Python de la siguiente manera:
# listen for incoming requests
while True:
client_socket, addr = server.accept()
print(f"Accepted connection from {addr[0]}:{addr[1]}")
# create a thread to handle the client request
client_handler = threading.Thread(target=handle_client_request, args=(client_socket,))
client_handler.start()
Para gestionar las peticiones de varios clientes a la vez, tienes que utilizar multihilos tal y como hemos indicado anteriormente. No olvides importar los hilos de la biblioteca estándar de Python:
import threading
Como puedes ver, el servidor proxy se encarga de las peticiones de entrada gracias a la función personalizada handle_client_request (). Vamos a ver a continuación cómo se define.
Paso 4: procesar las peticiones de entrada
Una vez creado el socket de cliente, tienes que usarlo para:
- Leer los datos de las peticiones de entrada.
- Extraer el host y el puerto del servidor de destino de esos datos.
- Reenviar la petición del cliente al servidor de destino.
- Enviar al cliente original la respuesta que has recibido.
En esta sección, nos vamos a centrar en los dos primeros pasos. Define la función handle_client_request () y úsala para leer los datos de la petición de entrada:
def handle_client_request(client_socket):
print("Received request:\n")
# read the data sent by the client in the request
request = b''
client_socket.setblocking(False)
while True:
try:
# receive data from web server
data = client_socket.recv(1024)
request = request + data
# Receive data from the original destination server
print(f"{data.decode('utf-8')}")
except:
break
setblocking (False) establece el socket del cliente sin bloqueo. Después, utiliza recv () para leer los datos de entrada y añádelos a la petición en formato byte. Como no sabes el tamaño de los datos de la petición de entrada, tienes que leer los bloques de uno en uno. En este caso, se ha especificado un bloque de 1024 bytes. En el modo «sin bloqueo», si recv () no encuentra datos, generará una excepción de error. Por lo tanto, el comando de excepción marca el final de la operación.
Anota los mensajes registrados para saber lo que está haciendo el servidor proxy en Python.
Después de recuperar la petición de entrada, tienes que extraer el host y el puerto del servidor de destino:
host, port = extract_host_port_from_request(request)
In particular, this is what the extract_host_port_from_request() function looks like:
def extract_host_port_from_request(request):
# get the value after the "Host:" string
host_string_start = request.find(b'Host: ') + len(b'Host: ')
host_string_end = request.find(b'\r\n', 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)
# if there is a specific port
port_pos = host_string.find(":")
# no port specified
if port_pos == -1 or webserver_pos < port_pos:
# default port
port = 80
host = host_string[:webserver_pos]
else:
# extract the specific port from the host string
port = int((host_string[(port_pos + 1):])[:webserver_pos - port_pos - 1])
host = host_string[:port_pos]
return host, port
To better understand what it does, consider the example below. This is what the encoded string of an incoming request usually contains:
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, el host es example.com y el puerto es 80, ya que no se ha indicado un puerto específico.
Paso 5: reenviar la petición del cliente y gestionar la respuesta
Cuando ya tengas el host y el puerto de destino, tienes que reenviar la petición del cliente al servidor de destino. En handle_client_request (), crea un socket web nuevo y úsalo para enviar la petición original al destino que quieras:
# create a socket to connect to the original destination server
destination_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# connect to the destination server
destination_socket.connect((host, port))
# send the original request
destination_socket.sendall(request)
Then, get ready to receive the server response and propagate it to the original client:
# read the data received from the server
# once chunk at a time and send it to the client
print("Received response:\n")
while True:
# receive data from web server
data = destination_socket.recv(1024)
# Receive data from the original destination server
print(f"{data.decode('utf-8')}")
# no more data to send
if len(data) > 0:
# send back to the client
client_socket.sendall(data)
else:
break
Aquí también tienes que trabajar los bloques de uno en uno porque no sabes el tamaño de la respuesta. Cuando los datos están vacíos, ya 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:
# close the sockets
destination_socket.close()
client_socket.close()
¡Genial! Acabas de crear un servidor proxy HTTP en Python. Ahora toca ver el código completo, ejecutarlo y comprobar que funciona correctamente.
Paso 6: juntarlo todo
Este es el código final de la secuencia de comandos de tu servidor proxy en Python:
import socket
import threading
def handle_client_request(client_socket):
print("Received request:\n")
# read the data sent by the client in the request
request = b''
client_socket.setblocking(False)
while True:
try:
# receive data from web server
data = client_socket.recv(1024)
request = request + data
# Receive data from the original destination server
print(f"{data.decode('utf-8')}")
except:
break
# extract the webserver's host and port from the request
host, port = extract_host_port_from_request(request)
# create a socket to connect to the original destination server
destination_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# connect to the destination server
destination_socket.connect((host, port))
# send the original request
destination_socket.sendall(request)
# read the data received from the server
# once chunk at a time and send it to the client
print("Received response:\n")
while True:
# receive data from web server
data = destination_socket.recv(1024)
# Receive data from the original destination server
print(f"{data.decode('utf-8')}")
# no more data to send
if len(data) > 0:
# send back to the client
client_socket.sendall(data)
else:
break
# close the sockets
destination_socket.close()
client_socket.close()
def extract_host_port_from_request(request):
# get the value after the "Host:" string
host_string_start = request.find(b'Host: ') + len(b'Host: ')
host_string_end = request.find(b'\r\n', 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)
# if there is a specific port
port_pos = host_string.find(":")
# no port specified
if port_pos == -1 or webserver_pos < port_pos:
# default port
port = 80
host = host_string[:webserver_pos]
else:
# extract the specific port from the host string
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
# bind the proxy server to a specific address and port
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('127.0.0.1', port))
# accept up to 10 simultaneous connections
server.listen(10)
print(f"Proxy server listening on port {port}...")
# listen for incoming requests
while True:
client_socket, addr = server.accept()
print(f"Accepted connection from {addr[0]}:{addr[1]}")
# create a thread to handle the client request
client_handler = threading.Thread(target=handle_client_request, args=(client_socket,))
client_handler.start()
if __name__ == "__main__":
start_proxy_server()
Launch it with this command:
python proxy_server.py
Deberías ver el siguiente mensaje en el terminal:
Proxy server listening on port 8888...
Para comprobar que el servidor funciona, ejecuta una petición de proxy con cURL. Lee nuestra guía para saber más sobre cómo usar cURL con un proxy.
Abre un nuevo terminal y ejecuta:
curl --proxy "http://127.0.0.1:8888" "http://httpbin.org/ip"
Eso enviaría una petición 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 punto final /ip del proyecto HTTPBin devuelve la IP de la que viene la petición. Si estás ejecutando el servidor de forma local, «origin» corresponderá a tu IP.
Nota: el servidor proxy en Python que hemos creado aquí solo funciona con destinos HTTP. Es bastante complicado extenderlo para gestionar conexiones HTTPS.
Ahora, echa un vistazo al registro que has escrito por la aplicación Python de tu servidor proxy. Debería incluir:
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 indica que el servidor proxy ha recibido la petición en el formato indicado por el protocolo HTTP. Después, lo ha reenviado al servidor de destino, ha registrado los datos de respuesta y ha devuelto la respuesta al cliente. ¿Por qué sabemos que eso es lo que ha hecho? Porque las IP que aparecen en «origin» son las mismas.
¡Enhorabuena! Acabas de aprender a crear un servidor proxy HTTP en Python.
Ventajas y desventajas de usar un servidor proxy en Python personalizado
Ahora que ya sabes cómo ejecutar un servidor proxy en Python, estás listo para conocer las ventajas y las limitaciones de este modelo.
Ventajas:
- Control absoluto: con una secuencia de comandos personalizada en Python como esta, tienes un control absoluto de todo lo que hace tu servidor proxy. No hay actividad sospechosa ni filtración de datos.
- Personalización: el servidor proxy se puede ampliar para incluir funciones útiles como el registro y el almacenamiento en caché de las peticiones para mejorar su rendimiento.
Desventajas:
- Costes de infraestructura: configurar una arquitectura de servidor proxy no es fácil y cuesta mucho dinero en términos de hardware o de servicios VPS.
- Mantenimiento complejo: tú eres el responsable de mantener la arquitectura del proxy, sobre todo de la escalabilidad y de la disponibilidad. Esta es una tarea que solo pueden abordar los administradores de sistemas profesionales.
- Falta de fiabilidad: el problema principal de esta solución es que la IP de salida del servidor proxy nunca cambia. Como resultado, los sistemas anti-bots podrán bloquear la IP e impedir que el servidor acceda a las peticiones que necesites. En otras palabras, el proxy, en algún momento, dejará de funcionar.
Estas limitaciones y desventajas son bastante negativas como para usar un servidor proxy en Python personalizado en un entorno de producción. ¿Cuál es la solución? Un proveedor de proxy fiable como Bright Data. Crea tu cuenta, confirma tu identidad, consigue un proxy gratuito y utilizado en tu lenguaje de programación preferido. Por ejemplo, integra un proxy en tu secuencia de comandos de Python con peticiones.
Nuestra gran red proxy incluye millones de servidores proxy rápidos, fiables y seguros en todo el mundo. Descubre por qué somos el mejor proveedor de servidores proxy.
Conclusión
En esta guía, has aprendido qué es un servidor proxy y cómo funciona en Python. Además, has aprendido a construir uno desde cero usando sockets web. Ya puedes decir que eres un experto en proxies en Python. El principal problema de este modelo es que la IP estática de salida de tu servidor proxy te bloqueará en algún momento. Evítalo con los proxies rotativos de Bright Data.
Bright Data controla los mejores servidores proxy del mundo y trabaja para empresas de la lista Fortune 500 y para más de 20 000 clientes. La oferta incluye una amplia gama de tipos de proxy:
- Proxies de centros de datos: más de 770 000 IP de centros de datos.
- Proxies residenciales: más de 72 millones de IP residenciales en más de 195 países.
- Proxys de ISP: más de 700 000 IP de ISP.
- Proxies móviles: más de 7 millones de IP móviles.
Una red proxy fiable, rápida y global también es la base de una serie de servicios de raspado web para recuperar datos de cualquier sitio de forma sencilla.