Qu’est-ce qu’un serveur proxy Python ?

Le serveur proxy Python permet le routage des requêtes HTTP/S via un vaste réseau d’adresses IP via du code Python. Il prend en charge des fonctionnalités telles que la rotation des adresses IP, la persistance des sessions et le ciblage par géolocalisation.
14 min de lecture
Python Proxy Server

Dans ce didacticiel, vous allez apprendre :

C’est parti !

Qu’est-ce qu’un serveur proxy Python ?

Un serveur proxy Python est une application Python qui sert d’intermédiaire entre les clients et Internet. Il intercepte les demandes des clients, les transmet aux serveurs cibles et renvoie la réponse au client. Ce faisant, il masque l’identité du client aux serveurs de destination.

Lisez notre article pour découvrir ce qu’est un serveur proxy et comment il fonctionne.

Les capacités de programmation de socket de Python facilitent la mise en œuvre d’un serveur proxy de base, permettant aux utilisateurs d’inspecter, de modifier ou de rediriger le trafic réseau. Les serveurs proxy sont parfaits pour la mise en cache, l’amélioration des performances et la sécurité en matière de web scraping.

Comment implémenter un serveur proxy HTTP en Python

Suivez les étapes ci-dessous et apprenez à créer un script de serveur proxy Python.

Étape 1 : initialisez votre projet Python

Avant de commencer, assurez-vous que Python 3+ est installé sur votre machine. Sinon, téléchargez le programme d’installation, exécutez-le et suivez-en les instructions.

Ensuite, utilisez les commandes ci-dessous pour créer un dossier python-http-proxy-server et initialiser un projet Python contenant un environnement virtuel :

mkdir python-http-proxy-server

cd python-http-proxy-server

python -m venv env

Ouvrez le dossier python-http-proxy-server dans votre IDE Python et créez un fichier proxy_server.py vide.

Formidable ! Vous avez tout ce dont vous avez besoin pour créer un serveur proxy HTTP en Python.

Étape 2 : initialisez un socket entrant

Tout d’abord, vous devez créer un serveur de socket Web pour accepter les demandes entrantes. Si vous n’êtes pas familier avec ce concept, un socket est une abstraction de programmation de bas niveau qui permet un flux de données bidirectionnel entre un client et un serveur. Dans le contexte d’un serveur Web, un socket de serveur est utilisé pour écouter les connexions entrantes des clients. 

Utilisez les lignes suivantes pour créer un serveur Web basé sur des 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)

Cela initialise un serveur de socket entrant et le lie à l’adresse locale http://127.0.0.1:8888. Il permet ensuite au serveur d’accepter les connexions avec la méthode listen ().

Remarque : n’hésitez pas à modifier le numéro du port que le proxy Web doit écouter. Vous pouvez également modifier le script pour lire ces informations depuis la ligne de commande pour une flexibilité maximale.

Le socket provient de la bibliothèque standard Python. Vous aurez donc l’importation suivante en plus de votre script :

import socket

Pour vérifier que le serveur proxy Python a démarré comme prévu, enregistrez ce message :

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

Étape 3 : acceptez les demandes des clients

Lorsqu’un client se connecte au serveur proxy, celui-ci doit créer un nouveau socket pour gérer la communication avec ce client spécifique. Voici comment procéder en Python :

# 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()

Pour gérer plusieurs demandes clients simultanément, vous devez utiliser le multithreading comme ci-dessus. N’oubliez pas d’importer threading depuis la bibliothèque standard Python :

import threading

Comme vous pouvez le constater, le serveur proxy gère les demandes entrantes via la fonction personnalisée handle_client_request (). Découvrez comment il est défini dans les étapes suivantes.

Étape 4 : traitez les demandes entrantes

Une fois le socket client créé, vous devez l’utiliser pour :

  1. Lire les données des demandes entrantes.
  2. Extraire l’hôte et le port du serveur cible à partir de ces données.
  3. Les utiliser pour transmettre la demande du client au serveur de destination.
  4. Obtenir la réponse et la transférer au client d’origine.

Dans cette section, concentrons-nous sur les deux premières étapes. Définissez la fonction handle_client_request() et utilisez-la pour lire les données de la requête entrante :

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) met le socket client en mode non bloquant. Ensuite, utilisez recv() pour lire les données entrantes et les ajouter à la requête au format octet. Comme vous ne connaissez pas la taille des données de demande entrantes, vous devez les lire un morceau à la fois. Dans ce cas, un bloc de 1 024 octets a été spécifié. En mode non bloquant, si recv() ne trouve aucune donnée, cela déclenchera une exception d’erreur. Ainsi, l’instruction except marque la fin de l’opération.

Notez les messages enregistrés pour suivre ce que fait le serveur proxy Python.

Après avoir récupéré la demande entrante, vous devez en extraire l’hôte et le port du serveur de destination :

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() extrait l’hôte et le port du serveur Web à partir du champ « Host: ». Dans ce cas, l’hôte est example.com et le port est 80 (car aucun port spécifique n’a été spécifié). 

Étape 5 : transférez la demande du client et gérez la réponse

