如果你天天用 requests.get(),请务必读懂这篇文章

本文涉及的产品
RDS DuckDB + QuickBI 企业套餐,8核32GB + QuickBI 专业版
简介: 本文深度剖析Requests底层原理,揭秘HTTP请求全链路:从Session调度、PreparedRequest格式化,到HTTPAdapter适配、ConnectionPool连接复用,直至socket层I/O。厘清代理介入时机与报错根因,附高并发爬虫最佳实践。
作为一名常年和爬虫、高并发数据工程打交道的程序员,这个报错你一定不陌生:
ProxyError: HTTPSConnectionPool(host='example.com', port=443): Max retries exceeded
或者在深夜跑数时突然蹦出来的:
ConnectionResetError: [Errno 104] Connection reset by peer
大多数人在遇到这类问题时,习惯性的动作是打开搜索引擎,盲目地换几个代理 IP 或者加几行重试代码,运气好问题解决了,运气不好就继续在工位上抓耳挠腮。 今天,我们不搞玄学。直接脱掉 Requests 的外衣,去它的源码底层看一看: 一个 HTTP 请求到底经历了什么?连接池是怎么复用的?代理又是在哪一步被强行塞进去的? 干货满满,建议先赞后看。

一、 Requests 架构全景图:它真的只是个“壳”

很多人以为 Requests 承载了所有网络通信逻辑,但读过源码后你会发现,Requests 本质上是一个优秀的 高层封装 ,它把脏活累活全委派给了底层的 urllib3 它的核心调用链路如下:
requests.get()
    ↓
Session.request()
    ↓
Session.send()
    ↓
PreparedRequest
    ↓
HTTPAdapter.send()        ← 握手 urllib3
    ↓
ConnectionPool            ← 掌管连接池 (urllib3)
    ↓
socket                    ← Python标准库
为了让大家更直观地理解,我把各层级的职责梳理成了一张表:
层级 组件 职责
高层封装 requests 提供极其友好的用户 API,封装 Cookie、认证和自动重试机制。
请求构造 PreparedRequest 将复杂的 Python 参数转换成符合 HTTP 标准的字节格式。
适配器层 HTTPAdapter 作为桥梁接入 urllib3,处理连接池与代理的路由。
连接池层 ConnectionPool 管理长连接(Keep-Alive),负责复用 TCP socket 减少握手开销。
传输层 socket 真正执行底层网络字节流的发送与接收。

二、 核心步骤拆解:从写下代码到字节流出发

Step 1: 请求入口 — Session.send()

当我们调用 requests.get() 时,它只是个快捷方式,底层会立刻转交给 Session 类来处理。
# requests/sessions.py 核心逻辑简化
class Session(SessionRedirectMixin):
    def request(self, method, url, ...):
        # 1. 注入默认请求头(比如大家熟知的 User-Agent)
        headers = headers or {
   }
        headers.setdefault("User-Agent", "python-requests/2.31.0")

        # 2. 创建 PreparedRequest 对象
        req = PreparedRequest()
        req.prepare_request(headers, data, ...)

        # 3. 协调发送
        response = self.send(req, **kwargs)
        return response

技术内幕: Session 是请求的管理容器。它自己不负责网络 I/O,它的核心职责是协调:选择正确的适配器、处理 Cookie 状态的维持以及应对重定向。

Step 2: 规范化格式 — PreparedRequest

在网络世界里,服务器只认符合 HTTP 协议标准的文本。 PreparedRequest 的工作就是把你传的字典、字符串等“散装参数”组装好。
class PreparedRequest(RequestEncodingMixin):
    def prepare_url(self, url, params):
        # 对 URL 进行标准编码,拼接 Query String
        if params:
            url = url + "?" + urlencode(params)
        self.url = url
无论是 URL 编码、Headers 的大小写不敏感处理( CaseInsensitiveDict ),还是 Body 的序列化,都在这一步完成。在进入具体的发送流程前,任何请求都必须被转化为一个 PreparedRequest 实例。

Step 3 & 4: 进军底层 — HTTPAdapter ConnectionPool

