Сбор данных с помощью мобильного прокси на Python
Рабочий код прокси для запросов, httpx, aiohttp и Scrapy — словарь прокси, который многие понимают неверно, схемы ротации, выдерживающие нагрузку реальных сайтов, и алгоритм отсрочки, учитывающий параметр Retry-After вместо того, чтобы засыпать сайт ошибками 429.
- Работает со стандартным набором библиотек — для requests, httpx, aiohttp и Scrapy не требуется специальный клиент.
- Один URL-адрес прокси-сервера — одинаковая строка http://user:pass@host:port, понятная каждому клиенту.
- Ротация по запросу — инициируйте использование нового IP-адреса из собственного кода на границах заданий.
- Неограниченная пропускная способность — количество одновременных подключений и размер передаваемых данных никак не влияют на сумму счета.
Обычные конечные точки HTTP(S) и SOCKS5 — не требуется интеграция с SDK поставщика.
Инициировать использование нового IP-адреса выхода на границах заданий, а не по фиксированному таймеру.
запросы и httpx: сессии, словари прокси и туннель CONNECT
Если нужная вам разметка уже содержится в исходном ответе, не запускайте браузер. HTTP-клиент на порядок экономичнее с точки зрения использования памяти и времени. Используйте одну сессию, чтобы пул соединений и хранилище файлов cookie использовались повторно для всех запросов, а не приходилось заново устанавливать соединение TLS при каждом вызове:
import os
import requests
PROXY = os.environ["PXM2_PROXY"] # http://user:pass@host:port
with requests.Session() as s:
s.proxies.update({"http": PROXY, "https": PROXY})
s.headers.update({
"User-Agent": "Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36 ...",
"Accept-Language": "en-GB,en;q=0.9",
})
r = s.get("https://example.com/listings", timeout=30)
r.raise_for_status()
Деталь, которую многие упускают из виду, — это запись https. В ней по-прежнему используется схема http://, поскольку эта схема описывает способ подключения к прокси, а не протокол целевого сервера. Клиент открывает туннель CONNECT через прокси, а затем внутри этого туннеля запускается TLS, обеспечивающий сквозное шифрование, поэтому прокси никогда не видит ваш текст в открытом виде. Использование вместо этого https:// является самой распространённой причиной, по которой прокси на Python, казалось бы, не работает с HTTPS.
Второй важный момент — это согласованность: мобильный User-Agent должен сопоставляться с мобильным IP-адресом. Претензия на то, что устройство является телефоном на базе Android, при этом IP-адрес принадлежит диапазону, характерному для центров обработки данных, представляет собой противоречие, которое может обнаружить любой уровень идентификации по отпечаткам.
httpx работает точно так же, но с другим ключевым словом — прокси передается в конструктор Client. Его стоит использовать, если требуется HTTP/2, поскольку версия протокола, соответствующая браузеру, устраняет ещё одно несоответствие между заявленными в заголовках параметрами и фактическим поведением клиента.
| Библиотека | Куда направляется доверенность | Поворотный крюк | Лучше всего подходит для |
|---|---|---|---|
| requests | Словарь Session.proxies или прокси для каждого вызова= | Один сеанс на один прокси-сервер | Простые синхронные сканирования |
| httpx | proxy= включено на клиенте | Один клиент на один прокси-сервер | HTTP/2, или синхронный и асинхронный режимы в одном API |
| aiohttp | proxy= в отдельном запросе | По запросу, поэтому ротация не представляет сложности | Высокая степень параллелизма |
| Scrapy | request.meta["proxy"] | Промежуточное ПО для загрузки файлов | Крупные структурированные сканирования |
Чередование прокси-серверов в Python: алгоритмы «Round-Robin», «Random» и «Rotate-on-Failure»
Здесь речь идет о двух совершенно разных ситуациях, и их смешение и является основной причиной путаницы. Если вам предоставлена одна вращающаяся конечная точка, ротация происходит на верхнем уровне, а ваш код лишь определяет, когда необходимо запросить новый IP-адрес. Если же у вас есть список конечных точек, вы сами определяете политику ротации.
Во втором случае назначайте один прокси на сессию, а не на каждый запрос. Сессия содержит пул соединений и набор файлов cookie, поэтому смена прокси в ходе работы сводит на нет весь смысл его использования:
import itertools
import requests
PROXIES = [
"http://user:pass@host:10001",
"http://user:pass@host:10002",
"http://user:pass@host:10003",
]
def sessions_for(proxies):
"One Session per exit IP, reused for the life of a job."
out = []
for p in proxies:
s = requests.Session()
s.proxies.update({"http": p, "https": p})
out.append(s)
return out
pool = itertools.cycle(sessions_for(PROXIES))
for job in jobs:
session = next(pool) # a whole job stays on one IP
run(session, job)
Обратите внимание на то, чего цикл не делает: он никогда не изменяет IP-адрес внутри функции run(). Переключение происходит между логическими задачами, но никогда не происходит в середине одной из них — IP-адрес выхода, изменяющийся во время аутентифицированного потока, выглядит как перехват сеанса, что является гораздо более тревожным сигналом, чем сам скрапинг.
Случайный выбор является разумной альтернативой алгоритму «по кругу», когда задания независимы друг от друга и необходимо избежать предсказуемого цикла. К любому из этих подходов стоит добавить принцип «ротации при сбое»: когда прокси начинает возвращать блоки, его следует исключить из пула, а не продолжать направлять ему задания.
Работа с 429-кодами: Retry-After, экспоненциальный откат и джиттер
Код ошибки 429 означает, что частота запросов неверна. Смена IP-адреса и повторная отправка запроса приравнивают проблему с частотой запросов к проблеме идентификации, и именно так сканирование переходит из режима с ограничением пропускной способности в режим блокировки. Соблюдайте параметр Retry-After и применяйте экспоненциальное отступление:
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
retry = Retry(
total=5,
backoff_factor=1.5, # 1.5s, 3s, 6s, 12s, 24s
status_forcelist=[429, 500, 502, 503, 504],
respect_retry_after_header=True,
allowed_methods=["GET", "HEAD"],
)
s.mount("https://", HTTPAdapter(max_retries=retry, pool_maxsize=20))
Если вы запускаете параллельные рабочие процессы, добавьте к этому некоторую неравномерность, иначе все они «проснутся» и начнут повторные попытки одновременно — получится «грохочущее стадо», которое будет выглядеть гораздо более механически, чем исходный трафик. При этом параллелизм должен обеспечиваться на уровне каждого целевого хоста, а не глобально: восемь параллельных запросов, распределенных по восьми доменам, — это норма, а восемь запросов на один домен — это всплеск нагрузки.
Повторные попытки должны быть идемпотентными. Использование `status_forcelist` в сочетании с запросом POST, создающим какой-либо объект, приведёт к его многократному созданию. Ограничение `allowed_methods` методами GET и HEAD, как показано выше, является безопасным значением по умолчанию.
Scrapy и asyncio: промежуточное программное обеспечение, aiohttp и ограничения параллелизма
Scrapy already ships proxy support. Its built-in HttpProxyMiddleware reads request.meta["proxy"], so setting that key in a spider or a small middleware is enough for a single endpoint. For rotation across a list, scrapy-rotating-proxies adds a pool and its own ban tracking:
ROTATING_PROXY_LIST = [
"http://user:pass@host:10001",
"http://user:pass@host:10002",
]
# or: ROTATING_PROXY_LIST_PATH = "proxies.txt"
DOWNLOADER_MIDDLEWARES = {
"rotating_proxies.middlewares.RotatingProxyMiddleware": 610,
"rotating_proxies.middlewares.BanDetectionMiddleware": 620,
}
CONCURRENT_REQUESTS_PER_DOMAIN = 4
DOWNLOAD_DELAY = 0.5
AUTOTHROTTLE_ENABLED = True
Его эвристика блокировки по умолчанию рассматривает статус, отличный от 200, пустое тело запроса или исключение как неработающий прокси-сервер, что является слишком грубым подходом — цель, которая отвечает на блокировку кодом 200 и страницей запроса аутентификации, просто обойдет эту блокировку. Переопределите ROTATING_PROXY_BAN_POLICY с помощью класса, который распознает, как ведет себя конкретный целевой сервер при блокировке вашего запроса.
Два параметра внизу играют более важную роль для предотвращения блокировки, чем промежуточное ПО. Функция AutoThrottle адаптирует задержку в зависимости от наблюдаемой задержки, а ограничение на количество одновременных запросов для каждого домена не позволяет широкому сканированию превратиться в массовый поток запросов к одному хосту.
При использовании aiohttp прокси применяется к отдельному запросу, а не к сеансу, что значительно упрощает ротацию по запросам, но при этом ответственность за соблюдение правил вежливого доступа к хостам полностью ложится на вас. Оберните свои запросы в семафор, индексируемый по целевому хосту — с помощью asyncio очень легко превратить вежливый сканирование в непреднамеренную атаку типа «отказ в обслуживании».
Получите выделенный прокси-сервер для сбора данных на Python
Активные локации PXM2 — выберите страну, из которой, по вашему замыслу, должен поступать запрос, и получите выделенный IP-адрес с поддержкой 4G/5G, неограниченной пропускной способностью и возможностью смены адреса:
Великобритания
Испания
Индия
Часто задаваемые вопросы
Как организовать ротацию прокси в модуле requests на Python?
Ведите список конечных точек и циклически обрабатывайте его с помощью `itertools.cycle`, назначая один прокси на сессию, а не на каждый запрос — сессия содержит пул соединений и хранилище файлов cookie, поэтому смена прокси в ходе работы сессии сводит на нет смысл её использования. В случае одной ротирующейся конечной точки ротация происходит на верхнем уровне, и вашему коду нужно лишь определить, когда запрашивать новый IP-адрес.
Почему мой прокси-сервер Python не работает с HTTPS?
Практически всегда это происходит из-за того, что записи https в словаре прокси указана схема https://. Эта схема описывает способ подключения к прокси, а не протокол целевого сервера, и для обычного HTTP-прокси она по-прежнему должна быть http://. В этом случае клиент открывает через него туннель CONNECT, и TLS работает от конца до конца внутри этого туннеля.
Как использовать прокси с aiohttp?
Передавайте параметр `proxy=` в отдельном запросе, а не в сессии, поскольку aiohttp учитывает его для каждого вызова. Учетные данные указываются в URL или в объекте aiohttp.BasicAuth, передаваемом в качестве proxy_auth. Ограничивайте количество одновременных запросов к целевому хосту с помощью семафора — asyncio позволяет с легкостью превратить «вежливый» сканирование в серию запросов, которая приведет к ограничению скорости.
Как настроить прокси в Scrapy?
Встроенный HttpProxyMiddleware считывает значение request.meta["proxy"], поэтому для работы с одним конечным пунктом достаточно просто задать этот ключ в спайдере или пользовательском промежуточном модуле. Для ротации по списку модуль scrapy-rotating-proxies добавляет ROTATING_PROXY_LIST и свои собственные промежуточные модули, а также отслеживает, какие конечные точки выглядят заблокированными, чтобы прекратить их использование.
Как устранить ошибки 429 при скрапинге на Python?
Считайте значение заголовка Retry-After и подождите столько времени. Если сервер не отправляет этот заголовок, используйте экспоненциальный откат с джиттером. Функция Retry в urllib3 делает и то, и другое — установите respect_retry_after_header и backoff_factor, подключите её к HTTPAdapter и сдерживайте желание сменить IP-адрес и повторить попытку сразу: код 429 указывает на проблему с частотой запросов, и если рассматривать его как проблему идентификации, то сканирование может привести к жесткой блокировке.
Связанные руководства по мобильным прокси-серверам
Если целевой ресурс отображает данные с помощью JavaScript, приведенный выше путь HTTP-клиента не позволит к нему добраться — этот случай описан в руководстве по работе с браузером.
Руководства по веб-парсингу
Основные руководства по мобильным прокси-серверам
Направьте свой скрейпер на Python на IP-адрес оператора связи
Специализированные модемы 4G/5G на стандартных конечных точках HTTP(S) и SOCKS5 — без SDK от поставщика, без ограничения пропускной способности и с возможностью ротации, которую вы запускаете из собственного кода.
Получить прокси для веб-парсинга на Python