返回博客
指南
Ștefan RăcilăLast updated on May 7, 20263 min read

如何在 Python 请求中使用代理:从基础到生产

如何在 Python 请求中使用代理:从基础到生产
简而言之:本指南将详细介绍如何在 Python Requests 中端到端地使用代理:包括一个可正常工作的 proxies 字典、需认证的 URL、环境变量、 Session 服务器复用、无DNS泄漏的SOCKS5协议,以及包含重试机制和断路器的轮询池。读完本文,您将明白在何种情况下,托管API比自建代理池更具价值。

引言

如果你曾发布过一个在本地运行正常的爬虫,却在生产环境中开始返回 403、429 错误或无提示超时,那么你已经明白代理存在的意义。掌握如何在 Python Requests 中使用代理,将决定你的脚本是仅在笔记本电脑上运行一次,还是能跨越数千个页面,经受住速率限制、地理封锁和 IP 封禁的考验。

最简单的 Python Requests 代理配置,是一个将 httphttps 映射到代理 URL,并传递给 requests.get()。这能让你暂时解除封锁十分钟。但生产环境需要更多:将凭据排除在 Git 之外、能持久化 Cookie 的会话、不泄露 DNS 的 SOCKS5 端点、带退避策略的重试,以及不会对已死代理持续轰炸的轮换策略。

本指南面向已掌握 requests ,并希望在不重写爬虫的情况下可靠地添加代理支持。我们将涵盖从简单的字典到生产环境轮换循环的 Python Requests 代理使用方法,并用通俗易懂的语言说明其中的权衡取舍。

快速入门:五分钟搭建可用的 Python Requests 代理

在深入探讨轮询和重试机制之前,先看这个仅八行的示例——当开发者查询如何在 Python Requests 中使用代理时,90% 的人实际上需要的正是这个。将其保存到文件中,替换为任何有效的代理主机:端口,然后运行。

import requests

proxies = {
    "http":  "http://203.0.113.10:8080",
    "https": "http://203.0.113.10:8080",
}

resp = requests.get("https://api.ipify.org?format=json", proxies=proxies, timeout=10)
print(resp.json())

如果打印出的 IP 是代理地址而非您的地址,说明您的代理已正确配置在请求路径中。本指南的其余部分将介绍如何强化这一模式。

先决条件:Python、pip 以及一个可访问的代理

你需要 Python 3.8 或更高版本(python --version), pip,以及至少一个可用的代理主机:端口。使用虚拟环境(python -m venv venv)可确保每个项目的依赖关系保持整洁。使用 pip install requests。代理可来自免费列表、付费池,或本地 Squid 及 Tor 实例。

如何在 Python Requests 中使用代理:思维模型

在编写代码之前,了解 Requests 实际如何决定将流量发送至何处会有所帮助。该库会根据协议类型(HTTP、HTTPS 以及(通过额外包支持的)SOCKS)将每次调用路由至相应的代理 URL。该 URL 可由以下三个来源提供,优先级大致如下: proxies= 单次调用的参数、 session.proxies 请求中的 Session,最后是 HTTP_PROXY / HTTPS_PROXY 环境变量。确切的优先级及小写变体处理规则详见 Requests 高级用法文档;请务必对照您锁定的版本进行确认。

使用 Python Requests 配置基本代理

基本设置分为两步:构建一个 proxies 字典,然后通过它发送验证请求。接下来的两个小节将逐步讲解每个步骤,并说明在代理不可用或配置错误时可能出现的陷阱。

构建 HTTP 和 HTTPS 的代理字典

在 Python Requests 中,代理以字典形式传递,该字典将协议映射到代理 URL。即使您仅计划访问 HTTPS 目标,也务必同时填充这两个键,因为重定向可能会导致协议降级。

proxies = {
    "http":  "http://user:pass@proxy.example.com:8080",
    "https": "http://user:pass@proxy.example.com:8080",
}
requests.get(url, proxies=proxies, timeout=(5, 15))

timeout=(connect, read) 在生产环境中是不可或缺的。若缺少它,一个失效的代理会导致你的工作进程挂起。

确认请求路径中包含代理