这是最精彩的部分。Requests 默认的适配器 HTTPAdapter 内部持有一个 urllib3.PoolManager() 当请求发起时, PoolManager 会根据目标网站的 (scheme, host, port) 作为 Key,去寻找对应的 HTTPConnectionPool
# urllib3/poolmanager.py 核心逻辑
class PoolManager:
    def get_pool(self, host, port, scheme):
        key = (scheme, host, port)
        if key not in self.pools:
            # 如果没有,就为这个 host 专门建一个连接池
            self.pools[key] = HTTPConnectionPool(host, port, ...)
        return self.pools[key]
  • 为什么要搞个连接池? 如果不搞,你每一次 requests.get() 都要经历:创建 socket -> TCP 三次握手 -> 发数据 -> 四次挥手销毁。在高并发爬虫场景下,频繁握手会让效率低到令人发指。
  • 复用机制: 有了连接池,当请求来临时,先看池里有没有空闲的旧连接,有就直接拿来发数据,省去了 TCP 握手的三次交换时间。

Step 5: 最终决战 — socket 层的网络 I/O

HTTPConnection 内部,Python 终于揭开了它最底层的网络面纱:
class HTTPConnection:
    def connect(self):
        # 建立底层的 TCP 连接
        self.sock = socket.create_connection((self.host, self.port), timeout)
        if self.scheme == "https":
            self.sock = ssl.wrap_socket(self.sock, ...) # 加密握手
连接建立后,它会将协议行、请求头拼接成标准的文本串,通过 self.sock.sendall() 变成电信号发往远端服务器。

三、 爬虫代理的隐秘角落:代理到底在哪个层面介入?

作为爬虫程序员,我们天天都在和代理 IP 打交道。以下是一个接入 16YUN爬虫代理 的典型生产环境示例:
import requests
from requests.auth import HTTPProxyAuth

# 亿牛云代理配置信息
proxy_host = "http.proxy.16yun.cn"
proxy_port = 8080
proxy_user = "your_username"
proxy_pass = "your_password"

# 组装代理 URL(将用户名密码内嵌)
proxy_url = f"http://{proxy_user}:{proxy_pass}@{proxy_host}:{proxy_port}"

proxies = {
   
    "http": proxy_url,
    "https": proxy_url,
}

# 针对需要显式 Proxy 认证的对象
auth = HTTPProxyAuth(proxy_user, proxy_pass)

try:
    response = requests.get(
        "https://httpbin.org/ip",
        proxies=proxies,
        auth=auth,
        timeout=10,
        verify=False  # 某些隧道代理使用自签证书时需注意
    )
    print(f"返回IP: {response.json()}")
except requests.exceptions.ProxyError as e:
    print(f"代理连接失败,可能代理服务器宕机或认证过期: {e}")

那么,当你传入 proxies 字典时,底层发生了什么?

答案是: 它直接改变了 ConnectionPool 的路由方向。 ConnectionPool
  • 常规模式: 你的 socket 直连目标服务器(如 example.com:80)。
  • 代理模式: urllib3 在建立连接时,会把 socket 的连接目标改为代理服务器的地址(例如上述的 http.proxy.16yun.cn:8080)。
在发送的 HTTP 报文数据行上,也会发生微妙的变化:
# 无代理直连时
GET /api HTTP/1.1
Host: example.com

# 有代理介入时
GET http://example.com/api HTTP/1.1
Host: http.proxy.16yun.cn
代理服务器正是通过读取请求行里完整的 http://example.com/api ,从而知道自己该把这个请求转发到哪里去。 如果你遇到了文章开头提到的 ProxyError ,在读懂源码后,你的排查思路应当无比清晰:
  1. 连接失败: 检查你的机器到代理服务器(如爬虫代理主机)的端口通不通。
  2. 认证失败: 检查用户名和密码是否拼写错误、格式是否正确,或者代理套餐是不是到期欠费了。

四、 避坑指南:数据工程中的最佳实践

1. 别再盲目新建 Session 了!

很多新手喜欢在函数内部写 requests.get() ,或者每次请求都声明一个全新的 requests.Session() 。这样做的后果是,每一次请求都在重复创建和销毁连接池,连接池直接沦为摆设,并发一高还会导致 连接泄露 和大量系统句柄占满。
  • 正确姿势: 全局复用同一个 Session 实例。
# 推荐:全局复用
SUITE_SESSION = requests.Session()

def fetch_data(url):
    return SUITE_SESSION.get(url)

2. 遭遇高并发?记得调大连接池容量

Requests 默认的连接池大小( pool_maxsize )是 10 。在多线程高并发爬虫场景下,10 个连接根本不够用,会导致大量线程在 urllib3 层等待空闲连接。
  • 正确姿势: 显式修改适配器的连接池上限。
