Python3.6下Requests/urllib无法通过代理使用HTTPS的问题求助
Fixing HTTPS Proxy Stalls in Python 3.6 (Requests & urllib)
我之前维护老项目用Python 3.6的时候也踩过一模一样的坑!问题根源很明确:Python 3.6里的urllib(包括依赖它的Requests)默认会用HTTP/1.0发送CONNECT请求,而很多现代代理已经不再兼容这个老旧的协议版本了——这就是为什么你的进程会永久卡住,代理压根就不响应这个旧格式的请求。而curl默认用的是HTTP/1.1,所以能正常建立连接。
下面是针对两个库的具体解决方案:
解决urllib的代理问题
我们可以自定义一个代理处理器,强制让CONNECT请求使用HTTP/1.1:
import urllib.request import http.client # 自定义代理处理器,强制使用HTTP/1.1发送CONNECT class HTTP11ProxyHandler(urllib.request.HTTPProxyHandler): def connect(self, req): # 建立到代理的HTTP连接 conn = http.client.HTTPConnection(self.proxy_host, self.proxy_port) # 设置隧道目标,带上请求头 conn.set_tunnel(req.host, req.port, headers=req.headers) # 用HTTP/1.1发送CONNECT请求 conn.request("CONNECT", "/", "", req.headers) response = conn.getresponse() # 检查代理是否返回连接成功 if response.status != 200: raise urllib.error.URLError(f"Proxy connection failed: {response.status} {response.reason}") # 返回建立好的socket return conn.sock # 配置代理并构建opener proxy_settings = {"https": "http://127.0.0.1:8899"} proxy_handler = HTTP11ProxyHandler(proxy_settings) opener = urllib.request.build_opener(proxy_handler) urllib.request.install_opener(opener) # 测试请求 try: response = urllib.request.urlopen("https://example.com") print(response.read().decode("utf-8")) except Exception as e: print(f"Error: {e}")
解决Requests的代理问题
Requests底层依赖urllib3,所以我们可以通过修改urllib3的协议版本来解决,有两种方式:
方式一:全局修改urllib3配置(简单直接)
import requests from urllib3.connection import HTTPConnection # 强制urllib3使用HTTP/1.1 HTTPConnection._http_vsn = 11 HTTPConnection._http_vsn_str = 'HTTP/1.1' # 配置代理并发送请求 proxies = { "https": "http://127.0.0.1:8899" } try: response = requests.get("https://example.com", proxies=proxies) print(response.text) except Exception as e: print(f"Error: {e}")
方式二:自定义适配器(更优雅,不影响全局)
如果不想修改全局设置,可以给Requests会话挂载一个自定义适配器,只针对特定请求生效:
import requests from urllib3.poolmanager import PoolManager from urllib3.connection import HTTPConnection class HTTP11Adapter(requests.adapters.HTTPAdapter): def init_poolmanager(self, connections, maxsize, block=False): # 创建使用HTTP/1.1的连接池管理器 self.poolmanager = PoolManager( num_pools=connections, maxsize=maxsize, block=block, connection_class=lambda *args, **kwargs: HTTPConnection( *args, _http_vsn=11, _http_vsn_str='HTTP/1.1', **kwargs ) ) # 创建会话并挂载适配器 session = requests.Session() session.mount("https://", HTTP11Adapter()) # 配置代理并发送请求 proxies = { "https": "http://127.0.0.1:8899" } try: response = session.get("https://example.com", proxies=proxies) print(response.text) except Exception as e: print(f"Error: {e}")
额外注意事项
- 如果代理需要认证,直接在代理URL里加上用户名和密码即可,比如:
"https": "http://username:password@127.0.0.1:8899" - Python 3.6已经停止维护了,如果条件允许,尽量升级到更高版本(比如3.8+),新版本的urllib/Requests默认已经使用HTTP/1.1发送CONNECT请求,不会有这个问题。
内容的提问来源于stack exchange,提问作者Jorjon
相关产品推荐
相关产品推荐