访问 IP 回显端点并将其与您的真实 IP 进行比对。两个可靠的地址是 https://api.ipify.org?format=jsonhttps://httpbin.org/ip

print(requests.get("https://api.ipify.org?format=json", proxies=proxies, timeout=10).json())

如果返回的地址与您的本地 IP 不一致,则代理正在工作。如果地址匹配,则代理已无声地失败并转为开放模式。

代理认证与凭据保护

大多数付费代理都需要身份验证,这也使得在 Python Requests 中使用代理变得更加复杂。接下来的三个小节将介绍 URL 嵌入、环境变量以及您可能会遇到的三种错误代码。

在代理 URL 中嵌入用户名和密码

支持的格式为 http://user:pass@host:port。如果您的密码包含 @, :, %/,请对其进行 URL 编码,否则 Requests 会错误解析 URL,导致出现 407 错误:

from urllib.parse import quote
user = quote("alice@corp")
pwd  = quote("p@ss:w/rd%1")
proxy_url = f"http://{user}:{pwd}@proxy.example.com:8080"

切勿将该字符串提交至 Git。

将密钥移至 HTTP_PROXY、HTTPS_PROXY 和 NO_PROXY

Requests 会自动识别 HTTP_PROXY, HTTPS_PROXY,并 NO_PROXY ,根据官方文档,它在 POSIX 系统上也支持小写变体。这意味着你可以完全将凭据从代码中移除:

# Linux / macOS
export HTTPS_PROXY="http://user:pass@proxy.example.com:8080"
export NO_PROXY="localhost,127.0.0.1,.internal"
# Windows
setx HTTPS_PROXY "http://user:pass@proxy.example.com:8080"

对于 Docker 镜像和 CI 运行器而言,这是最简洁的模式——密钥应存放在环境中,而非代码仓库中。

诊断 407、401 和 403 代理错误

当出现异常时,状态码会告诉你哪个层出了问题。

状态

可能原因

一行代码修复

407 需要代理身份验证

代理凭据缺失或格式错误

将密码进行 URL 编码后重新测试

401 未授权

用户名或密码错误

轮换凭据并使用以下内容进行验证 curl -x

403 禁止访问

目标网站屏蔽了代理 IP

切换到其他代理或更改地理位置

先检查代理,再检查目标。

A Session 是进行多次调用时的理想基础组件。它会保留 proxies、默认头和 Cookie,并保持底层 TCP 连接处于活动状态,因此您无需在每次请求时都进行新的 TLS 握手。Session 已内置于 Requests 中,因此无需额外安装。

session = requests.Session()
session.proxies = proxies
session.headers.update({"User-Agent": "my-scraper/1.0"})

session.post("https://example.com/login", data={"u": "alice", "p": "secret"})
dashboard = session.get("https://example.com/dashboard")  # cookies persist
print(dashboard.status_code, len(dashboard.content))

同一个会话涵盖 .text, .json().content,因此文本、JSON 和二进制下载均可通过同一 Python Requests 会话代理传输,无需重新配置。

通过 requests[socks] 使用 SOCKS5 代理

Requests 默认不支持 SOCKS。通过 socks extra:

pip install "requests[socks]"

然后使用 socks5h:// scheme。末尾的 h 参数会指示 PySocks 通过代理而非本地解析 DNS,这在您不信任 ISP 的解析器或通过 Tor 运行时正是您所需要的。

proxies = {
    "http":  "socks5h://127.0.0.1:9050",  # Tor default
    "https": "socks5h://127.0.0.1:9050",
}
requests.get("https://check.torproject.org/", proxies=proxies, timeout=15)

Plain socks5:// 模式会在本地解析 DNS,并会悄无声息地泄露你访问的主机名。

轮换代理以避免封禁和速率限制

单个 IP 地址会被限速,最终被封禁。关于如何在大规模场景下使用 Python Requests 配合代理的真正解决方案是轮换,接下来的三个小节将展示成熟度逐渐提高的模式。

带重试循环的随机轮换

最简单的模式是 random.choice 遍历代理列表,并将其封装在重试循环中:

import random, requests
from requests.exceptions import RequestException

PROXIES = [{"http": p, "https": p} for p in PROXY_URLS]