adapter = requests.adapters.HTTPAdapter(
    pool_connections=20,  # 允许缓存的 host 池数量
    pool_maxsize=50       # 每个池子内的最大连接数
)
session.mount("https://", adapter)

3. 超时设置要精准

timeout 并不是针对整个请求的下载时间,而是传递给最底层的 socket 的。为了防止爬虫卡死在某些垃圾代理或慢速服务器上,强烈建议传入元组进行分阶段控制:
# (连接超时, 读取超时)
requests.get(url, timeout=(3.05, 27))

总结

看透了 Requests 的底层,你会发现网络编程的本质依然是那些经典的概念:协议格式化、Socket 传输、连接池复用。了解了 HTTPAdapter ConnectionPool 的运作机制,下次再面对各式各样的网络报错,相信你已经能够从容地定位到源码层级,优雅地解决它了。
相关文章
|
6天前
|
人工智能 自然语言处理 文字识别
阿里云百炼Qwen3.7-Max简介:能力、优势、支持订阅计划参考
Qwen3.7-Max是阿里云百炼面向智能体时代推出的新一代旗舰模型,对标GPT-5.5、Claude Opus 4.7等闭源旗舰。该模型支持百万级token上下文窗口,具备顶级推理能力、多模态搜索与视觉理解增强、流式输出低延迟响应等核心优势,覆盖编程、办公、长周期自主执行等复杂场景。同时支持OpenAI接口兼容,便于系统快速迁移。用户可通过Token Plan团队或节省计划等订阅方式灵活调用,适合企业级高要求场景使用。
3077 10
阿里云百炼Qwen3.7-Max简介:能力、优势、支持订阅计划参考
|
14天前
|
人工智能 开发工具 iOS开发
Claude Code 新手完全上手指南:安装、国产模型配置与常用命令全解
Claude Code 是一款运行在终端环境中的 AI 编程助手,能够直接在命令行中完成代码生成、项目分析、文件修改、命令执行、Git 管理等开发全流程工作。它最大的特点是**任务驱动、终端原生、轻量高效、多模型兼容**,无需图形界面、不依赖 IDE 插件,能够深度融入开发者日常工作流。
3492 12
|
16天前
|
Shell API 开发工具
Claude Code 快速上手指南(新手友好版)
AI编程工具卷疯啦!Claude Code凭借任务驱动+终端原生的特性,成了开发者的效率搭子。本文从安装、登录、切换国产模型到常用命令,手把手带新手快速上手,全程避坑,30分钟独立用起来。
3576 25
|
10天前
|
人工智能 Linux BI
国内用 Claude Code 终于不用翻墙了:一行命令搞定,自动接 DeepSeek
JeecgBoot AI专题研究 一键脚本:Claude Code + JeecgBoot Skills + DeepSeek 全平台接入 一行命令装好 Claude Code + JeecgBoot Skills + DeepSeek 接入,无需翻墙使用 Claude Code,支持 Wind
2769 6
国内用 Claude Code 终于不用翻墙了:一行命令搞定,自动接 DeepSeek
|
8天前
|
人工智能 自然语言处理 供应链
|
8天前
|
人工智能 自然语言处理 安全
Claude Code 全攻略:命令大全+三种模式+记忆体系+实战工作流完整手册
Claude Code 是当前最流行的终端级 AI 编程助手,能够直接在命令行中完成代码生成、项目理解、文件修改、命令执行、错误修复等全流程开发工作。它不依赖图形界面、不占用额外资源,却能深度理解项目结构,自动生成规范代码,大幅提升研发效率。
1307 3
|
29天前
|
人工智能 JSON 供应链
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」
LucianaiB分享零成本畅用JVS Claw教程(学生认证享7个月使用权),并开源GeoMind项目——将JVS改造为科研与产业地理情报可视化AI助手,支持飞书文档解析、地理编码与腾讯地图可视化,助力产业关系图谱构建。
23612 15
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」
|
1天前
|
人工智能 缓存 自然语言处理
阿里Qwen3.7-Max评测:Agent能力显著提升,耗时与调用成本大幅下降
阿里云百炼推出面向智能体的旗舰大模型Qwen3.7-Max,具备长周期自主执行能力,显著提升编程、办公自动化等复杂任务处理水平;支持MCP集成与多框架兼容,并以限时5折+100万Tokens免费试用大幅降低使用门槛,助力企业高效落地AI应用。在阿里云百炼平台快速体验:https://t.aliyun.com/U/fPVHqY