Python标准库HTTPS请求失败,requests成功的原因探究
先帮你拆解两个标准库失败的核心原因,再看看requests到底做了哪些额外处理来规避这些问题:
一、你的标准库代码失败的具体诱因
1. httplib的超时问题
你的httplib代码用了www.python.org,而requests请求的是python.org——这是第一个关键差异:www.python.org大概率会触发重定向,但httplib默认不会自动跟随重定向;另外你没有设置任何请求头(比如User-Agent),部分服务器会对无标识的“裸请求”进行限流或直接拒绝,最终导致连接超时。
同时,Python2.7的HTTPSConnection连接参数非常原始,没有内置重试、连接池管理机制,遇到轻微网络波动就容易触发超时报错。
2. urllib的SSL未知协议错误
这个问题更明确:Python2.7的ssl模块默认仅支持较旧的SSL/TLS版本(比如TLSv1.0甚至SSLv3),而python.org这类现代服务器早已禁用了这些不安全的旧协议,只支持TLSv1.2及以上版本。当urllib尝试用旧协议握手时,服务器直接返回“未知协议”的错误。
二、requests做了哪些关键优化?
requests之所以能成功,核心是它基于urllib3做了大量封装和适配,精准解决了标准库的痛点:
自动处理域名重定向:requests默认会自动跟随3xx重定向(可通过
allow_redirects=False关闭),不管你请求的是python.org还是www.python.org,它都会帮你跳转至最终的有效地址。适配现代SSL/TLS协议:requests会自动配置SSL上下文,启用对TLSv1.2、TLSv1.3的支持(只要系统SSL库允许),彻底避免旧协议握手失败的问题。而Python2.7标准库的
ssl模块需要手动指定ssl.PROTOCOL_TLSv1_2才能启用这些协议。默认添加标准请求头:requests会自动给请求加上
User-Agent、Accept、Accept-Encoding等必要请求头,模拟标准客户端的行为,避免被服务器识别为“异常请求”而拒绝。比如你的httplib代码完全没有设置请求头,这很容易被限流。更鲁棒的连接管理:urllib3(requests的底层依赖)实现了连接池、自动重试、超时精细化控制等功能,相比标准库的httplib/urllib,能更好地处理网络波动和服务器响应延迟。
三、如果非要用Python2.7标准库解决问题,你可以这么改
修复urllib的SSL问题(手动指定TLS版本)
import urllib import ssl # 创建支持TLSv1.2的SSL上下文 context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) # 用自定义上下文打开HTTPS链接 response = urllib.urlopen("https://python.org", context=context) print(response.read())
修复httplib的超时与重定向问题
from httplib import HTTPSConnection import ssl # 指定支持TLSv1.2的上下文,请求主域名 conn = HTTPSConnection('python.org', context=ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)) # 添加User-Agent请求头,模拟正常客户端 conn.request('GET', '/', headers={'User-Agent': 'Mozilla/5.0'}) resp = conn.getresponse() # 手动处理重定向逻辑 if resp.status in (301, 302): redirect_url = resp.getheader('Location') # 解析重定向后的域名和路径 domain = redirect_url.split('//')[1].split('/')[0] path = '/' + '/'.join(redirect_url.split('/')[3:]) if len(redirect_url.split('/'))>3 else '/' # 重新发起请求 new_conn = HTTPSConnection(domain, context=ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)) new_conn.request('GET', path, headers={'User-Agent': 'Mozilla/5.0'}) resp = new_conn.getresponse() print(resp.read())
内容的提问来源于stack exchange,提问作者ToonAlfrink

