杰瑞科技汇

Python requests如何实现长连接?

HTTP Keep-Alive

我们需要理解什么是 HTTP 长连接,在 HTTP/1.0 中,默认情况下,每次客户端和服务器之间的请求/响应完成后,TCP 连接就会被关闭,如果客户端需要再次请求资源,就必须重新建立一个新的 TCP 连接,这个过程包括“三次握手”,会带来额外的延迟和开销。

Python requests如何实现长连接?-图1
(图片来源网络,侵删)

为了解决这个问题,HTTP/1.1 引入了 Keep-Alive 机制,它允许在同一个 TCP 连接上发送多个 HTTP 请求和响应,而不必为每个请求都重新建立连接,这大大减少了网络延迟,提高了性能,尤其是在需要请求同一服务器上多个资源的场景下(一个网页加载 HTML、CSS、JS、图片等)。

requests 库默认就支持并开启了 Keep-Alive


requests 如何默认使用长连接

当你使用 requests 发起请求时,它会自动在请求头中添加 Connection: keep-alive,并尝试复用底层的 TCP 连接。

示例:

Python requests如何实现长连接?-图2
(图片来源网络,侵删)
import requests
# 第一次请求
response1 = requests.get('https://httpbin.org/get')
print(f"第一次请求状态码: {response1.status_code}")
# 第二次请求到同一个域名
response2 = requests.get('https://httpbin.org/get')
print(f"第二次请求状态码: {response2.status_code})
# 第三次请求到同一个域名的不同路径
response3 = requests.get('https://httpbin.org/headers')
print(f"第三次请求状态码: {response3.status_code}")

在上面的例子中,requests 会尽可能地在同一个 TCP 连接上完成这三个请求,而不是为每个请求都新建一个连接。


如何手动控制长连接行为

虽然 requests 默认开启长连接,但你也可以手动进行控制。

A. 禁用长连接

如果你想在特定请求中禁用长连接,可以手动设置 Connection: close 头。

import requests
headers = {'Connection': 'close'}
response = requests.get('https://httpbin.org/get', headers=headers)
print(f"禁用长连接后的状态码: {response.status_code}")
print(f"服务器返回的响应头: {response.headers}")

在这个例子中,requests 会发送 Connection: close 头,告诉服务器这个请求完成后就关闭连接,服务器在响应中通常也会包含 Connection: close

Python requests如何实现长连接?-图3
(图片来源网络,侵删)

B. 强制使用新连接

有时,你可能想强制 requests 不使用已经存在的连接,而是创建一个新的,这在调试或者需要确保请求完全独立的情况下非常有用。

你可以通过 Session 对象的 close() 方法来关闭底层连接,或者使用 requests.request() 而不是 Session 的方法(但这不是最优雅的方式)。

更推荐的方式是,当你想强制使用新连接时,创建一个新的 Session 对象。

import requests
# 创建一个 Session 对象
session = requests.Session()
# 第一次请求,会建立一个连接
response1 = session.get('https://httpbin.org/get')
print(f"第一次请求状态码: {response1.status_code}")
# 关闭当前 Session 的所有连接
session.close() 
# 再次请求时,requests 会发现连接已关闭,从而建立新连接
response2 = session.get('https://httpbin.org/get')
print(f"关闭连接后再次请求的状态码: {response2.status_code}")
# 完全关闭 Session
session.close()

另一种更直接的方式是,在每次需要新连接时,都创建一个新的 Session

import requests
# 第一次请求
session1 = requests.Session()
response1 = session1.get('https://httpbin.org/get')
session1.close()
# 第二次请求,使用全新的 Session
session2 = requests.Session()
response2 = session2.get('https://httpbin.org/get')
session2.close()
print("两次请求使用了不同的 Session 对象,因此必然是不同的连接。")

连接池(requests.Session

requests.Session 是实现高效复用连接的关键,当你创建一个 Session 对象时,它会创建一个连接池(Connection Pool)。

  • 工作原理:当 Session 发起请求时,它会从连接池中查找一个可用的、空闲的连接到目标服务器,如果找到了,就直接使用;如果没有,或者连接池已满,它就会创建一个新的连接。
  • 优点:对于向同一个服务器发起多个请求的场景(爬虫与 API 交互),使用 Session 可以极大地减少连接建立的开销,显著提升性能。

最佳实践: 在一个应用程序的生命周期内(一个爬虫任务),尽量复用一个 Session 对象。

import requests
# 推荐:在整个应用生命周期内复用 Session
session = requests.Session()
try:
    # 发送多个请求
    response1 = session.get('https://api.example.com/users/1')
    print(f"请求1: {response1.json()}")
    response2 = session.get('https://api.example.com/users/2')
    print(f"请求2: {response2.json()}")
    response3 = session.post('https://api.example.com/users', json={'name': 'John Doe'})
    print(f"请求3: {response3.json()}")
finally:
    # 确保在最后关闭 Session,释放连接池
    session.close()

超时设置

长连接虽然高效,但如果服务器长时间没有响应,可能会导致连接一直占用,设置合理的超时非常重要。

  • timeout 参数可以防止程序无限期等待。
  • 它可以是一个单一的浮点数(timeout=5.0),这表示连接和读取的总超时时间。
  • 也可以是一个元组 (connect_timeout, read_timeout),分别指定连接超时和读取超时。
import requests
# 设置总超时为 5 秒
try:
    response = requests.get('https://httpbin.org/delay/10', timeout=5)
except requests.exceptions.Timeout:
    print("请求超时了!")
# 分别设置连接超时和读取超时
try:
    # 连接超时 2 秒,读取超时 5 秒
    response = requests.get('https://httpbin.org/delay/10', timeout=(2, 5))
except requests.exceptions.ConnectTimeout:
    print("连接超时了!")
except requests.exceptions.ReadTimeout:
    print("读取超时了!")

总结与最佳实践

场景 推荐做法 原因
单个、独立的请求 直接使用 requests.get() 简单直接,requests 会自动处理。
向同一服务器发起多个请求 使用 requests.Session() 这是关键! Session 的连接池会复用 TCP 连接,性能提升巨大。
需要强制使用新连接 创建一个新的 Session 对象,或调用 session.close() 确保请求的独立性,避免复用可能带来问题的旧连接。
需要禁用长连接 在请求头中添加 {'Connection': 'close'} 特殊场景下的需求,如调试或兼容旧服务器。
所有网络请求 务必设置 timeout 防止因网络问题或服务器无响应而导致程序卡死。
代码健壮性 使用 try...except 处理异常 网络请求不可靠,必须处理可能发生的 ConnectionError, Timeout, HTTPError 等。

最终示例:一个健壮的、使用长连接的爬虫/客户端

import requests
from requests.exceptions import RequestException
def fetch_data(url, session, timeout=10):
    """
    使用给定的 Session 从 URL 获取数据。
    """
    try:
        response = session.get(url, timeout=timeout)
        # 检查 HTTP 状态码
        response.raise_for_status()  # 如果状态码不是 2xx,则抛出 HTTPError
        return response.json()
    except RequestException as e:
        print(f"请求 {url} 时发生错误: {e}")
        return None
def main():
    # 目标 API 的基础 URL
    base_url = "https://jsonplaceholder.typicode.com"
    # 创建一个 Session 对象来复用连接
    with requests.Session() as session:
        # 1. 获取所有用户
        users_url = f"{base_url}/users"
        users = fetch_data(users_url, session)
        if users:
            print(f"成功获取到 {len(users)} 个用户。")
            # 2. 获取第一个用户的帖子
            first_user_id = users[0]['id']
            posts_url = f"{base_url}/posts?userId={first_user_id}"
            posts = fetch_data(posts_url, session)
            if posts:
                print(f"成功获取到用户 {first_user_id} 的 {len(posts)} 篇帖子。")
if __name__ == "__main__":
    main()

在这个例子中,我们:

  1. 使用 with requests.Session() as session: 来管理 Session,确保它在代码块执行完毕后自动关闭。
  2. 将网络请求逻辑封装在 fetch_data 函数中,并处理了常见的异常。
  3. 通过 session.get() 复用连接,提高了与 jsonplaceholder.typicode.com 服务器交互的效率。
分享:
扫描分享到社交APP
上一篇
下一篇