اسکریپت رصد قیمت شما تمام شب بیمشکل کار کرد. امروز صبح آدرس پروکسی پلن تازهتان را در آن گذاشتید و حالا ترمینال یک سطر طولانی چاپ میکند: HTTPSConnectionPool(host='example.com', port=443): Max retries exceeded with url. اسکریپت هیچ تنظیمی برای تلاش دوباره ندارد و میزبانی که در پیام آمده همان فروشگاهی است که از آن داده جمع میکنید، پس نگاهتان مستقیم به فروشگاه میرود. اما در انتهای همان سطر، داخل پرانتز، نوشته شده Tunnel connection failed: 403 Forbidden و این پاسخ را پروکسی داده است، نه فروشگاه.
در این راهنما پیام را جزءبهجزء باز میکنیم: چرا با اینکه هیچ تلاش دوبارهای تنظیم نکردهاید «max retries» میگوید، جدولی برای خواندن پیام که از رشتههای خطای تولیدشده به دست خودمان ساخته شده، گونههای مربوط به پروکسی، مهلتها (timeout) و همین خطا در pip. در پایان یک اسکریپت آزموده Python آمده که علت را نام میبرد.
«Max retries exceeded with url» یعنی چه؟
این پیام را urllib3 تولید میکند، کتابخانهای که اتصالها را برای Requests باز میکند. وقتی urllib3 از یک درخواست دست میکشد، MaxRetryError را پرتاب میکند. Requests آن را میگیرد و استثنای خودش را پرتاب میکند که بر اساس علت درونی انتخاب میشود: ConnectTimeout، ProxyError، SSLError، RetryError وقتی تلاشهای دوباره بر اساس کد وضعیت تمام شود، یا ConnectionError ساده برای بقیه موارد. این رشتهای از اجرای آزمایشی ماست که به چهار بخش تقسیم شده است:
requests.exceptions.ProxyError <- 1. the Requests exception
HTTPSConnectionPool(host='example.com', port=443): <- 2. the pool: target host and port
Max retries exceeded with url: / <- 3. the wrapper text and the path
(Caused by ProxyError('Unable to connect to proxy', <- 4. the real cause, outermost first
OSError('Tunnel connection failed: 403 Forbidden')))بیشتر اشتباهها در بخش 2 رخ میدهد. برای آدرس https://، سطر pool مقصد را نشان میدهد، حتی وقتی درخواست از پروکسی میگذرد؛ آدرس پروکسی فقط داخل بخش 4 میآید، آن هم اگر بیاید. برای آدرس ساده http:// که از پروکسی فرستاده میشود، وضع برعکس است: سطر pool پروکسی را نشان میدهد و بخش 3 آدرس کامل مقصد را در خود دارد، مانند HTTPConnectionPool(host='127.0.0.1', port=8083): Max retries exceeded with url: http://example.com/.
چرا وقتی هیچ تلاش دوبارهای تنظیم نکردهاید «max retries» میگوید؟
Requests بهطور پیشفرض اتصالهای ناموفق را دوباره تلاش نمیکند. در کد منبع requests/adapters.py مقدار DEFAULT_RETRIES = 0 است و adapter آن را به Retry(0, read=False) تبدیل میکند. مستندات urllib3 برای کلاس Retry میگوید خطاها درون MaxRetryError پیچیده میشوند، مگر آنکه تلاش دوباره با retries=False غیرفعال شده باشد. صفر تلاش دوباره غیرفعال به حساب نمیآید، پس همان یک تلاش ناموفق هم به شکل «Max retries exceeded» بیرون میآید.
بالا بردن تعداد تلاشهای دوباره علت را برطرف نمیکند. نام پروکسی غلطنوشتهشده، پورت نادرست یا تونل ردشده هر بار به همان شکل شکست میخورد. در آزمون ما، یک آدرس دسترسناپذیر با مهلت اتصال 2 ثانیه در حالت پیشفرض پس از 2 ثانیه شکست خورد و با دو تلاش دوباره و backoff کوتاه پس از 7 ثانیه. تلاش دوباره فقط در قطعیهای کوتاه شبکه کمک میکند.
پیام خطا را گامبهگام چگونه بخوانیم؟
پیام را از بیرون به درون بخوانید:
- استثنای Requests را پیدا کنید.
ProxyErrorیعنی شکست در مسیر رسیدن به پروکسی یا در خود پروکسی رخ داده است. - سطر pool را بخوانید. برای مقصدهای
https://، مقدارهایhostوportمربوط به مقصدند. پورت 443 از طریق پروکسی HTTP یعنی درخواست از یک تونل CONNECT میگذرد. - نخستین کلاس پس از
Caused byرا بخوانید. این تشخیص urllib3 است:NameResolutionError،NewConnectionError،ConnectTimeoutError،SSLErrorیاProxyError. - درونیترین متن را بخوانید.
Failed to resolve 'name'،[Errno 111] Connection refusedدر Linux،[WinError 10061]در Windows،CERTIFICATE_VERIFY_FAILEDیاTunnel connection failed: 403 Forbidden. مقدارhost=در این بخش ممکن است آدرس پروکسی باشد. - به مدت فراخوانی توجه کنید. شکستی که دقیقاً پس از مهلت اتصال شما (یا مضربی از آن) رخ دهد timeout است؛ شکست سریعتر یعنی اتصال رد شده یا تونل پذیرفته نشده است. در آزمونهای ما روی Windows، اتصال ردشده برای هر آدرس حدود دو ثانیه طول کشید و
localhostچهار ثانیه (نخست IPv6، سپس IPv4).
خطاهای HTTPSConnectionPool: بخش Caused by چه میگوید
همه رشتههای زیر را روی Python 3.13 با Requests 2.34.2 و urllib3 2.8.0 و از طریق یک پروکسی آزمایشی محلی تولید کردهایم. بخشهای طولانی با ... کوتاه شدهاند؛ متن خطای Windows به زبان سیستم هم بستگی دارد.
| Caused by (درونیترین بخش) | استثنای Requests | چه رخ داد | نخست بررسی کنید |
|---|---|---|---|
NameResolutionError(... Failed to resolve 'no-such-host.invalid' ...) | ConnectionError | نام مقصد به IP تبدیل نشد | املای URL، DNS، VPN |
NewConnectionError(... [Errno 111] or [WinError 10061] ...) | ConnectionError | هیچ برنامهای روی آن پورت گوش نمیدهد | روشن بودن سرویس، پورت درست |
ConnectTimeoutError(... 'Connection to 10.255.255.1 timed out. (connect timeout=2)') | ConnectTimeout | اتصال TCP در مهلت تعیینشده برقرار نشد | آدرس، فایروال، مقدار مهلت |
SSLError(SSLCertVerificationError(... CERTIFICATE_VERIFY_FAILED ...)) | SSLError | گواهی مورد اعتماد نیست | certifi، گواهی ریشه سازمان |
ProxyError('Unable to connect to proxy', NewConnectionError(...)) | ProxyError | پورت پروکسی اتصال را رد کرد | میزبان و پورت پروکسی، فایروال محلی |
ProxyError('Unable to connect to proxy', NameResolutionError(... 'pr.proxynet.invalid' ...)) | ProxyError | نام پروکسی به IP تبدیل نشد | غلط املایی در میزبان پروکسی |
ProxyError('Unable to connect to proxy', ConnectTimeoutError(...)) | ProxyError | پروکسی بهموقع پاسخ نداد | آدرس پروکسی، قواعد خروجی برای آن پورت |
ProxyError(..., OSError('Tunnel connection failed: 403 Forbidden')) | ProxyError | پروکسی تونل CONNECT را رد کرد | پورت مقصد، نوع و پورت پروکسی، قواعد مجاز |
ProxyError(..., OSError('Tunnel connection failed: 407 Proxy Authentication Required')) | ProxyError | پروکسی اطلاعات ورود معتبر میخواهد | احراز هویت پروکسی را ببینید |
ProxyError(..., OSError('Tunnel connection failed: 502 Bad Gateway')) | ProxyError | پروکسی به مقصد نرسید | میزبان و پورت مقصد |
ProxyError('... Your proxy appears to only use HTTP and not HTTPS ...', SSLError(... WRONG_VERSION_NUMBER ...)) | ProxyError | آدرس پروکسی با https:// شروع میشود | http:// بنویسید |
سه نکته بیرون از جدول میماند. پروکسیای که مهلتش تمام شود ProxyError پرتاب میکند، پس except ConnectTimeout هرگز آن را نمیبیند. تمام شدن مهلت خواندن پیچیده نمیشود: به شکل ReadTimeout با متن Read timed out. (read timeout=3) میرسد. و آدرس ساده http:// از طریق پروکسی با رمز نادرست هیچ استثنایی پرتاب نمیکند؛ پاسخی با کد وضعیت 407 میگیرید.
«ProxyError: Cannot connect to proxy» یعنی چه؟
Cannot connect to proxy. (urllib3 1.26) و Unable to connect to proxy (urllib3 2.x) یک خطا هستند. هر دو ممکن است روی یک رایانه دیده شوند: pip 25.x نسخه urllib3 1.26.20 را همراه خود دارد، در حالی که نصب تازه Requests نسخه urllib3 2.8.0 را میآورد. بهجای متن پیام، آرگومان دوم را بخوانید:
NewConnectionError: هیچ برنامهای روی آن پورت پروکسی گوش نمیدهد یا فایروال محلی جلوی آن را میگیرد. میزبان و پورت را دوباره از پنل کپی کنید.NameResolutionError: نام میزبان پروکسی غلط نوشته شده یا DNS شما نمیتواند آن را به IP تبدیل کند.ConnectionResetError(Errno 104در Linux وWinError 10054در Windows): پروکسی یا دستگاهی در مسیر اتصال را بست.OSError('Tunnel connection failed: ...'): پروکسی پاسخ داد اما تونل را باز نکرد؛ کد وضعیت همان پاسخ اوست.
برای نسخه مرورگری این خطا، یعنی «The proxy server is not responding»، نوشته خطای پروکسی چیست؟ را ببینید.
خطای Tunnel connection failed: 403 Forbidden: چرا پروکسی CONNECT را رد میکند؟
درخواست https:// از طریق پروکسی HTTP با CONNECT example.com:443 آغاز میشود. پروکسی یک اتصال TCP به مقصد باز میکند و بایتهای رمزگذاریشده را جابهجا میکند؛ خود صفحه را هرگز نمیبیند. بر اساس بخش 9.3.6 از RFC 9110، هر پاسخی جز 2xx یعنی تونل برقرار نشده است. ماژول http.client در Python این پاسخ را میخواند و Tunnel connection failed مینویسد.
403 تصمیم خود پروکسی است؛ مقصد هرگز درخواست شما را ندیده است. این RFC میگوید پروکسیها باید CONNECT را به پورتهای شناختهشده یا فهرستی از مقصدهای امن محدود کنند، چون تونلی به پورت 25 میتواند هرزنامه را جابهجا کند. علتهای رایج:
- پورت مقصد مجاز نیست. ممکن است
https://example.com:8443/شکست بخورد در حالی کهhttps://example.com/کار میکند. - فهرست مجاز. پروکسیهای سازمانی و برخی پلتفرمهای میزبانی فقط به دامنههای تأییدشده اجازه میدهند. از تیم IT بپرسید یا مستندات پلتفرم را بخوانید؛ سیاست را دور نزنید.
- نوع یا پورت نادرست پروکسی، برای نمونه پورتی که برای پروتکل دیگری در نظر گرفته شده است.
- قواعد ارائهدهنده. ارائهدهنده میتواند تونل به برخی مقصدها را رد کند؛ مستندات آن میگوید از کدام کدهای وضعیت استفاده میکند.
403 از سوی سایت مقصد به شکل پاسخی عادی همراه یک صفحه میرسد؛ این حالت را در کدهای وضعیت HTTP در وب اسکرپینگ توضیح دادهایم. 407 در تونل یعنی پروکسی اطلاعات ورود میخواهد.
آدرس پروکسی باید با http:// شروع شود یا https://؟
در دیکشنری proxies، کلید طرح (scheme) آدرس مقصد است و مقدار، راه رسیدن به پروکسی. نمونه خود مستندات Requests کلید 'https' را به 'http://10.10.1.10:1080' نگاشت میکند. بیشتر پروکسیها HTTP ساده صحبت میکنند و برای سایتهای رمزگذاریشده تونل CONNECT باز میکنند (صفحه پروکسی HTTPS ما را ببینید)، پس هر دو کلید مقدار http:// میگیرند:
proxies = {
"http": "http://user:pass@pr.proxynet.io:8000",
"https": "http://user:pass@pr.proxynet.io:8000", # http:// here too
}اگر مقدار با https:// شروع شود، urllib3 2.x با خود پروکسی اتصال TLS باز میکند، پروکسی HTTP ساده با بایتهای غیر TLS پاسخ میدهد و خطای WRONG_VERSION_NUMBER را همراه راهنمایی Your proxy appears to only use HTTP and not HTTPS میگیرید. صفحه urllib3 درباره این خطا همین راهحل را میدهد، برای متغیر نادرست HTTPS_PROXY هم.
دو نکته مرتبط:
- SOCKS. بسته
requests[socks]را نصب کنید. باsocks5://رایانه خود شما نام را به IP تبدیل میکند، پس در آزمون ما نام نادرست پیش از تماس با پروکسی شکست خورد؛ باsocks5h://این کار را پروکسی انجام میدهد (تفاوت پروکسی SOCKS و HTTP، پروکسی SOCKS5). - متغیرهای محیطی. صفحه کاربرد پیشرفته Requests هشدار میدهد که پروکسیهای تعریفشده در متغیرهای محیطی میتوانند
session.proxiesرا بازنویسی کنند و توصیه میکندproxies=را در هر درخواست بدهید. این متغیرها را در استفاده از پروکسی در wget توضیح دادهایم.
بدون مهلت چه اتفاقی میافتد؟ ConnectTimeout در برابر ReadTimeout
Requests مهلت پیشفرض ندارد: بدون timeout= ممکن است یک فراخوانی چند دقیقه معلق بماند و هیچ خطایی برای خواندن به شما ندهد. یک تاپل مانند timeout=(3.05, 20) بدهید: نخست مهلت اتصال، سپس مهلت خواندن، بر حسب ثانیه. مستندات پیشنهاد میکند مهلت اتصال کمی بیشتر از مضربی از 3 باشد، یعنی پنجره پیشفرض ارسال دوباره بستهها در TCP.
این دو مهلت به دو شکل متفاوت شکست میخورند:
- ConnectTimeout: اتصال TCP بهموقع برقرار نشد. این خطا درون «Max retries exceeded» پیچیده میرسد و مرجع API آن را برای تلاش دوباره امن میداند.
- ReadTimeout: اتصال برقرار شد اما در مهلت خواندن دادهای نرسید. این خطا پیچیده نمیشود و ممکن است سرور درخواست را پیشتر پردازش کرده باشد، پس تلاش دوباره برای یک POST ممکن است کار را دو بار انجام دهد.
مهلت اتصال برای هر آدرس IP جداگانه اعمال میشود، پس نامی که آدرس IPv4 و IPv6 دارد میتواند زمان انتظار را دو برابر کند. مهلت خواندن فاصله میان دو بایت پیاپی است، نه سقفی برای کل دانلود. مقادیر پیشفرض کتابخانههای دیگر در مقایسه HTTPX، Requests و AIOHTTP آمده است.
همین خطا در pip: «Retrying ... after connection broken by»
pip نسخههای خودش از Requests و urllib3 را همراه دارد، پس pip install به همان دلایل شکست میخورد. مستندات pip مقادیر پیشفرض را چنین آورده است: --retries 5 و --timeout 15 ثانیه. ما pip 26.2.1 را با --retries 2 از طریق پروکسی محلیای اجرا کردیم که هر CONNECT را با 403 رد میکند:
WARNING: Retrying (Retry(total=1, connect=None, read=None, redirect=None, status=None)) after connection broken by 'OSError('Tunnel connection failed: 403 Forbidden')': /simple/six/
WARNING: Retrying (Retry(total=0, connect=None, read=None, redirect=None, status=None)) after connection broken by 'OSError('Tunnel connection failed: 403 Forbidden')': /simple/six/
ERROR: Could not find a version that satisfies the requirement six (from versions: none)
ERROR: No matching distribution found for sixبسته وجود دارد؛ pip هرگز به ایندکس نرسید. علت در سطرهای WARNING است. pip 25.2 همین هشدار را با عبارتبندی urllib3 1.26 یعنی ProxyError('Cannot connect to proxy.', ...) چاپ میکرد.
برای CERTIFICATE_VERIFY_FAILED پشت پروکسی سازمانی که TLS را بازرسی میکند، گواهی ریشه سازمان را با --cert بدهید؛ --trusted-host تأیید گواهی را خاموش میکند و آخرین راه است. تنظیم پروکسی برای pip را در تنظیمات پروکسی در Linux توضیح دادهایم.
اسکریپت Python که علت را نام میبرد
این اسکریپت یک استثنا را به یک سطر تبدیل میکند: چه چیزی شکست خورد و چه چیزی را باید بررسی کرد. این اسکریپت:
- فقط اتصالهای ناموفق را، آن هم دو بار، دوباره تلاش میکند؛
- مقدار
read=Falseرا نگه میدارد: در آزمون ماread=0یکReadTimeoutرا بهConnectionErrorپیچیده تبدیل کرد؛ - مقدار
other=0را تنظیم میکند: بدون آن، تونلی که با403یا407رد شده بود سه بار تلاش شد؛ - در هر درخواست
proxies=وtimeout=(3.05, 20)را میدهد؛ - کلاسهای
ProxyError،SSLErrorوConnectTimeoutرا پیش ازConnectionErrorکه کلاس والد آنهاست میگیرد.
تلاش دوباره بر اساس کد وضعیت لایه جداگانهای است (کدهای وضعیت HTTP در وب اسکرپینگ) و چرخش پروکسی هم همینطور (چرخاندن پروکسی در Python).
"""Find the real cause behind "Max retries exceeded with url" and say what to check."""
import re
import ssl
import time
import requests
from requests.adapters import HTTPAdapter
from urllib3.exceptions import (
ConnectTimeoutError,
NameResolutionError,
NewConnectionError,
ProxyError as Urllib3ProxyError,
)
from urllib3.util import Retry
PROXY = "http://user:pass@pr.proxynet.io:8000" # http:// even for https:// targets
TIMEOUT = (3.05, 20) # seconds: (connect, read)
def make_session():
"""A session that retries failed connections twice and nothing else."""
retry = Retry(
total=2,
connect=2, # DNS failures, refused and timed-out connections
read=False, # keep ReadTimeout a ReadTimeout: the server may have the request
other=0, # a proxy that refused the tunnel will refuse it again
status=0, # retries by status code belong to another layer
backoff_factor=0.5,
)
adapter = HTTPAdapter(max_retries=retry)
session = requests.Session()
session.mount("http://", adapter)
session.mount("https://", adapter)
return session
def causes(exc):
"""The exception and every error wrapped inside it, outermost first."""
chain = []
while exc is not None and all(exc is not seen for seen in chain):
chain.append(exc)
inner = None
for candidate in (getattr(exc, "reason", None), getattr(exc, "original_error", None),
exc.__cause__, exc.__context__, *exc.args[:2]):
if isinstance(candidate, BaseException):
inner = candidate
break
exc = inner
return chain
def explain(exc):
"""One line: what failed, on which side, and what to check first."""
chain = causes(exc)
text = " | ".join(str(e) for e in chain)
side = "proxy" if any(isinstance(e, Urllib3ProxyError) for e in chain) else "target"
tunnel = re.search(r"Tunnel connection failed: (\d{3})", text)
if tunnel:
code = tunnel.group(1)
if code == "407":
return "proxy asked for credentials (407): check user:pass or the IP whitelist"
if code == "403":
return "proxy refused the CONNECT tunnel (403): check the target port and the proxy's allow rules"
return f"proxy was reached but could not reach the target ({code}): check the target host and port"
if "appears to only use HTTP" in text:
return "proxy URL starts with https:// but the proxy speaks plain HTTP: write http://"
if any(isinstance(e, NameResolutionError) for e in chain):
host = re.search(r"Failed to resolve '([^']+)'", text)
return f"{side} name {host.group(1) if host else ''} does not resolve: check spelling, DNS and VPN"
if any(isinstance(e, ssl.SSLCertVerificationError) for e in chain):
return "certificate not trusted: update certifi or set REQUESTS_CA_BUNDLE to your company root CA"
if isinstance(exc, requests.exceptions.ReadTimeout):
return "connected, but no answer within the read timeout: the server may have the request"
if any(isinstance(e, NewConnectionError) for e in chain):
return f"{side} refused the connection: wrong port, service down, or a firewall rejects it"
if any(isinstance(e, ConnectTimeoutError) for e in chain):
return f"no TCP connection to the {side} within the connect timeout: check address, port, firewall"
return f"unrecognised, read the innermost error: {chain[-1]!r}"
def fetch(session, url, proxy=PROXY):
"""GET one URL and print the status, or the diagnosis if the request failed."""
proxies = {"http": proxy, "https": proxy} if proxy else None # per request: env vars cannot override it
start = time.monotonic()
try:
resp = session.get(url, proxies=proxies, timeout=TIMEOUT)
except requests.exceptions.ProxyError as exc: # before ConnectionError: it is a subclass
kind, error = "ProxyError", exc
except requests.exceptions.SSLError as exc: # also a ConnectionError
kind, error = "SSLError", exc
except requests.exceptions.ConnectTimeout as exc: # a ConnectionError and a Timeout at once
kind, error = "ConnectTimeout", exc
except requests.exceptions.ReadTimeout as exc: # never wrapped in "Max retries exceeded"
kind, error = "ReadTimeout", exc
except requests.exceptions.ConnectionError as exc:
kind, error = "ConnectionError", exc
else:
print(f"{'OK':<16}{time.monotonic() - start:6.2f}s {url} HTTP {resp.status_code}")
return resp
print(f"{kind:<16}{time.monotonic() - start:6.2f}s {url}\n{'':<24}{explain(error)}")
return None
if __name__ == "__main__":
session = make_session()
for url in ["https://httpbin.org/ip", "https://example.com/"]:
fetch(session, url)در urllib3 2.x، NameResolutionError زیرکلاس NewConnectionError است و آن هم زیرکلاس ConnectTimeoutError، پس explain() نخست خاصترین کلاس را آزمایش میکند. اسکریپت فقط به pip install requests نیاز دارد.
خروجی چه شکلی دارد
تابع fetch() را روی Windows 11 برای هر حالت یک بار اجرا کردیم: یک پروکسی آزمایشی محلی (رمز درست و نادرست، پورت بسته، نام غلطنوشتهشده، طرح https://)، پروکسی دومی که هر CONNECT را رد میکند، و میزبانهای واقعی برای حالتهای گواهی و مهلت. مهلت خواندن 5 ثانیه بود:
OK 0.77s https://httpbin.org/ip HTTP 200
ProxyError 0.02s https://httpbin.org/ip
proxy asked for credentials (407): check user:pass or the IP whitelist
ProxyError 0.00s https://example.com/
proxy refused the CONNECT tunnel (403): check the target port and the proxy's allow rules
ProxyError 0.02s https://no-such-host.invalid/
proxy was reached but could not reach the target (502): check the target host and port
ProxyError 7.10s https://httpbin.org/ip
proxy refused the connection: wrong port, service down, or a firewall rejects it
ProxyError 1.02s https://httpbin.org/ip
proxy name pr.proxynet.invalid does not resolve: check spelling, DNS and VPN
ProxyError 0.21s https://httpbin.org/ip
proxy URL starts with https:// but the proxy speaks plain HTTP: write http://
SSLError 0.63s https://self-signed.badssl.com/
certificate not trusted: update certifi or set REQUESTS_CA_BUNDLE to your company root CA
ConnectTimeout 10.16s http://10.255.255.1/
no TCP connection to the target within the connect timeout: check address, port, firewall
ReadTimeout 5.02s http://127.0.0.1:8082/
connected, but no answer within the read timeout: the server may have the request
ConnectionError 13.12s http://localhost:8083/
target refused the connection: wrong port, service down, or a firewall rejects itتونلهای ردشده در چند میلیثانیه شکست خوردند، چون other=0 جلوی تکرارشان را گرفت. پورت بسته پروکسی سه تلاش حدوداً دوثانیهای بهاضافه یک ثانیه backoff طول کشید، مهلت اتصال سه بار 3.05 ثانیه بهاضافه backoff و localhost 13 ثانیه، چون هر تلاش نخست IPv6 و سپس IPv4 را امتحان کرد. 502 از پروکسی آزمایشی ما آمد که نمیتوانست نام مقصد را به IP تبدیل کند.
کجا با این خطا روبهرو میشوید
- اسکرپینگ از طریق پروکسی: اشتباههای پروکسی پیش از هر کد وضعیتی همینجا ظاهر میشوند (استخراج داده).
- بیلدهای CI و Docker پشت پروکسی سازمانی: بیلدها اغلب تنظیمات پروکسی میزبان را دریافت نمیکنند و
localhostدر آنها خود کانتینر است (تنظیمات پروکسی در Linux). - یکپارچهسازیهای API با IP خروجی ثابت: رد شدن تونل شبیه از کار افتادن API به نظر میرسد (IP ثابت برای API).
- آزمودن پروکسی تازه: پیش از اجرای کل کار، یک درخواست با مهلت بفرستید (چگونه پروکسی را آزمایش کنیم).
- خودکارسازی مرورگر: همین شکست تونل به شکل
ERR_TUNNEL_CONNECTION_FAILEDظاهر میشود (Playwright با پروکسی). - شبکههای سازمانی: فایروال و پروکسی هر دو اتصالها را پالایش میکنند (پروکسی و فایروال).
اشتباهات رایج
- بالا بردن تعداد تلاشهای دوباره. علت دائمی دائمی میماند؛ فقط بیشتر منتظر میمانید.
- مقصر دانستن مقصد چون نامش در سطر pool آمده است. در آدرسهای
https://پروکسی فقط داخل پرانتز دیده میشود. - فراموش کردن یک متغیر قدیمی
HTTPS_PROXY. این متغیر میتواند بیصدا جایsession.proxiesرا بگیرد. - باقی گذاشتن
verify=Falseدر محیط عملیاتی. مستندات Requests هشدار میدهد که این کار شما را در معرض حمله مرد میانی قرار میدهد. certifi را بهروز کنید یاREQUESTS_CA_BUNDLEرا تنظیم کنید؛ برای پروکسی محلی اشکالزدایی، پروکسی MITM را ببینید. - گرفتن
ConnectionErrorدر ابتدا. این کلاسProxyError،SSLErrorوConnectTimeoutرا هم میبلعد. - یکی گرفتن
403و407در تونل. یکی قاعده است و دیگری اطلاعات ورود. - خواندن سطر آخر pip. علت در سطرهای
WARNINGبالای آن است.
راهنمای انتخاب
| آنچه میبینید | چه باید کرد |
|---|---|
NameResolutionError در Caused by | نامی را که به IP تبدیل نشد اصلاح کنید (میزبان پروکسی یا URL)؛ تلاش دوباره لازم نیست |
ProxyError همراه NewConnectionError | میزبان و پورت پروکسی را دوباره کپی کنید؛ فایروال محلی را بررسی کنید |
Tunnel connection failed: 403 | پورت مقصد، نوع و پورت پروکسی و قواعد مجاز را بررسی کنید |
Tunnel connection failed: 407 | اطلاعات ورود یا لیست سفید IP را بررسی کنید |
SSLError: CERTIFICATE_VERIFY_FAILED | certifi را بهروز کنید یا REQUESTS_CA_BUNDLE را تنظیم کنید (در pip: --cert) |
| فراخوانیها گاهی معلق میمانند یا به مهلت میرسند | timeout=(3.05, 20) بهاضافه یک Retry فقط برای اتصال |
| pip میگوید «No matching distribution found» | سطر WARNING: Retrying را بخوانید |
پرسشهای متداول
آیا بالا بردن تعداد تلاشهای دوباره خطای «Max retries exceeded» را برطرف میکند؟
فقط وقتی شبکه برای لحظهای قطع شده باشد. برای نامی که به IP تبدیل نمیشود، پورت بسته یا تونل ردشده، هر تلاش دوباره به همان شکل شکست میخورد. نخست بخش Caused by را بخوانید.
مهلت پیشفرض در Python Requests چقدر است؟
مهلت پیشفرضی وجود ندارد: بدون timeout= یک درخواست ممکن است بیپایان منتظر بماند. به هر فراخوانی یک تاپل (connect, read) بدهید.
مهلت Requests بر حسب ثانیه است یا میلیثانیه؟
ثانیه. عدد اعشاری مانند 3.05 هم کار میکند؛ یک عدد تنها هر دو مرحله را تعیین میکند و تاپلی مانند (3.05, 20) آنها را جداگانه تعیین میکند.
آیا میتوانم برای CERTIFICATE_VERIFY_FAILED از verify=False استفاده کنم؟
فقط برای یک آزمون سریع محلی، چون این کار به هر کسی در مسیر اجازه میدهد ترافیک را بخواند یا تغییر دهد. راهحل ماندگار certifi بهروز است یا، پشت پروکسی سازمانی که TLS را بازرسی میکند، قرار دادن گواهی ریشه آن در REQUESTS_CA_BUNDLE.
چرا pip با اینکه بسته وجود دارد میگوید «No matching distribution found»؟
pip نتوانست به ایندکس بستهها برسد، پس هیچ نسخهای پیدا نکرد. سطرهای WARNING: Retrying ... after connection broken by در بالای آن علت واقعی را نام میبرند، مانند Tunnel connection failed: 403 Forbidden.
چرا هنگام فراخوانی localhost:8000 این خطا را میگیرم؟
هیچ برنامهای روی آن پورت گوش نمیدهد: سرور خاموش است، از پورت دیگری استفاده میکند یا کد شما در Docker اجرا میشود که در آن localhost خود کانتینر است. درونیترین خطا [Errno 111] یا [WinError 10061] است. یافتن برنامهای که یک پورت را در اختیار دارد را در پورت 8080 چیست؟ توضیح دادهایم.
خلاصه
«Max retries exceeded with url» یک پوشش است: علت پس از Caused by میآید و چون Requests بهطور پیشفرض تلاش دوباره نمیکند، پس از یک تلاش هم ظاهر میشود. در خطاهای پروکسی، شکست در مسیر رسیدن به پروکسی را از تونلی که پروکسی رد کرده جدا کنید و در آدرس پروکسی http:// را نگه دارید. به هر درخواست یک تاپل مهلت بدهید و فقط شکستهای اتصال را دوباره تلاش کنید. پیکربندی تازه را نخست با یک درخواست بیازمایید (چگونه پروکسی را آزمایش کنیم) و گزینهها را در صفحه خدمات پروکسی ما مقایسه کنید.




