Что такое прокси-сервер Python

Прокси-сервер Python позволяет маршрутизировать запросы HTTP/S через обширную сеть IP-адресов с помощью кода Python. Он поддерживает такие функции, как ротация IP-адресов, сохранение сеанса и геолокационный таргетинг.
6 min read
Python Proxy Server

Из этого руководства вы узнаете:

Давайте узнаем подробности!

Что такое прокси-сервер Python?

Прокси-сервер Python — это приложение Python, выполняющее роль посредника между клиентами и Интернетом. Он перехватывает запросы клиентов, пересылает их на целевые серверы и отправляет ответ обратно клиенту. Таким образом, он скрывает личность клиента от целевых серверов. 

Прочитайте нашу статью, чтобы узнать, что такое прокси-сервер и как он работает

Возможности программирования сокетов Python позволяют легко реализовать базовый прокси-сервер, позволяющий пользователям анализировать, изменять или перенаправлять сетевой трафик. Прокси-серверы отлично подходят для кэширования, повышения производительности и безопасности при очистке веб-страниц.

Как реализовать прокси-сервер HTTP на Python

Выполните следующие шаги и узнайте, как создать скрипт прокси-сервера Python.

Шаг 1. Инициализация проекта Python

Перед началом работы убедитесь, что на вашем компьютере установлен Python 3+. В противном случае загрузите инсталлятор, запустите его и следуйте указаниям мастера установки.

Затем используйте следующие команды для создания папки python-http-proxy-server и инициализации проекта Python с виртуальной средой внутри него: 

mkdir python-http-proxy-server

cd python-http-proxy-server

python -m venv env

Откройте папку python-http-proxy-server в Python IDE и создайте пустой файл proxy_server.py.

Отлично! У вас есть все необходимое для создания прокси-сервера HTTP на Python.

Шаг 2. Инициализация входящего сокета

Во-первых, вам нужно создать сервер веб-сокетов для приема входящих запросов. Если вы не знакомы с этой концепцией, то сокет — это низкоуровневая программная абстракция, которая обеспечивает двунаправленный обмен данными между клиентом и сервером. В контексте веб-сервера серверный сокет используется для «прослушивания» входящих подключений от клиентов. 

Используйте следующие строки для создания веб-сервера на основе сокетов на 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)

Это инициализирует сервер входящих сокетов и привязывает его к локальному адресу http://127.0.0.1:8888. Затем это позволяет серверу принимать соединения с помощью способа прослушивания () .

Примечание: вы можете изменить номер порта, который должен прослушивать веб-прокси. Можно также изменить сценарий для считывания этой информации из командной строки для обеспечения максимальной гибкости. 

Сокет берется из стандартной библиотеки Python. Итак, поверх скрипта будут добавлены следующие импортированные данные:

import socket

Чтобы убедиться, что прокси-сервер Python запущен надлежащим образом, зарегистрируйте следующее сообщение:

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

Шаг 3. Прием запросов клиентов

Когда клиент подключается к прокси-серверу, ему необходимо создать новый сокет для связи с этим конкретным клиентом. Вот как это можно сделать в 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()

для одновременной обработки нескольких клиентских запросов следует использовать многопоточность, как указано выше. Не забудьте импортировать функцию многопоточности из стандартной библиотеки Python:

import threading

Как вы можете видеть, прокси-сервер обрабатывает входящие запросы с помощью специальной функции handle_client_request (). Порядок определения данной функции описан в следующих шагах.

Шаг 4. Обработка входящих запросов

После создания клиентского сокета вам необходимо использовать его для:

  1. Считывания данных из входящих запросов.
  2. Извлечения хоста и порта целевого сервера из этих данных.
  3. Использования его для пересылки клиентского запроса на целевой сервер.
  4. Получения ответа и отправки его исходному клиенту.

В этом разделе давайте сосредоточимся на первых двух шагах. Определите функцию handle_client_request () и используйте ее для чтения данных из входящего запроса:

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 (ложь) переводит клиентский сокет в неблокирующий режим. Затем используйте recv () для чтения входящих данных и добавления их в запрос в байтовом формате. Поскольку вы не знаете размер данных входящего запроса, вам потребуется считывать их по частям за раз. В данном случае указан фрагмент размером 1024 байта. В неблокирующем режиме, если recv () не найдет никаких данных, возникнет исключение из-за ошибки. Таким образом, инструкция except («исключение») обозначает завершение операции.

Обратите внимание на зарегистрированные сообщения, чтобы отслеживать, что делает прокси-сервер Python.

После получения входящего запроса вам необходимо извлечь из него хост и порт целевого сервера:

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 () извлекает хост и порт веб-сервера из поля «Host:» («Хост:»). В данном случае хостом является example.com, а порт — 80 (поскольку конкретный порт не указан). 