def fetch(url, attempts=4):
    for _ in range(attempts):
        proxy = random.choice(PROXIES)
        try:
            return requests.get(url, proxies=proxy, timeout=10)
        except RequestException:
            continue
    raise RuntimeError("all attempts failed")

虽然有效,但纯粹的随机性会反复选中已失效的代理,且忽略负载情况。

采用“2 的幂”选择策略实现更智能的负载均衡

一种经过深入研究的改进方案是“2的幂次选择法”:对于每个请求,随机选取两个代理,并选择当前处理的待处理请求较少的那一个。这种直觉得到了负载均衡文献的支持,通常归功于 Mitzenmacher 2001 年的分析,其原理在于:与均匀随机选择相比,这种方法在保持低成本的同时,能更有效地抑制最坏情况下的负载。

import random
LOAD = {p: 0 for p in PROXY_URLS}

def pick():
    a, b = random.sample(PROXY_URLS, 2)
    return a if LOAD[a] <= LOAD[b] else b

增量 LOAD[proxy] 在请求前递增,处理后递减。确切收益取决于池大小;引用数值前请先进行基准测试。

添加断路器,防止已宕机的代理继续浪费请求

随机选择和二进制幂选择都会持续选中已死代理,直到成功为止。断路器可解决此问题。按代理追踪状态: CLOSED (正常), OPEN (已跳过) 和 HALF_OPEN (试用期)。

import time
state = {p: {"fail": 0, "open_until": 0} for p in PROXY_URLS}
MAX_FAILS, COOLDOWN = 3, 60

def usable(p):
    return time.time() >= state[p]["open_until"]

def record(p, ok):
    if ok:
        state[p]["fail"] = 0
    else:
        state[p]["fail"] += 1
        if state[p]["fail"] >= MAX_FAILS:
            state[p]["open_until"] = time.time() + COOLDOWN

冷却期结束后,在完全恢复代理之前,先对其发送一个试用请求。

使用 HTTPAdapter 和 urllib3 重试失败的请求

挂载一个 HTTPAdapter 策略 urllib3 Retry 策略挂载到会话上,将对该会话中的每个 HTTP 和 HTTPS 调用应用重试。固定 urllib3 (例如 urllib3==2.2.*) 以便参数名称在升级过程中保持稳定。

from requests import Session
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

retry = Retry(
    total=3,
    status_forcelist=[429, 500, 502, 503, 504],
    backoff_factor=2,
    allowed_methods=["GET", "POST"],
    respect_retry_after_header=True,
)
adapter = HTTPAdapter(max_retries=retry)
s = Session()
s.mount("http://", adapter)
s.mount("https://", adapter)

使用 backoff_factor=2,urllib3 会在每次尝试之间暂停大约 backoff_factor * (2 ** (n - 1)) 秒(约 2、4、8 秒)。将重试与轮询结合,使每次重试都选择一个新的代理。

处理 SSL 验证和自签名代理证书

如果代理提供自签名证书, verify=False 会屏蔽警告但会使您面临中间人攻击的风险,因此仅应在可信的本地代理或测试环境中使用。更安全的解决方法是通过 verify="/path/to/ca.pem"REQUESTS_CA_BUNDLE。仅在您已审慎权衡安全风险后,才应禁用 InsecureRequestWarning 仅在您已刻意权衡过安全风险后才进行。

何时应放弃自建代理池,转而使用托管式爬取 API

请运行此检查清单。若勾选三项或以上,托管代理或爬取 API 通常比您投入的时间更划算:

  • 您需要在两个以上国家/地区进行地理定位。
  • 封禁会导致实际收入损失,而不仅仅是重试。
  • 目标网站使用 JavaScript 渲染内容。
  • 一名资深工程师每周需花费一天时间维护代理池。
  • 合规要求必须使用经过审计的住宅IP。

关键要点

  • 关于如何在 Python Requests 中使用代理的最简答案是一个字典映射 httphttps 映射到代理 URL,并通过 proxies= 并附带 timeout.
  • 请勿在源代码中暴露凭据:建议使用 HTTP_PROXY, HTTPS_PROXY,并 NO_PROXY 环境变量,并对密码中的特殊字符进行 URL 编码。
  • A requests.Session 会保留代理、标头和 Cookie,并复用 TCP 连接,这对任何多调用工作流来说都是正确的默认设置。
  • 生产环境的轮换机制将“2 的幂”选项与断路器以及 HTTPAdapter Retry 可应对 429 和 5xx 状态码的策略。
  • 对于 SOCKS5,请安装 requests[socks] 并使用 socks5h:// ,以便 DNS 通过代理解析,而不是在本地泄露。