Compte tenu de l’hôte et du port cibles, vous devez maintenant transmettre la demande du client au serveur de destination. Dans handle_client_request(), créez un nouveau socket Web et utilisez-le pour envoyer la demande d’origine à la destination souhaitée :

# 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

Encore une fois, vous devez travailler un morceau à la fois, car vous ne connaissez pas la taille de la réponse. Lorsque les données sont vides, il n’y a plus de données à recevoir et vous pouvez mettre fin à l’opération.

N’oubliez pas de fermer les deux sockets que vous avez définis dans la fonction :

# close the sockets

destination_socket.close()

client_socket.close()

Génial ! Vous venez de créer un serveur proxy HTTP en Python. Il est temps de voir l’intégralité du code, de le lancer et de vérifier qu’il fonctionne comme prévu !

Étape 6 : assemblez le tout

Voici le code final du script de votre serveur proxy 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

Le message suivant devrait s’afficher dans le terminal :

Proxy server listening on port 8888...

Pour vous assurer que le serveur fonctionne, exécutez une requête proxy avec cURL. Lisez notre guide pour en savoir plus sur comment utiliser cURL avec un proxy.

Ouvrez un nouveau terminal et exécutez :

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

Cela enverrait une requête GET à la destination http://httpbin.org/ip via le serveur proxy http://127.0.0.1:8888.

Vous devriez obtenir quelque chose comme :

{

  "origin": "45.12.80.183"

}

Il s’agit de l’adresse IP du serveur proxy. Pourquoi ? Parce que l’endpoint /ip du projet HTTPBin renvoie l’adresse IP d’où provient la demande. Si vous utilisez le serveur localement, « origin » correspondra à votre adresse IP. 

Remarque : le serveur proxy Python créé ici ne fonctionne qu’avec des destinations HTTP. L’étendre pour gérer les connexions HTTPS est assez difficile.

À présent, explorez le journal écrit par l’application Python de votre serveur proxy. Il doit contenir :

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"

}

Cela vous indique que le serveur proxy a reçu la demande dans le format spécifié par le protocole HTTP. Il l’a ensuite transmis au serveur de destination, a enregistré les données de réponse et a renvoyé la réponse au client. Pourquoi en sommes-nous sûrs ? Parce que les adresses IP dans « origin » sont les mêmes !

Félicitations ! Vous venez d’apprendre à créer un serveur proxy HTTP en Python !

Avantages et inconvénients de l’utilisation d’un serveur proxy Python personnalisé

Maintenant que vous savez comment implémenter un serveur proxy en Python, vous êtes prêt(e) à découvrir les avantages et les limites de cette approche.

Avantages :

  • Contrôle total : avec un script Python personnalisé comme celui-ci, vous avez un contrôle total sur le fonctionnement de votre serveur proxy. Pas d’activité douteuse ni de fuite de données !
  • Personnalisation : le serveur proxy peut être étendu pour inclure des fonctionnalités utiles telles que la journalisation et la mise en cache des requêtes afin d’améliorer les performances.

Inconvénients :

  • Coûts d’infrastructure : la mise en place d’une architecture de serveur proxy n’est pas facile et coûte cher en termes de matériel ou de services VPS.
  • Difficile à gérer : vous êtes responsable de la maintenance de l’architecture du proxy, en particulier de son évolutivité et de sa disponibilité. Il s’agit d’une tâche que seuls les administrateurs système expérimentés peuvent effectuer.
  • Peu fiable : le principal problème avec cette solution est que l’adresse IP de sortie du serveur proxy ne change jamais. Par conséquent, les technologies anti-bot seront en mesure de bloquer l’IP et d’empêcher le serveur d’accéder aux requêtes souhaitées. En d’autres termes, le proxy finira par cesser de fonctionner.

Ces limites et inconvénients sont trop graves pour utiliser un serveur proxy Python personnalisé dans un scénario de production. La solution ? Un fournisseur de proxy fiable comme Bright Data ! Créez un compte, vérifiez votre identité, obtenez un proxy gratuit et utilisez-le dans votre langage de programmation préféré. Par exemple, intégrez un proxy dans votre script Python avec des requêtes.

Notre vaste réseau de proxy comprend des millions de serveurs proxy rapides, fiables et sécurisés dans le monde entier. Découvrez pourquoi nous sommes le meilleur fournisseur de serveurs proxy.

Conclusion

Dans ce guide, vous avez appris ce qu’est un serveur proxy et comment il fonctionne en Python. Dans le détail, vous avez appris à en créer un à partir de zéro à l’aide de sockets Web. Vous êtes désormais un maître des proxys en Python. Le principal problème avec cette approche est que l’adresse IP de sortie statique de votre serveur proxy finira par vous bloquer. Évitez cela avec les proxys rotatifs de Bright Data !

Bright Data contrôle les meilleurs serveurs proxy au monde, au service des entreprises du Fortune 500 et de plus de 20 000 clients. Son offre comprend un large éventail de types de proxy :

Ce réseau proxy fiable, rapide et mondial est également à la base d’un certain nombre de services de web scraping permettant de récupérer facilement des données depuis n’importe quel site.