简而言之:本指南将详细介绍如何在 Python Requests 中端到端地使用代理:包括一个可正常工作的proxies字典、需认证的 URL、环境变量、Session服务器复用、无DNS泄漏的SOCKS5协议,以及包含重试机制和断路器的轮询池。读完本文,您将明白在何种情况下,托管API比自建代理池更具价值。
引言
如果你曾发布过一个在本地运行正常的爬虫,却在生产环境中开始返回 403、429 错误或无提示超时,那么你已经明白代理存在的意义。掌握如何在 Python Requests 中使用代理,将决定你的脚本是仅在笔记本电脑上运行一次,还是能跨越数千个页面,经受住速率限制、地理封锁和 IP 封禁的考验。
最简单的 Python Requests 代理配置,是一个将 http 和 https 映射到代理 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=json 和 https://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 未授权 |
用户名或密码错误 |
轮换凭据并使用以下内容进行验证 |
|
403 禁止访问 |
目标网站屏蔽了代理 IP |
切换到其他代理或更改地理位置 |
先检查代理,再检查目标。
使用 requests.Session 重用设置以处理 Cookie 和连接池
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 中使用代理的最简答案是一个字典映射
http和https映射到代理 URL,并通过proxies=并附带timeout. - 请勿在源代码中暴露凭据:建议使用
HTTP_PROXY,HTTPS_PROXY,并NO_PROXY环境变量,并对密码中的特殊字符进行 URL 编码。 - A
requests.Session会保留代理、标头和 Cookie,并复用 TCP 连接,这对任何多调用工作流来说都是正确的默认设置。 - 生产环境的轮换机制将“2 的幂”选项与断路器以及
HTTPAdapterRetry可应对 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基于的代码从原型到生产环境始终保持良好状态。




