# Naprawa ProxyError, SSLError i ConnectTimeout w Requests

requests.exceptions.ProxyError to objaw, a nie przyczyna. Oto zdekodowana hierarchia wyjątków, każdy traceback dopasowany do rzeczywistej naprawy oraz funkcja sprawdzania kondycji, która klasyfikuje awarie i rotuje wokół martwych wyjść.

`requests.exceptions.ProxyError` to jedna z najmniej pomocnych wiadomości o błędach w Pythonie: występuje w przypadku martwego proxy, błędnych danych uwierzytelniających, złego schematu, przeciążonego wyjścia i blokady zapory, wszystkie z niemal identycznymi tracebackami. Sztuczka, aby szybko to naprawić, polega na zrozumieniu, że ProxyError nie jest przyczyną pierwotną — to kategoria. W źródle Requests, `ProxyError`, `SSLError` i `ConnectTimeout` są podklasami `ConnectionError`, a każdy z nich występuje na określonym etapie cyklu życia żądania. Przeczytaj etap, a poznasz przyczynę. Ten przewodnik dekoduje hierarchię wyjątków, dopasowuje każdy typowy traceback do rzeczywistej naprawy i dostarcza funkcję sprawdzania kondycji, która automatycznie klasyfikuje awarie i rotuje wokół umierających wyjść.

## Hierarchia wyjątków w requests

Każdy błąd związany z proxy w Requests pochodzi od `RequestException`. Przydatną gałęzią do debugowania jest `ConnectionError`, ponieważ trzy wyjątki, które faktycznie napotykasz, znajdują się pod nią:

- **ProxyError** — występuje, gdy połączenie z samym proxy się nie powiodło: nieosiągalny host, zły port, odmowa połączenia lub odrzucony `407`. Wiadomość często zagnieżdża `Cannot connect to proxy` wewnątrz wrappera `HTTPSConnectionPool(...)`.
- **SSLError** — proxy połączyło się, ale handshake TLS z celem się nie powiódł. W konfiguracjach proxy zwykle przyczyną jest `https://` zapisane wewnątrz klucza `https` zamiast `http://`.
- **ConnectTimeout** — proxy nie odpowiedziało w oknie połączenia. Jest podklasą zarówno `ConnectionError`, jak i `Timeout`, i jest wyraźnie dokumentowane jako bezpieczne do ponowienia.
- **ReadTimeout** — proxy połączyło się i przekazało żądanie, ale cel był zbyt wolny, aby odpowiedzieć. To problem z celem lub jakością wyjścia, a nie błąd konfiguracji.
- **MissingSchema / InvalidProxyURL** — występuje przed jakimkolwiek wywołaniem sieciowym, gdy URL proxy jest nieprawidłowy. To czyste literówki.