Шаг 5. Пересылка запроса клиента и обработка ответа

Зная целевой хост и порт, теперь вам нужно переслать запрос клиента на целевой сервер. В handle_client_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)

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

Опять же, вам нужно работать по одному фрагменту за раз, так как вы не знаете размер ответа. Если данные пусты, их больше не нужно принимать и операцию можно прервать.

Не забудьте закрыть два сокета, которые вы определили в функции:

# close the sockets

destination_socket.close()

client_socket.close()

Потрясающе! Вы только что создали прокси-сервер HTTP на Python. Пора посмотреть весь код, запустить его и убедиться, что он работает должным образом!

Шаг 6. Соберите все воедино

Это окончательный код скрипта вашего прокси-сервера на 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

В терминале должно появиться следующее сообщение:

Proxy server listening on port 8888...

Чтобы убедиться, что сервер работает, выполните прокси-запрос с помощью cURL. Прочтите наше руководство, чтобы узнать больше о том, как использовать cURL с прокси-сервером.

Откройте новый терминал и запустите:

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

Это позволит отправить запрос GET к адресату http://httpbin.org/ip через прокси-сервер http://127.0.0.1:8888.

У вас должно получиться что-то вроде:

{

  "origin": "45.12.80.183"

}

Это IP-адрес прокси-сервера. Почему? Поскольку конечная точка /ip проекта HttpBin возвращает IP-адрес, с которого поступил запрос. Если вы запускаете сервер локально, «origin» будет соответствовать вашему IP-адресу. 

Примечание: созданный здесь прокси-сервер на Python работает только с адресатами HTTP. Расширить его для обработки HTTPS-подключений довольно сложно.

Теперь изучите журнал, записанный вашим приложением прокси-сервере на Python. Он должен содержать:

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"

}

Это говорит о том, что прокси-сервер получил запрос в формате, указанном протоколом HTTP. Затем он переслал его на целевой сервер, зарегистрировал данные ответа и отправил ответ обратно клиенту. Почему мы в этом уверены? Потому что IP-адреса в «origin» одинаковы!

Поздравляем! Вы только что научились создавать прокси-сервер HTTP на Python!

Плюсы и минусы использования собственного прокси-сервера на Python

Теперь, когда вы знаете, как реализовать прокси-сервер на Python, вы готовы узнать преимущества и недостатки этого подхода.

Преимущества:

  • Полный контроль. С помощью такого настраиваемого скрипта Python вы полностью контролируете работу вашего прокси-сервера. Не будет никакой теневой активности или утечки данных!
  • Настройка. Прокси-сервер можно расширить, включив в него полезные функции, такие как ведение журнала и кэширование запросов, для повышения производительности.

Недостатки:

  • Стоимость инфраструктуры. Настроить архитектуру прокси-сервера непросто и стоит больших денег с точки зрения оборудования или услуг VPS.
  • Сложное обслуживание. Вы несете ответственность за сохранение архитектуры прокси-сервера, особенно за его масштабируемость и доступность. С этой задачей могут справиться только опытные системные администраторы.
  • Ненадежность. Основная проблема этого решения заключается в том, что выходной IP-адрес прокси-сервера никогда не меняется. В результате технологии защиты от ботов смогут блокировать IP-адрес и препятствовать доступу сервера к нужным запросам. Другими словами, прокси-сервер в конечном итоге перестанет работать.

Эти ограничения и недостатки слишком опасны для использования собственного прокси-сервера Python в производственном сценарии. Решение? Надежный поставщик услуг прокси-серверов, такой как Bright Data! Создайте аккаунт, подтвердите свою личность, получите бесплатный прокси-сервер и используйте его на своем любимом языке программирования. Например, интегрируйте прокси-сервер в свой скрипт Python с помощью запросов.

Наша огромная сеть включает миллионы быстрых, надежных и безопасных прокси-серверов по всему миру. Узнайте, почему мы являемся лучшим поставщиком прокси-серверов.

Заключение

Из этого руководства вы узнали, что такое прокси-сервер и как он работает в Python. В частности, вы узнали, как создать его с нуля с помощью веб-сокетов. Теперь вы стали мастером в созданию прокси-серверов на Python. Основная проблема этого подхода заключается в том, что статический выходной IP-адрес вашего прокси-сервера в конечном итоге приведет к тому, что вас заблокируют. Избегайте этого с помощью ротируемых прокси-серверов Bright Data!

Bright Data контролирует лучшие прокси-серверы в мире, обслуживая компании из списка Fortune 500 и более 20 000 клиентов. Предложение компании включает в себя широкий ассортиментов прокси-серверов различных типов:

Эта надежная, быстрая и глобальная прокси-сеть также является основой ряда сервисов скрейпинга веб-страниц, позволяющих легко извлекать данные с любого сайта.