Назад к блогу

Как исправить timeout ошибки при работе через прокси

Timeout ошибки через прокси — частая проблема при парсинге и автоматизации. Разбираем причины и даём работающие решения с примерами кода.

📅15 декабря 2025 г.

Как исправить timeout ошибки при работе через прокси

Запрос завис, скрипт упал с ошибкой TimeoutError, данные не получены. Знакомая ситуация? Timeout-ошибки через прокси — одна из самых частых проблем при парсинге и автоматизации. Разберём причины и дадим конкретные решения.

Почему возникают timeout ошибки

Timeout — это не одна проблема, а симптом. Прежде чем лечить, нужно понять причину:

Медленный прокси-сервер. Перегруженный сервер или географически удалённый прокси добавляют задержку к каждому запросу. Если ваш таймаут 10 секунд, а прокси отвечает за 12 — ошибка.

Блокировка на стороне целевого сайта. Сайт может намеренно «подвешивать» подозрительные запросы вместо явного отказа. Это тактика против ботов — держать соединение открытым бесконечно.

Проблемы с DNS. Прокси должен резолвить домен. Если DNS-сервер прокси медленный или недоступен — запрос зависает на этапе подключения.

Неправильная настройка таймаутов. Один общий таймаут на всё — частая ошибка. Connect timeout и read timeout — разные вещи, и настраивать их нужно отдельно.

Сетевые проблемы. Потеря пакетов, нестабильное соединение прокси, проблемы маршрутизации — всё это приводит к таймаутам.

Типы таймаутов и их настройка

Большинство HTTP-библиотек поддерживают несколько типов таймаутов. Понимание разницы между ними — ключ к правильной настройке.

Connect timeout

Время на установку TCP-соединения с прокси и целевым сервером. Если прокси недоступен или сервер не отвечает — сработает этот таймаут. Рекомендуемое значение: 5-10 секунд.

Read timeout

Время ожидания данных после установки соединения. Сервер подключился, но молчит — сработает read timeout. Для обычных страниц: 15-30 секунд. Для тяжёлых API: 60+ секунд.

Total timeout

Общее время на весь запрос от начала до конца. Страховка от зависших соединений. Обычно: connect + read + запас.

Пример настройки в Python с библиотекой requests:

import requests

proxies = {
    "http": "http://user:pass@proxy.example.com:8080",
    "https": "http://user:pass@proxy.example.com:8080"
}

# Кортеж: (connect_timeout, read_timeout)
timeout = (10, 30)

try:
    response = requests.get(
        "https://target-site.com/api/data",
        proxies=proxies,
        timeout=timeout
    )
except requests.exceptions.ConnectTimeout:
    print("Не удалось подключиться к прокси или серверу")
except requests.exceptions.ReadTimeout:
    print("Сервер не отправил данные вовремя")

Для aiohttp (асинхронный Python):

import aiohttp
import asyncio

async def fetch_with_timeout():
    timeout = aiohttp.ClientTimeout(
        total=60,      # Общий таймаут
        connect=10,    # На подключение
        sock_read=30   # На чтение данных
    )
    
    async with aiohttp.ClientSession(timeout=timeout) as session:
        async with session.get(
            "https://target-site.com/api/data",
            proxy="http://user:pass@proxy.example.com:8080"
        ) as response:
            return await response.text()

Retry-логика: правильный подход

Таймаут — не всегда фатальная ошибка. Часто повторный запрос проходит успешно. Но retry нужно делать с умом.

Экспоненциальная задержка

Не долбите сервер повторными запросами без паузы. Используйте экспоненциальный backoff: каждая следующая попытка — с увеличивающейся задержкой.

import requests
import time
import random

def fetch_with_retry(url, proxies, max_retries=3):
    """Запрос с retry и экспоненциальной задержкой"""
    
    for attempt in range(max_retries):
        try:
            response = requests.get(
                url,
                proxies=proxies,
                timeout=(10, 30)
            )
            response.raise_for_status()
            return response
            
        except (requests.exceptions.Timeout, 
                requests.exceptions.ConnectionError) as e:
            
            if attempt == max_retries - 1:
                raise  # Последняя попытка — пробрасываем ошибку
            
            # Экспоненциальная задержка: 1s, 2s, 4s...
            # + случайный jitter чтобы не создавать волны запросов
            delay = (2 ** attempt) + random.uniform(0, 1)
            print(f"Попытка {attempt + 1} не удалась: {e}")
            print(f"Повтор через {delay:.1f} секунд...")
            time.sleep(delay)

