周一至周五 09:00 - 18:00(UTC+08:00) 
©2026 NST LABS TECH LTD. 保留所有权利。 如何在Python中使用和轮换代理服务器(2026)Kai WatanabeScraping Infrastructure Evangelist
如何在Python中使用和轮换代理服务器 (2026)
TL;DR
requests 通过一个普通的字典路由流量经过代理。 将 proxies={"http": "...", "https": "..."} 传递给任何请求,或在 requests.Session() 上设置一次 session.proxies,该会话中的每个调用都继承它。
- 一个代理很快就会失效。 一个单独的 IP 在面对大多数网站的几次请求后会被限速或封锁;通过多个出口 IP 轮换 — 无论是您维护的列表还是提供商管理的网关 — 是使脚本延续运行的关键。
- SOCKS5 需要一个额外的包,而不是不同的方法。 安装
requests[socks](底层使用 PySocks)使同一个 proxies 字典可以与 socks5h:// URL 而不是 http:// 一起使用。
urllib3.util.Retry 将临时的代理故障转化为自动重试。 在 HTTPAdapter 上挂载一个 Retry(total=3, backoff_factor=0.3, status_forcelist=[502, 503, 504]) 策略意味着丢失的连接或 503 会以指数回退的方式重试,而不是立即抛出异常。
aiohttp 使用与 requests 相同的代理字符串格式。 切换到 aiohttp.ClientSession 与 asyncio.gather 让脚本可以同时与一个轮换的代理池进行交互,从而减少批量作业的实际运行时间。
- 轮换网关完全消除了列表维护的问题。 像 Nstproxy 这样的提供商暴露一个固定的主机:端口,并根据会话或编码在代理用户名中的时间值在服务器端轮换出口 IP,因此客户端代码不再需要跟踪哪些 IP 处于活动状态。
介绍:将 Python 连接到代理服务器
一个与代理服务器通信的 Python 脚本只是一个将 requests 或 aiohttp 调用指向中介地址,而不是直接指向目标站点的脚本 — 代理转发请求,响应也通过同一个跳点返回。这一架构上的改变就是为什么代理几乎出现在所有进行持续 HTTP 工作的 Python 项目中的原因:从一个固定 IP 监控竞争对手的公共定价页面通常会在几分钟内触发封锁,但同样的工作如果分布在轮换的 IP 上,则会持续返回 。
200
本指南涵盖了竞争对手教程倾向于跳过的那些工作流部分:验证重试逻辑是否真正从丢失的代理连接中恢复,与 aiohttp 并发运行相同的轮换模式,以及自我维护的 IP 列表与提供商管理的轮换网关之间的区别。下面的每个代码块都是在一个真实的本地代理上执行的,然后才被记录下来 — 请参见内联的验证备注,其中某个步骤依赖于本指南无法提供的凭证。
快速浏览
一旦脚本需要每分钟超过几次请求,维护和健康检查自己的代理列表便成了一个独立项目 — Nstproxy 的 Residential Lite 网关在服务器端接管了这一轮换,因此您的 Python 代码只需指向一个主机:端口。
安装 requests、aiohttp 和 SOCKS 扩展
三个软件包涵盖本指南中的每种模式:requests 用于同步调用,requests[socks] 用于 SOCKS5 支持,以及 aiohttp 用于并发轮换。
pip install requests "requests[socks]" aiohttp
requests[socks] 会引入 PySocks,它实际上实现了 SOCKS4/SOCKS5 握手 — requests 本身只知道如何将连接交给它。跳过这个额外的步骤并直接使用 socks5:// URL 会引发 MissingSchema 或依赖错误,而不是代理连接失败,这是一个常见的混淆点,当一个脚本“找不到”可以工作的 SOCKS 代理时。
为单个请求或整个会话配置代理
在 requests 中,代理是一个将每个 URL 方案映射到代理地址的字典,最干净的重用方式是将该字典在 Session 上设置一次,而不是在每个调用中传递它。
import requests
PROXY_URL = "http://username:password@proxy-host:proxy-port"
proxies = {"http": PROXY_URL, "https": PROXY_URL}
resp = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=15) # 每个请求仅使用一次
session = requests.Session()
session.proxies = proxies
resp = session.get("https://httpbin.org/ip", timeout=15) # 此会话中的每个调用重用它
将凭据嵌入为 username:password@host:port 是 requests、curl 和大多数代理提供商文档中使用的相同身份验证代理格式。如果密码包含 @、: 或其他保留的 URL 字符,请在构建字符串之前通过 urllib.parse.quote() 处理它——未转义的特殊字符是导致 ProxyError 的常见原因,这种错误看起来像是密码错误,但实际上是格式错误的 URL。
这个会话模式,加上一个 Retry 适配器和下一节中给出的轮换池,已在一个本地身份验证代理 (proxy.py,基本身份验证) 上运行,并针对一个被允许的 HTTPS 目标:对同一会话的两次连续调用均返回 200,而故意使用错误凭据的相同调用则失败,并返回预期的 407 Proxy Authentication Required,通过 requests.exceptions.ProxyError 确认。
当 session.trust_env 为 True (默认值)时,requests 还会自动读取 HTTP_PROXY、HTTPS_PROXY 和 NO_PROXY 环境变量,具体行为已在 requests 代理参考 中记录——值得注意的是,因为在 shell 环境中设置的代理会静默覆盖代码中设置的代理,除非关闭 trust_env。
基本实现:重试和轮换 IP 列表
一个 Session 本身并不重试任何操作——这种行为来自在 HTTPAdapter 上挂载 Retry 策略,而在此基础上轮换意味着每次调用之前选择一个不同的代理字符串。
import random
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
PROXY_POOL = [
"http://username:password@proxy-host-1:port",
"http://username:password@proxy-host-2:port",
"http://username:password@proxy-host-3:port",
]
retry = Retry(
total=3,
backoff_factor=0.3,
status_forcelist=[502, 503, 504],
allowed_methods=frozenset(["GET", "POST"]),
)
session = requests.Session()
session.mount("https://", HTTPAdapter(max_retries=retry))
def fetch_with_rotation(url: str) -> requests.Response:
proxy = random.choice(PROXY_POOL)
return session.get(url, proxies={"http": proxy, "https": proxy}, timeout=15)
backoff_factor=0.3 意味着 urllib3 在每次尝试之间睡眠 0.3 * (2 ** (retries - 1)) 秒——大约是 0.3 秒、0.6 秒、1.2 秒——由 backoff_max(默认为 120 秒)限制,根据 urllib3 Retry 参考。status_forcelist 是导致 503 自动重试的原因,而不是立即返回;没有它,Retry 只对连接级别的故障做出反应,而不是 HTTP 状态码。
验证:这个确切的重试会话模式和跨两个本地代理实例的四次请求轮换循环都针对一个被允许的目标实时运行,在每次调用中返回 200,并如预期一样在两个代理端点之间交替。
高级模式:网关轮换、粘性会话和异步
上述轮换模式假定一个 Python 脚本拥有并刷新 PROXY_POOL——由提供商管理的轮换网关通过给客户端提供一个固定地址并将轮换逻辑转移到服务器端来消除这一责任。
Nstproxy 的住宅网关是这种形状的一个文档示例:客户端连接到从仪表板中的 Channel 页面生成的单个 host:port,出口 IP 根据直接编码在代理用户名中的参数进行更改,而不是在应用代码中。下面的代码块是示意,直到提供真实的凭据——GATEWAY_HOST、GATEWAY_PORT、CHANNEL_ID 和 PASSWORD 都来自该 Channel 页面,而不是本指南。
import requests
username = "CHANNEL_ID-residential-country_us-r_10m-s_session123"
password = "PASSWORD"
proxy_url = f"http://{username}:{password}@GATEWAY_HOST:GATEWAY_PORT"
proxies = {"http": proxy_url, "https": proxy_url}
resp = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=15)
r_10m 段设置了一个定时轮换窗口——Nstproxy 记录了一个可配置的范围从 1 到 120 分钟——并且将会话标识符 (s_session123) 更换为一个新值将强制立即新的出口 IP,这就是登录或结账流程中需要在多个步骤中保持相同 IP 的粘性会话模式,但下次运行时需要一个新的 IP。如果将 r_10m 设置为每请求轮换,则在每次调用时返回一个新的 IP,而完全不需要任何会话标记。
Nstproxy 是一个围绕该网关模型构建的代理基础设施提供商,旨在为需要轮换而无需维护列表的 Python 和 Node.js 开发人员提供服务。其 Residential Lite 系列是这类工作的入门点:预付费套餐从 10GB 起,价格从 $1.00/GB 开始,没有订阅自动续订,提供商声称拥有 5000 万以上的住宅 IP,在 200 多个国家和地区,成功率为 99.5%。值得提前了解的选择权衡:Residential Lite 的定价适合那些可以容忍偶尔较慢跳点的脚本,以换取更低的成本,而不适合对延迟敏感的实时使用。
- 基于网关的轮换 — 整个池只有一个
host:port;提供商在服务器端轮换出口 IP,因此 PROXY_POOL 列表维护代码变得不必要。
- 用户名中的国家和会话定位 — 通过编辑用户名字符串设置国家和会话参数,无需每次请求进行单独的 API 调用。
- 同一通道上的 HTTP、HTTPS 和 SOCKS5 — 文档化的网关支持这三种协议,因此之前的
requests[socks] 模式只需更改 URL 方案即可在同一主机上工作。
对于并发轮换,aiohttp 每个请求使用一个 proxy 关键字,而不是一个 proxies 字典,并且 asyncio.gather 将一次运行一批请求:
import asyncio
import random
import aiohttp
PROXY_POOL = [
"http://username:password@proxy-host-1:port",
"http://username:password@proxy-host-2:port",
]
async def fetch(session: aiohttp.ClientSession, url: str) -> int:
proxy = random.choice(PROXY_POOL)
async with session.get(url, proxy=proxy, timeout=aiohttp.ClientTimeout(total=15)) as resp:
await resp.read()
return resp.status
async def fetch_many(urls: list[str]) -> list[int]:
async with aiohttp.ClientSession() as session:
return await asyncio.gather(*(fetch(session, url) for url in urls))
asyncio.gather 调度传递给它的每个协程并同时运行它们,而不是一个接一个地运行,这与 Python asyncio 任务参考 中所记录的行为一致。验证:通过这个确切模式进行六个并发请求,分布在两个代理的本地池中,所有请求返回 200,目标在两个代理端点之间交替。
SOCKS5 使用与 HTTP 相同的代理字典形式,只需使用 socks5h:// 方案(末尾的 h 意味着 DNS 解析通过代理而不是本地进行,这对于隐藏客户端自身网络的目标主机名非常重要):
proxies = {
"http": "socks5h://username:password@proxy-host:1080",
"https": "socks5h://username:password@proxy-host:1080",
}
resp = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=15)
此代码块在本地 SOCKS5 服务器(pproxy,带认证)上实时运行,并返回 200。SOCKS5 被定义为一般用途协议,可以在不检查的情况下转发 TCP 流量,因此同一 SOCKS5 端点可以承载 HTTP、HTTPS 或其他基于 TCP 的流量,而不需要在客户端进行特定于协议的处理 — 请参阅 Nstproxy 自己的 关于 SOCKS5 与 HTTP 代理的解释,了解其在爬取和自动化流量中的具体应用。
请求/aiohttp 层代理的诚实限制
以上所有内容都改变了请求从哪个 IP 发出 — 它并不改变响应内容,这个边界导致了大多数生产脚本中的意外情况。
代理对JavaScript渲染的内容没有影响:requests和aiohttp返回服务器发送的原始HTML,因此一个客户端构建内容的页面需要无头浏览器(Playwright或Selenium,二者都接受相同的代理字符串格式),而不管轮换配置得多好。代理延迟在并发下也会累积——与直接连接相比,住宅IP通常每次跳跃增加几十到几百毫秒,因此在高并发下处理一千个URL的作业,受限于代理响应时间与目标站点响应时间的影响。轮换IP并不覆盖目标站点的服务条款或爬虫指令;在对特定站点指向轮换脚本之前,请检查该站点的条款许可,并保持请求量与该站点的人工用户生成的量成比例。最后,Retry与status_forcelist会自动重试HTTP级别的故障,但它无法区分真正失效的代理与目标站点对该特定IP进行速率限制的情况——生产代码仍然需要将持续失败的代理剔除出轮换,而不是无限重试。
在Python中排查常见代理错误
407 代理身份验证要求, raised as requests.exceptions.ProxyError,意味着代理拒绝了URL中的用户名或密码——通过故意发送错误凭据并观察到相同错误来确认。一个简单的ProxyError伴随Connection refused意味着主机或端口错误,或代理服务宕机;在同一调用上的ConnectTimeout通常意味着防火墙或网络路径静默地丢弃了连接,而不是直接拒绝,这在日志中表现得不同,尽管两者都看起来像"请求从未完成"。通过代理的SSLError几乎总是代理执行了未配置为信任的TLS中断,或socks5h://代理的目标证书与解析器返回的不匹配——切换到socks5://以测试本地DNS解析是否更改故障模式,有助于将其与证书本身区分开来。如果代理反复返回200,并且页面显示"访问被拒绝"或CAPTCHA,而不是抛出HTTP错误,这根本不是连接问题——这是目标网站检测到自动化流量,尽管代理正常工作,而仅靠重试逻辑无法解决。
结论
Python代理设置从每个请求或会话的相同proxies字典开始,之后的一切——重试、轮换、SOCKS5、异步并发——都是在这一模式之上累加的,而不是另一种方法。脚本在自维护列表与托管网关问题上的落脚点主要取决于涉及的请求量多少值得避免IP列表维护。
常见问题
问:我需要在每个requests调用中设置代理,还是可以一次设置?
通过session.proxies = {...}在requests.Session()上设置一次;在该会话对象上做的每个调用都会重用相同的代理和连接池,而无需重复字典。
407 代理身份验证要求意味着代理拒绝了嵌入在代理URL中的用户名或密码——首先检查密码中是否有未转义的特殊字符,因为这些是看起来正确但解析不正确的凭据最常见的原因。
问:我可以使用与aiohttp相同的轮换逻辑,而不是requests吗?
可以——aiohttp.ClientSession.get()接受proxy关键字参数,而不是proxies字典,通过asyncio.gather()并行运行多个这样的调用可以在代理池中轮换,而不是一次请求一次。
问:SOCKS5比HTTP代理在Python脚本中更好吗?
两者并没有绝对的优劣;SOCKS5是协议无关的,可以转发任何TCP流量,而不进行检查,这适合非HTTP协议或通过代理的DNS解析(socks5h://),而HTTP代理更简单,足以处理直接的HTTP/HTTPS请求。
自维护列表需要在自己的代码中源源不断地维护、健康检查和更新IP,而轮换网关(一个固定的主机:端口)将轮换逻辑移至服务器端,代价是依赖于提供商的网关正常工作,而不是自己的列表。
问:使用urllib3.util.Retry添加重试能修复被阻塞或禁止的代理吗?
不——重试能够从暂时性故障中恢复,比如连接重置或502/503/504响应,但被目标网站IP封禁的代理在每次重试时将会持续返回相同的失败,因此生产代码需要单独的逻辑将持续失败的代理剔除出轮换。
Aug. 6th 2026
立即访问住宅、数据中心、IPv6 与 ISP 高质量代理池。