结论先说: 用 Python 的 requests 走代理,就是传一个同时包含 "http" 和 "https" 两个键的 proxies 字典:requests.get(url, proxies={"http": proxy, "https": proxy}),其中 proxy 写成 http://USERNAME:PASSWORD@HOST:PORT。漏掉 "https" 键,所有 https:// 请求都会悄悄绕开代理。我们实测还发现两个容易踩的坑:系统代理设置会覆盖 session.proxies;Session 会复用同一条连接,轮换代理在这些请求之间没法换 IP。
安装
pip install requests
pip install "requests[socks]" # 只有用 SOCKS5 代理时才需要通过代理发送请求
import requests
proxy = "http://USERNAME:PASSWORD@HOST:PORT"
proxies = {"http": proxy, "https": proxy}
response = requests.get("https://ipinfo.io/json", proxies=proxies, timeout=15)
response.raise_for_status()
print(response.json())运行后会输出一个字典,包含出口 ip、city、country 和 org,也就是网站看到的信息。一定要设置 timeout,否则代理没有响应时,请求可能一直等下去。
为什么两个键都要写

字典里的键对应的是你请求的网址的协议,而不是代理的类型。我们测试时,用 proxies={"http": proxy} 请求 https:// 网址,页面正常返回,但测试代理的日志里什么都没有:请求直接从我们自己的 IP 发出去了。两个键都要写,指向同一个代理。
密码里有特殊字符
账号密码写在代理地址里,所以 @、:、/、# 这些字符必须转义,否则地址会在错误的位置被拆开。交给 Python 处理:
from urllib.parse import quote
import requests
username = "USERNAME"
password = quote("PASSWORD", safe="") # encodes @ : / # and other special characters
proxy = f"http://{username}:{password}@HOST:PORT"
session = requests.Session()
session.proxies = {"http": proxy, "https": proxy}
for _ in range(3):
print(session.get("https://ipinfo.io/ip", timeout=15).text.strip())我们用密码 p@ss:word 测试,quote 把它转成了 p%40ss%3Aword。在照搬这种 Session 写法之前,请先看下面两节。
坑一:系统代理会覆盖 session.proxies

requests 还会读取环境里的代理:HTTPS_PROXY、HTTP_PROXY、NO_PROXY 环境变量,以及 macOS 和 Windows 的系统代理设置,而且它们的优先级高于 session.proxies。我们的测试电脑开着系统代理,上面的 Session 示例完全没有经过测试代理,也没有任何报错。把 HTTPS_PROXY 指向一个关闭的端口后,同一个 Session 直接报错;而在请求里直接传 proxies= 的写法仍然用的是正确的代理。
不管电脑怎么设置,都让 Session 走你的代理,有两种办法:
session.get(url, proxies=proxies, timeout=15) # pass proxies= on every request
session.trust_env = False # or ignore environment and system proxiestrust_env = False 还会让 Session 不再读取 .netrc 里的账号和 REQUESTS_CA_BUNDLE 设置。如果你依赖这两项,就用第一种写法。
坑二:Session 复用一条连接,也就是一个 IP