Библиотека tenacity

Для production-кода удобнее использовать готовые решения:

from tenacity import retry, stop_after_attempt, wait_exponential
from tenacity import retry_if_exception_type
import requests

@retry(
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=1, min=1, max=10),
    retry=retry_if_exception_type((
        requests.exceptions.Timeout,
        requests.exceptions.ConnectionError
    ))
)
def fetch_data(url, proxies):
    response = requests.get(url, proxies=proxies, timeout=(10, 30))
    response.raise_for_status()
    return response.json()

Ротация прокси при таймаутах

Если один прокси постоянно даёт таймауты — проблема в нём. Логичное решение: переключиться на другой.

import requests
from collections import deque
from dataclasses import dataclass, field
from typing import Optional
import time

@dataclass
class ProxyManager:
    """Менеджер прокси с отслеживанием неудачных попыток"""
    
    proxies: list
    max_failures: int = 3
    cooldown_seconds: int = 300
    _failures: dict = field(default_factory=dict)
    _cooldown_until: dict = field(default_factory=dict)
    
    def get_proxy(self) -> Optional[str]:
        """Получить рабочий прокси"""
        current_time = time.time()
        
        for proxy in self.proxies:
            # Пропускаем прокси на cooldown
            if self._cooldown_until.get(proxy, 0) > current_time:
                continue
            return proxy
        
        return None  # Все прокси на cooldown
    
    def report_failure(self, proxy: str):
        """Сообщить о неудачном запросе"""
        self._failures[proxy] = self._failures.get(proxy, 0) + 1
        
        if self._failures[proxy] >= self.max_failures:
            # Отправляем прокси на cooldown
            self._cooldown_until[proxy] = time.time() + self.cooldown_seconds
            self._failures[proxy] = 0
            print(f"Прокси {proxy} отправлен на cooldown")
    
    def report_success(self, proxy: str):
        """Сбросить счётчик ошибок при успехе"""
        self._failures[proxy] = 0


def fetch_with_rotation(url, proxy_manager, max_attempts=5):
    """Запрос с автоматической сменой прокси при ошибках"""
    
    for attempt in range(max_attempts):
        proxy = proxy_manager.get_proxy()
        
        if not proxy:
            raise Exception("Нет доступных прокси")
        
        proxies = {"http": proxy, "https": proxy}
        
        try:
            response = requests.get(url, proxies=proxies, timeout=(10, 30))
            response.raise_for_status()
            proxy_manager.report_success(proxy)
            return response
            
        except (requests.exceptions.Timeout, 
                requests.exceptions.ConnectionError):
            proxy_manager.report_failure(proxy)
            print(f"Таймаут через {proxy}, пробуем другой...")
            continue
    
    raise Exception(f"Не удалось получить данные после {max_attempts} попыток")

При использовании резидентных прокси с автоматической ротацией эта логика упрощается — провайдер сам переключает IP при каждом запросе или по заданному интервалу.

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

При массовом парсинге синхронные запросы неэффективны. Асинхронный подход позволяет обрабатывать сотни URL параллельно, но требует аккуратной работы с таймаутами.

import aiohttp
import asyncio
from typing import List, Tuple

async def fetch_one(
    session: aiohttp.ClientSession, 
    url: str,
    semaphore: asyncio.Semaphore
) -> Tuple[str, str | None, str | None]:
    """Загрузка одного URL с обработкой таймаута"""
    
    async with semaphore:  # Ограничиваем параллельность
        try:
            async with session.get(url) as response:
                content = await response.text()
                return (url, content, None)
                
        except asyncio.TimeoutError:
            return (url, None, "timeout")
        except aiohttp.ClientError as e:
            return (url, None, str(e))