Ponieważ pierwsze trzy dzielą rodzica, jedno `except requests.exceptions.ConnectionError` łapie je wszystkie dla logiki ponawiania — podczas gdy łapanie podklas indywidualnie pozwala na logowanie *dlaczego* każdy z nich zawiódł. Czysta konfiguracja, która unika większości z nich, jest omówiona w naszym [przewodniku po proxy w Python Requests](https://quantumproxies.io/blog/python-requests-proxy-guide); ten post dotyczy tego, co zrobić, gdy traceback jest już na ekranie.

Jedna nawyk oszczędza więcej czasu niż jakakolwiek pojedyncza naprawa: czytaj traceback od dołu do góry. Requests opakowuje podstawową awarię urllib3, więc górne ramki opisują *gdzie* dokonano wywołania, a dolne ramki opisują *co* poszło nie tak. Linia, której szukasz, to najgłębsza klauzula `Caused by` — nazywa ona konkretną awarię (odmowa połączenia, niezgodność certyfikatu, błąd parsowania portu), którą zewnętrzny `ProxyError` lub `ConnectionError` jedynie ponownie podnosi. Gdy już potrafisz przeczytać tę linię, reszta tego przewodnika to tabela wyszukiwania.

## Klasyczny ValueError przed ProxyError

Najczęściej wyszukiwany traceback proxy to nawet nie ProxyError — to `ValueError: invalid literal for int() with base 10`, rzucany głęboko wewnątrz urllib3. Dzieje się tak, gdy osadzasz dane uwierzytelniające w wartości proxy bez schematu, więc parser odczytuje tekst po dwukropku jako numer portu:

```python
# Broken — no scheme, so 'pass@host' is parsed as host:port
proxies = {"https": "user:pass@45.11.22.33:8000"}
# -> ValueError: invalid literal for int() with base 10: 'pass@45.11.22.33'

# Fixed — scheme in front, password URL-encoded if it has @ : or /
from urllib.parse import quote
pw = quote("p@ss:word", safe="")
proxies = {
    "http":  f"http://user:{pw}@residentialpremium.quantumproxies.io:8080",
    "https": f"http://user:{pw}@residentialpremium.quantumproxies.io:8080",
}
```

![Cykl życia żądania w czterech etapach, każdy z wyjątkiem, który zgłasza: budowanie URL proxy zgłasza MissingSchema, łączenie z proxy zgłasza ProxyError lub ConnectTimeout, handshake TLS zgłasza SSLError, a oczekiwanie na cel zgłasza ReadTimeout; karta mówi, które naprawić, a które ponowić.](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335156/blog/generated/qp-b9-018-diag1.png)
*Nazwa etapu cyklu życia wskazuje przyczynę: literówka w czasie budowy to MissingSchema, nieudane połączenie proxy to ProxyError, awaria TLS to SSLError, a wolne wyjście to ReadTimeout.*

## Sprawdzanie kondycji proxy, które klasyfikuje awarie

Zamiast zgadywać, złap każdy typ wyjątku i zamień go na werdykt w prostym języku. Ta funkcja zwraca adres IP wyjścia w przypadku sukcesu i oznaczoną przyczynę w przypadku niepowodzenia — umieść ją przed każdym skrobaniem, aby potwierdzić, że proxy jest aktywne, zanim zużyjesz na nim żądania. Działa z dowolną uwierzytelnioną bramą, w tym [proxy rezydencjalnymi](https://quantumproxies.io/residential-proxies):

```python
import requests

def check_proxy(proxies, url="https://httpbin.org/ip", timeout=(5, 20)):
    try:
        r = requests.get(url, proxies=proxies, timeout=timeout)
        r.raise_for_status()
        return True, r.json().get("origin")
    except requests.exceptions.ProxyError as e:
        return False, f"proxy unreachable or auth rejected: {e}"
    except requests.exceptions.SSLError as e:
        return False, f"TLS failed (https:// in the https key?): {e}"
    except requests.exceptions.ConnectTimeout:
        return False, "proxy did not answer within the connect window"
    except requests.exceptions.ReadTimeout:
        return False, "target too slow after connect (exit quality)"
    except requests.exceptions.RequestException as e:
        return False, f"other request error: {e}"

ok, detail = check_proxy(proxies)
print("OK" if ok else "FAIL", detail)
```

## Gdy curl działa, ale Python wyrzuca ProxyError

Jeśli te same dane uwierzytelniające działają w curl, ale wywołują ProxyError w Pythonie, zmienna środowiskowa prawie zawsze nadpisuje twój słownik. Requests odczytuje `HTTP_PROXY`, `HTTPS_PROXY` i `NO_PROXY` z powłoki, a nieaktualna wartość korporacyjna cicho przekierowuje każde wywołanie. Wydrukuj `session.proxies`, aby zobaczyć, co jest naprawdę używane, a następnie całkowicie wyłącz wyszukiwanie środowiska:

```python
import requests

session = requests.Session()
session.trust_env = False          # ignore HTTP_PROXY / HTTPS_PROXY from the shell
session.proxies = {
    "http":  "http://USER:PASS@residentialpremium.quantumproxies.io:8080",
    "https": "http://USER:PASS@residentialpremium.quantumproxies.io:8080",
}
print(session.get("https://httpbin.org/ip", timeout=(5, 20)).json())
```

`407`, który przetrwa poprawne dane uwierzytelniające, wskazuje na plan białej listy IP wywoływany z niezarejestrowanego adresu — pełna lista przyczyn znajduje się w naszym [przewodniku 407 Proxy Authentication Required](https://quantumproxies.io/blog/fix-407-proxy-authentication-required).

## Przerywany ProxyError: rotuj, nie restartuj

Frustrujący przypadek to kod, który działa przez dwadzieścia minut, potem wyrzuca ProxyError, a następnie znowu działa. To nie jest błąd w twoim skrypcie — to pojedynczy adres IP wyjścia umiera lub jest ograniczany w trakcie działania. Naprawą jest ponowienie z rotacją: opakuj wywołanie, złap `ConnectionError`, i pozwól, aby rotująca brama dostarczyła ci nowy adres IP przy następnym podejściu. Poprzez [rotujące proxy](https://quantumproxies.io/rotating-proxies) każde ponowienie przechodzi przez inne wyjście, więc jeden martwy adres nigdy nie może spowodować niepowodzenia żądania dwa razy:

```python
import requests

def get_with_rotation(url, proxies, attempts=4):
    last = None
    for _ in range(attempts):
        try:
            r = requests.get(url, proxies=proxies, timeout=(5, 20))
            if r.status_code not in (429, 500, 502, 503, 504):
                return r
            last = r.status_code
        except requests.exceptions.ConnectionError as e:  # Proxy/SSL/ConnectTimeout
            last = e
    raise RuntimeError(f"failed after {attempts} attempts: {last}")
```

Jeśli ProxyError utrzymuje się na wielu nowych adresach IP, problem przeniósł się z twojej puli na cel: jesteś blokowany, a nie rozłączany. To inna walka — zobacz [listę kontrolną antybanową](https://quantumproxies.io/blog/avoid-ip-ban-web-scraping) dla tempa, nagłówków i higieny sesji.

Jest jeszcze jedno rozróżnienie warte zapamiętania, ponieważ zmienia sposób, w jaki reagujesz. `ProxyError` lub `ConnectTimeout` oznacza, że żądanie nigdy nie zostało zakończone, więc ponowienie jest bezpieczne nawet dla POST — nic się nie wydarzyło po drugiej stronie. `ReadTimeout`, przeciwnie, oznacza, że cel otrzymał twoje żądanie i po prostu zbyt długo odpowiadał; ponowienie nie-idempotentnego zapisu może spowodować podwójne przesłanie. Kiedy budujesz pętlę ponawiania, traktuj awarie na etapie połączenia jako swobodnie ponawialne, a awarie na etapie odczytu jako ponawialne tylko dla GET i HEAD. Ta jedna zasada zapobiega subtelnemu błędowi, w którym niestabilne proxy zamienia jedno zamówienie na trzy.

![Słownik proxies z danymi uwierzytelniającymi, ale bez schematu, zgłasza ValueError: invalid literal for int() with base 10, a poprawiona wersja dodaje http:// i hasło zakodowane w URL; karta dopasowuje typowe komunikaty błędów do ich jednolinijkowych poprawek, od brakującego schematu po rotację wyjścia przy sporadycznych awariach.](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335157/blog/generated/qp-b9-018-diag2.png)
*Przeczytaj wiadomość, zastosuj naprawę: zły host, brakujący schemat, zły klucz TLS, niezakodowane hasło lub umierające wyjście, każdy z nich mapuje się na jeden krok naprawczy.*

## Często zadawane pytania

### Co powoduje requests.exceptions.ProxyError: nie można połączyć się z proxy?

Host proxy lub port jest błędny, proxy jest wyłączone lub zapora blokuje połączenie, zanim jakiekolwiek żądanie opuści. Zweryfikuj punkt końcowy za pomocą `curl -x` używając tych samych danych uwierzytelniających; jeśli curl również zawiedzie, proxy jest nieosiągalne, a jeśli curl się powiedzie, winowajcą jest zmienna środowiskowa lub nieprawidłowy słownik w twoim Pythonie.

### Dlaczego ProxyError jest opakowany w HTTPSConnectionPool?

To opakowanie po prostu nazywa pulę połączeń, której urllib3 użyło do dotarcia do celu — to szum wokół rzeczywistej wiadomości zagnieżdżonej wewnątrz. Przeczytaj najgłębszą klauzulę `Caused by`: `Cannot connect to proxy` oznacza awarię połączenia, podczas gdy wiadomość `407` lub SSL wewnątrz tej samej puli wskazuje na problem z uwierzytelnieniem lub TLS/schematem.

### Jak zatrzymać requests przed odczytywaniem proxy ze środowiska?

Ustaw `session.trust_env = False` na swojej Sesji, lub przekaż `trust_env=False` równoważnie, aby Requests ignorowało `HTTP_PROXY` i `HTTPS_PROXY`. To jest naprawa, gdy proxy działa w jednej powłoce, ale wyrzuca ProxyError w innej, lub gdy zmienna korporacyjna przejmuje skrobak, którego nie skonfigurowałeś do używania proxy.

### Czy ConnectTimeout jest bezpieczny do ponowienia?

Tak — dokumentacja Requests oznacza `ConnectTimeout` jako bezpieczny do ponowienia, ponieważ żądanie nigdy nie dotarło do serwera, więc nie mogło wystąpić żadne działanie uboczne. Ponów je, najlepiej przez rotującą bramę, aby następna próba użyła innego, szybszego wyjścia. `ReadTimeout` jest bardziej ryzykowny do ponowienia w ciemno na nie-idempotentnych metodach jak POST.

Gdy przestaniesz traktować ProxyError jako pojedynczą usterkę i zaczniesz czytać go według etapu cyklu życia, naprawy stają się mechaniczne: literówki w schemacie pojawiają się przed siecią, awarie połączeń nazywają martwe proxy, błędy SSL nazywają zły klucz, a przerywane awarie chcą rotacji, a nie restartu. Czyste wyjścia eliminują większość z nich całkowicie.

[Skrob na rezydencjalnych IP, które pozostają aktywne](https://quantumproxies.io/residential-proxies)