访问 https:// 网站时,requests 先让代理建一条隧道,再在隧道里发请求。Session 会保持并复用这条隧道:测试代理记录到,三次 session.get() 只建了一条隧道,三次普通的 requests.get() 建了三条。隧道里传的是加密数据,代理分不清哪里是一个请求,所以轮换代理只有在新连接建立时才能换 IP。
- 想要每次请求换 IP(轮换路由):不用 Session,直接
requests.get();或者设置session.headers["Connection"] = "close",让每次请求单独建隧道(我们测试时是三条隧道)。 - 想让整个任务保持一个 IP(比如登录后再访问几个页面):用粘性路由。复用连接有帮助,但粘性会话即使连接重建也能保持同一个 IP。
两种会话的区别见轮换会话和粘性会话的区别。
SOCKS5 代理
import requests
proxy = "socks5h://USERNAME:PASSWORD@HOST:PORT"
proxies = {"http": proxy, "https": proxy}
print(requests.get("https://ipinfo.io/ip", proxies=proxies, timeout=15).text.strip())需要先安装 requests[socks]。用 socks5h://:我们的测试代理收到的是域名,DNS 查询在代理那一端完成;用 socks5:// 时,你的电脑会先自己查出地址。什么时候值得用 SOCKS5,见 SOCKS5和HTTP代理的区别。
处理代理报错
import requests
proxy = "http://USERNAME:PASSWORD@HOST:PORT"
proxies = {"http": proxy, "https": proxy}
try:
response = requests.get("https://ipinfo.io/json", proxies=proxies, timeout=(5, 20))
response.raise_for_status()
print(response.json()["ip"])
except requests.exceptions.ProxyError as error:
print("Proxy refused or unreachable:", error)
except requests.exceptions.ConnectTimeout:
print("Timed out connecting to the proxy")
except requests.exceptions.HTTPError as error:
print("The website answered with an error:", error.response.status_code)timeout=(5, 20) 表示连接最多等 5 秒,读取最多等 20 秒。账号密码错误和代理连不上都会报 ProxyError,具体原因看后面的信息。密码错误时,我们得到的是:
ProxyError('Unable to connect to proxy', OSError('Tunnel connection failed: 407 Proxy Authentication Required'))端口没有开放时:
ProxyError('Unable to connect to proxy', NewConnectionError("… Failed to establish a new connection: [Errno 61] Connection refused"))407 是代理拒绝了你的账号密码,排查方法见代理服务器是什么里的连接信息说明和新手入门。
httpx 和 aiohttp
另外两个常用的 HTTP 库,同样用测试代理跑通了。
httpx 用一个 proxy 参数(httpx 0.28 已经去掉了旧的 proxies 参数):
import httpx
proxy = "http://USERNAME:PASSWORD@HOST:PORT"
with httpx.Client(proxy=proxy, timeout=15) as client:
print(client.get("https://ipinfo.io/ip").text.strip())aiohttp 在每个请求里传代理。aiohttp 3.14 中 proxy_auth 和 BasicAuth 会提示已弃用,改用请求头传账号密码:
import asyncio
import aiohttp
PROXY = "http://HOST:PORT"
PROXY_HEADERS = {"Proxy-Authorization": aiohttp.encode_basic_auth("USERNAME", "PASSWORD")}
async def main():
async with aiohttp.ClientSession() as session:
async with session.get("https://ipinfo.io/ip", proxy=PROXY, proxy_headers=PROXY_HEADERS) as response:
print((await response.text()).strip())
asyncio.run(main())在 Python 中使用 NanoIP 代理
- 动态住宅和移动代理:
proxy = "https://USERNAME:PASSWORD@gateway.nanoip.net:1111",账号密码用 Dynamic Routes 里生成的路由。生成路由时选择轮换或粘性,并注意上面的坑二。大量页面的采集任务,见数据采集。 - 静态 ISP 代理:
http://USERNAME:PASSWORD@HOST:PORT,用这个 IP 自己的主机和端口;也可以用同一组账号写成socks5h://。IP 在整个套餐期内不变。
NanoIP 只用用户名密码认证,脚本放在任何服务器或电脑上都能直接运行,不需要设置 IP 白名单。
常见问题
重定向也会走代理吗?
会。跳转后的请求使用和第一个请求相同的代理设置。
怎么让每个请求用不同的代理?
每次调用时传入不同的 proxies 字典。这也是最可靠的写法,因为请求里的参数优先级最高。
为什么设置了代理却没生效?
通常是这三种情况之一:漏了 "https" 键;session.proxies 被环境变量或系统代理覆盖;目标网站在 NO_PROXY 里。在请求里直接传 proxies=,这三种情况都能排除。