async def fetch_all(
    urls: List[str],
    proxy: str,
    max_concurrent: int = 10
) -> List[Tuple[str, str | None, str | None]]:
    """Массовая загрузка с контролем таймаутов и параллельности"""
    
    timeout = aiohttp.ClientTimeout(total=45, connect=10, sock_read=30)
    semaphore = asyncio.Semaphore(max_concurrent)
    
    connector = aiohttp.TCPConnector(
        limit=max_concurrent,
        limit_per_host=5  # Не больше 5 соединений на один хост
    )
    
    async with aiohttp.ClientSession(
        timeout=timeout,
        connector=connector
    ) as session:
        # Устанавливаем прокси для всех запросов
        tasks = [
            fetch_one(session, url, semaphore) 
            for url in urls
        ]
        results = await asyncio.gather(*tasks)
    
    # Статистика
    success = sum(1 for _, content, _ in results if content)
    timeouts = sum(1 for _, _, error in results if error == "timeout")
    print(f"Успешно: {success}, Таймауты: {timeouts}")
    
    return results


# Использование
async def main():
    urls = [f"https://example.com/page/{i}" for i in range(100)]
    results = await fetch_all(
        urls, 
        proxy="http://user:pass@proxy.example.com:8080",
        max_concurrent=10
    )

asyncio.run(main())

Важно: Не ставьте слишком высокую параллельность. 50-100 одновременных запросов через один прокси — уже много. Лучше 10-20 с несколькими прокси.

Диагностика: как найти причину

Прежде чем менять настройки, определите источник проблемы.

Шаг 1: Проверьте прокси напрямую

# Простой тест через curl с замером времени
curl -x http://user:pass@proxy:8080 \
     -w "Connect: %{time_connect}s\nTotal: %{time_total}s\n" \
     -o /dev/null -s \
     https://httpbin.org/get

Если time_connect больше 5 секунд — проблема с прокси или сетью до него.

Шаг 2: Сравните с прямым запросом

import requests
import time

def measure_request(url, proxies=None):
    start = time.time()
    try:
        r = requests.get(url, proxies=proxies, timeout=30)
        elapsed = time.time() - start
        return f"OK: {elapsed:.2f}s, status: {r.status_code}"
    except Exception as e:
        elapsed = time.time() - start
        return f"FAIL: {elapsed:.2f}s, error: {type(e).__name__}"

url = "https://target-site.com"
proxy = {"http": "http://proxy:8080", "https": "http://proxy:8080"}

print("Напрямую:", measure_request(url))
print("Через прокси:", measure_request(url, proxy))

Шаг 3: Проверьте разные типы прокси

Таймауты могут зависеть от типа прокси:

Тип прокси Типичная задержка Рекомендуемый таймаут
Datacenter 50-200 мс Connect: 5s, Read: 15s
Residential 200-800 мс Connect: 10s, Read: 30s
Mobile 300-1500 мс Connect: 15s, Read: 45s

Шаг 4: Логируйте детали

import logging
import requests
from requests.adapters import HTTPAdapter

# Включаем debug-логирование
logging.basicConfig(level=logging.DEBUG)
logging.getLogger("urllib3").setLevel(logging.DEBUG)

# Теперь увидите все этапы запроса:
# - Резолвинг DNS
# - Установка соединения
# - Отправка запроса
# - Получение ответа

Чек-лист решения timeout-ошибок

Краткий алгоритм действий при возникновении таймаутов:

  1. Определите тип таймаута — connect или read? Это разные проблемы.
  2. Проверьте прокси отдельно — работает ли он вообще? Какая задержка?
  3. Увеличьте таймауты — возможно, значения слишком агрессивные для вашего типа прокси.
  4. Добавьте retry с backoff — единичные таймауты нормальны, важна устойчивость.
  5. Настройте ротацию — автоматически переключайтесь на другой прокси при проблемах.
  6. Ограничьте параллельность — слишком много одновременных запросов перегружают прокси.
  7. Проверьте целевой сайт — возможно, он блокирует или throttle-ит ваши запросы.

Заключение

Timeout-ошибки через прокси — решаемая проблема. В большинстве случаев достаточно правильно настроить таймауты под тип прокси, добавить retry-логику и реализовать ротацию при сбоях. Для задач с высокими требованиями к стабильности используйте резидентные прокси с автоматической ротацией — подробнее на proxycove.com.