相关的 WebScrapingAPI 资源

常见问题

Python Requests 是否原生支持 SOCKS5 代理?

不支持。基础 requests 安装仅提供 HTTP 和 HTTPS 支持。运行 pip install "requests[socks]" 以引入 PySocks,然后使用 socks5:// 或(更推荐) socks5h:// URL proxies 字典中。这是实现 SOCKS 支持最简洁的方式。

为什么我的代理请求在 DNS 查询时仍会泄露真实 IP?

因为 socks5:// 方案会指示 PySocks 在建立隧道连接前先在本地解析主机名。请切换至 socks5h://,其中尾部的 h 表示远程主机名解析,因此 DNS 查询将通过 SOCKS 服务器传输。这对于 Tor 或任何 DNS 解析器不可信或会被记录的威胁模型尤为重要。

如何对包含 @、: 或 % 字符的代理密码进行 URL 编码?

使用 urllib.parse.quote 标准库中的 quote("p@ss:w/rd%1") 将变为 p%40ss%3Aw%2Frd%251。将编码后的值嵌入 http://user:encoded_pwd@host:port中。如果不进行编码,这些字符会提前终止用户信息段,即使密码在技术上正确,你仍会看到 407 代理身份验证所需(Proxy Authentication Required)的错误。

如何让 Python Requests 跳过本地主机或内部域名的代理?

NO_PROXY 为以逗号分隔的主机或域名后缀列表,例如 NO_PROXY="localhost,127.0.0.1,.internal,.svc.cluster.local"。在 POSIX 系统上,Requests 支持大小写不敏感的变体。若需按调用进行覆盖,请传递 proxies={"http": None, "https": None} 来绕过任何会话级代理。

何时应从自建轮换代理池切换到托管式爬取 API?

当运营成本超过账单金额时。具体触发条件:封禁处理的成本高于重试成本、您需要在多个国家/地区使用民用IP、目标网站大量使用JavaScript,或者您每周需要花费数小时以上的时间来调试代理池。在这些情况之下,一个配备重试机制和断路器的小型自建代理池通常已足够。

结论

掌握如何在 Python Requests 中使用代理,与其说是掌握某个技巧,不如说是掌握分层设计:一个干净的 proxies 字典作为起点,使用环境变量存储凭证以避免机密信息进入 Git, Session 用于连接复用和Cookie管理, socks5h:// 当 DNS 泄漏成为问题时,以及当单个 IP 不再足够时采用轮换加重试策略。将“2 的幂”级别的 IP 选择与断路器和 HTTPAdapter Retry 策略,当某个代理不可用或目标返回 429 错误时,您的爬虫将立即停止崩溃。

每个团队最终都会面临这样的临界点:运行代理池的成本超过了数据本身的价值。如果你的目标站点防护严密、受地理限制或采用 JavaScript 渲染,像 WebScrapingAPI Scraper API 这样的托管方案能通过单一接口处理请求层、IP 轮换及解锁问题,这样你只需保留已编写的解析代码,并替换掉抓取步骤即可。 请参考上述清单进行决策;若符合三项或以上条件,从成本效益角度看,采用托管基础设施比再进行一轮代理池维护更为划算。无论选择哪种方案,本指南中的模式都应能确保您的 requests基于的代码从原型到生产环境始终保持良好状态。

关于作者
Ștefan Răcilă, 全栈开发工程师 @ WebScrapingAPI
Ștefan Răcilă全栈开发工程师

Stefan Racila 是 WebScrapingAPI 的 DevOps 及全栈工程师,负责开发产品功能并维护确保平台稳定运行的基础设施。

开始构建

准备好扩展您的数据收集规模了吗?

加入2,000多家企业,使用WebScrapingAPI在无需任何基础设施开销的情况下,以企业级规模提取网络数据。