如果您在 Python 中通过代理解析网站或自动化请求,您迟早会遇到三种经典错误:
ProxyError、
SSLError: WRONG_VERSION_NUMBER 和 HTTP
407 Proxy Authentication Required。在本文中,我们将分析
每个错误的原因,并展示有效的代码来修复它们。
requests 如何与代理协作:基本语法
requests 库通过字典
proxies 接受代理,其中键是请求的协议
(http 和 https),
值是带有可选授权的代理服务器 URL。
import requests
proxies = {
"http": "http://user:password@proxy_host:port",
"https": "http://user:password@proxy_host:port",
}
response = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=10)
print(response.json())
请注意:即使是 HTTPS 请求,代理的协议通常也指定为
http://,因为大多数代理服务器通过 HTTP 协议接受
CONNECT 隧道,而不是将其封装在 SSL 中。如果指定
https:// 作为不支持此功能的代理,您将
遇到 SSLError,其分析将在下文中进行。
如果您使用 SOCKS5 代理,则需要额外的包:
pip install requests[socks]
# 代理的语法也会有所变化:
proxies = {
"http": "socks5://user:password@proxy_host:port",
"https": "socks5://user:password@proxy_host:port",
}
ProxyError:原因和解决方案
requests.exceptions.ProxyError 是低级网络异常的封装,
当尝试与代理服务器建立连接时会发生。最常见的情况有四种。
1. 代理不可用或地址/端口不正确
requests.exceptions.ProxyError: HTTPSConnectionPool(host='example.com', port=443):
Max retries exceeded with url: / (Caused by ProxyError('Cannot connect to proxy.',
ConnectionResetError(104, 'Connection reset by peer')))
请检查主机和端口是否输入正确,并确保代理服务器确实在运行。最快的诊断方法是
尝试通过 curl 连接:
curl -x http://user:password@proxy_host:port https://httpbin.org/ip -v
2. 代理阻止了您的请求(IP 或 User-Agent 封禁)
数据中心代理常常因其 IP 范围被反欺诈系统识别而被目标网站封禁。 如果在访问特定网站(例如 Wildberries、Ozon 或社交媒体)时错误稳定出现,而不是在所有网站上同时出现, 那么问题不在于代理本身,而在于其声誉。在这种情况下,切换到 住宅代理 会有所帮助——它们使用 真实用户的 IP,较少受到反欺诈系统的封锁。
3. 超过了并发连接限制
如果您运行多线程解析并通过同一个代理打开数百个连接,服务器可能会开始重置多余的连接。解决方案是使用
requests.adapters.HTTPAdapter 通过限制
pool_maxsize 和
max_retries 来使用连接池:
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
session = requests.Session()
retries = Retry(total=3, backoff_factor=1, status_forcelist=[502, 503, 504])
adapter = HTTPAdapter(max_retries=retries, pool_connections=20, pool_maxsize=20)
session.mount("http://", adapter)
session.mount("https://", adapter)
response = session.get("https://httpbin.org/ip", proxies=proxies, timeout=10)
4. 代理服务器超时
如果代理“挂起”超过设定的超时,您将看到
ProxyError,被包装在
ReadTimeoutError 中。将超时增加到合理的值
(通常 15-30 秒足够)并单独添加异常处理:
try:
response = requests.get(url, proxies=proxies, timeout=20)
except requests.exceptions.ProxyError as e:
print(f"代理问题:{e}")
except requests.exceptions.Timeout:
print("代理在规定时间内没有响应")
SSLError WRONG_VERSION_NUMBER:问题分析
这个错误乍一看是最难理解的,但实际上几乎总是有一个原因。
requests.exceptions.SSLError: HTTPSConnectionPool(host='example.com', port=443):
Max retries exceeded with url: / (Caused by SSLError(SSLError(1, '[SSL:
WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1123)')))
WRONG_VERSION_NUMBER 表示客户端试图在服务器期望普通 HTTP 流量(未加密)的地方执行 SSL/TLS 握手。最常见的原因是
proxies 字典中指定的代理协议不正确。
常见错误: 在代理服务器地址中指定 https:// 而不是
http://。大多数 HTTP(S) 代理即使在通过 CONNECT 机制代理 HTTPS 流量时也会通过普通 HTTP 接受连接。
# 错误 — 将导致 WRONG_VERSION_NUMBER
proxies = {
"http": "https://user:password@proxy_host:port",
"https": "https://user:password@proxy_host:port",
}
# 正确
proxies = {
"http": "http://user:password@proxy_host:port",
"https": "http://user:password@proxy_host:port",
}
第二个常见原因是物理上不支持 TLS 的端口。例如,如果代理提供商仅为 HTTP 流量分配了端口 8080,而您试图直接在其上建立 TLS 连接(绕过 CONNECT), 服务器将以普通文本响应,而不是 TLS 握手,OpenSSL 客户端库将返回此错误。 请检查您的代理提供商的文档,了解每个端口可用的端口和协议(HTTP/HTTPS/SOCKS5)。
第三个选项是您使用 socket 或第三方库直接与代理建立 TCP 连接,绕过标准的 requests 机制。在这种情况下,请确保
TLS 封装仅应用于实际目标网站,而不是与代理的连接。
407 Proxy Authentication Required:授权
响应代码 407 是标准 401 的类似物,但适用于代理服务器。它表示服务器要求 授权,而您要么没有提供,要么以错误的格式提供。
方法 1:直接在 URL 中输入用户名和密码
proxies = {
"http": "http://login:password@proxy_host:port",
"https": "http://login:password@proxy_host:port",
}
response = requests.get("https://httpbin.org/ip", proxies=proxies)
这种方法最简单,但如果用户名或密码中包含特殊字符(例如 @,
:, /),
需要通过 urllib.parse.quote 进行转义:
from urllib.parse import quote
login = quote("user@domain")
password = quote("p@ssw:ord!")
proxy_url = f"http://{login}:{password}@proxy_host:port"
proxies = {"http": proxy_url, "https": proxy_url}
方法 2:手动设置 Proxy-Authorization 头
如果代理提供商使用非标准授权方案或您需要更多控制,可以通过 base64 编码手动构建头:
import base64
import requests
login = "user"
password = "password"
credentials = base64.b64encode(f"{login}:{password}".encode()).decode()
headers = {
"Proxy-Authorization": f"Basic {credentials}"
}
proxies = {
"http": "http://proxy_host:port",
"https": "http://proxy_host:port",
}
response = requests.get(
"https://httpbin.org/ip",
proxies=proxies,
headers=headers,
timeout=10
)
重要提示:某些代理提供商将授权绑定到特定的静态客户端 IP(白名单)。如果您更改 IP(例如,通过 VPN 或动态 IP 提供商工作),407 错误 可能会在凭证正确的情况下出现——因为您当前的外部 IP 未在代理的白名单中。解决方案是登录到提供商的个人账户并更新允许的 IP 列表,或者切换到 通过用户名/密码而不是 IP 白名单进行授权。
重试逻辑和异常处理
在实践中,代理错误很少是一次性的——尤其是在大规模解析时。代码的正确架构 应该考虑到带有延迟的重试和每个错误的单独日志记录。
import time
import requests
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def fetch_with_retry(url, proxies, max_retries=3, backoff=2):
for attempt in range(1, max_retries + 1):
try:
response = requests.get(url, proxies=proxies, timeout=15)
response.raise_for_status()
return response
except requests.exceptions.ProxyError as e:
logger.warning(f"[尝试 {attempt}] ProxyError: {e}")
except requests.exceptions.SSLError as e:
logger.warning(f"[尝试 {attempt}] SSLError: {e}")
except requests.exceptions.HTTPError as e:
if response.status_code == 407:
logger.error("407: 请检查代理的用户名/密码或 IP 白名单")
break
logger.warning(f"[尝试 {attempt}] HTTPError: {e}")
except requests.exceptions.Timeout:
logger.warning(f"[尝试 {attempt}] 连接超时")
time.sleep(backoff * attempt)
raise RuntimeError(f"在 {max_retries} 次尝试后无法获取 {url}")
这种方法节省了调试时间:而不是在第一次网络故障时脚本崩溃,您将获得一份清晰的日志, 指出每次尝试的具体错误类型。
代理轮换和防止封禁
如果 ProxyError 和 407 通常与代码设置有关,那么大规模封禁和在特定域名上稳定的连接拒绝几乎总是指向 IP 池质量的问题。对于解析 市场(Wildberries、Ozon、Avito)或社交媒体等任务,建议在每次请求或通过指定间隔进行 IP 轮换。
import random
proxy_pool = [
"http://user:[email protected]:8000",
"http://user:[email protected]:8000",
"http://user:[email protected]:8000",
]
def get_random_proxy():
proxy = random.choice(proxy_pool)
return {"http": proxy, "https": proxy}
response = requests.get(url, proxies=get_random_proxy(), timeout=15)
对于生产环境的解析器,通常更方便使用提供商端的自动轮换代理——这样就不需要自己维护 IP 列表,轮换通过一个端点透明地进行。在这种情况下, 住宅代理 特别适合绕过具有严格检测系统的网站上的反机器人保护,而 数据中心代理 是一个不错的选择, 如果请求速度重要,而目标网站并不积极阻止数据中心范围。
| 代理类型 | 速度 | 封禁风险 | 任务 |
|---|---|---|---|
| 数据中心代理 | 高 | 中高 | 快速解析开放 API |
| 住宅代理 | 中等 | 低 | 解析市场、社交媒体 |
| 移动代理 | 中低 | 最低 | 与严格的反机器人系统合作 |
Session、超时和SSL验证
对于长期运行的脚本(例如循环解析器),建议使用
requests.Session() 而不是单独调用
requests.get()。Session 复用 TCP 连接,
这降低了代理服务器的负载,并减少了大量请求中的 ProxyError。
session = requests.Session()
session.proxies.update({
"http": "http://user:password@proxy_host:port",
"https": "http://user:password@proxy_host:port",
})
for url in urls_list:
try:
resp = session.get(url, timeout=15)
print(resp.status_code, url)
except requests.exceptions.RequestException as e:
print(f"{url} 上的错误:{e}")
另外,值得提及的是 verify 参数。如果您使用的代理自我替换 SSL 证书(用于调试的 MITM 代理或某些企业代理),
requests 可能会因证书不匹配而发出警告。完全禁用验证
(verify=False) 仅应在测试环境中进行——在生产环境中
这会带来“中间人攻击”的风险:
# 仅用于本地调试!
response = requests.get(url, proxies=proxies, verify=False, timeout=15)
# 正确的方法是指定代理的受信任证书路径
response = requests.get(url, proxies=proxies, verify="/path/to/proxy_cert.pem")
错误诊断检查表
如果脚本因代理错误崩溃,请从上到下检查此列表——在 90% 的情况下,原因会在前三个点中找到。
- 检查字典
proxies中的协议——几乎总是应该是http://,即使是对于 HTTPS 请求。 - 通过
curl -x直接测试代理,以排除代码侧的问题。 - 确保用户名和密码在 URL 中不包含未转义的特殊字符。
- 检查代理是否绑定到特定 IP(白名单),而不是通过用户名/密码进行授权。
- 在认为代理不工作的情况下,将
timeout增加到 15-30 秒。 - 如果错误仅在特定网站上出现——问题在于 IP 的声誉,而不是代码。
- 添加带有指数延迟的重试逻辑,而不是单次尝试。
- 在大规模解析时使用 IP 轮换,而不是在所有请求中使用一个静态代理。
结论
Python requests 中的大多数代理错误归结为三件事:代理 URL 中的协议不正确
(http 而不是 https),
授权缺失或格式错误,以及 IP 池的质量。ProxyError 通常表示连接问题或 IP 被封禁,SSLError WRONG_VERSION_NUMBER 几乎总是指向代理协议不正确,而 407 —
则表示用户名/密码或白名单存在问题。
如果您在解析市场或社交媒体时经常遇到封禁和 407 错误,您应该重新审视代理的来源,而不仅仅是代码。为了实现稳定的解析并降低封禁率, 我们建议尝试 住宅代理—— 它们使用真实的 IP 地址,并且在受保护的网站上更少引发 ProxyError。如果您更关注速度和请求量,而不是绕过复杂的反机器人系统,请考虑 数据中心代理——它们更便宜 并且在简单解析任务中更快。