每个Python HTTP库都将代理视为形如scheme://user:pass@host:port的URL;不同之处在于参数名、是每个客户端还是每个请求设置,以及SOCKS5和DNS的处理方式。以下代码片段是当前版本的requests、httpx和aiohttp可用的确切代码——每段代码在本文发布前都通过认证代理运行过——随后是导致大多数“我的代理在Python中不起作用”问题的三个原因:从旧教程复制的错误参数名、缺少SOCKS扩展,以及无论IP如何都会暴露脚本的TLS指纹。

代理URL

http://user:pass@proxy.example.com:8080     # HTTP代理,同时用于http://和https://目标
socks5://user:pass@proxy.example.com:1080   # SOCKS5,DNS本地解析
socks5h://user:pass@proxy.example.com:1080  # SOCKS5,DNS由代理解析(推荐使用)

有两个容易出错的地方。首先,HTTP代理也处理HTTPS目标——客户端发送CONNECT并通过它隧道TLS——因此你几乎不需要https://代理方案;那意味着TLS到代理本身,很少有提供商提供。其次,如果你的用户名包含轮换参数(user-country-de-session-abc123)或密码包含@、:或/,请进行百分号编码(urllib.parse.quote(pw, safe='')),否则URL解析会出错。

requests

import requests

PROXY = "http://user:pass@proxy.example.com:8080"
proxies = {"http": PROXY, "https": PROXY}

# 每个请求
r = requests.get("https://api.ipify.org?format=json", proxies=proxies, timeout=20)
print(r.json())

# 每个会话(重用连接;常规方式)
s = requests.Session()
s.proxies.update(proxies)
r = s.get("https://api.ipify.org?format=json", timeout=20)

字典的键是目标方案:对http://URL的请求使用"http"条目,对https://URL的请求使用"https"条目。除非有理由,否则将两者设置为同一代理。始终传递timeout;否则挂起的代理会让你的脚本永远挂起。

SOCKS5需要额外安装:pip install "requests[socks]"。然后proxies = {"http": "socks5h://…", "https": "socks5h://…"}。如果没有额外安装,你会得到InvalidSchema: Missing dependencies for SOCKS support。

环境变量:requests默认尊重HTTP_PROXY / HTTPS_PROXY / NO_PROXY(trust_env=True)。如果你没有设置代理但神秘地使用了代理,或者设置了但未使用,请检查环境变量。

httpx

import httpx

PROXY = "http://user:pass@proxy.example.com:8080"

# 同步
with httpx.Client(proxy=PROXY, timeout=20) as client:
    r = client.get("https://api.ipify.org?format=json")
    print(r.json())

# 异步
import asyncio
async def main():
    async with httpx.AsyncClient(proxy=PROXY, timeout=20) as client:
        r = await client.get("https://api.ipify.org?format=json")
        print(r.json())
asyncio.run(main())

参数是proxy=(单数)。旧教程显示proxies={"http://": …, "https://": …};该关键字在httpx 0.26中已弃用,并在0.28中移除,现在传递它会引发TypeError: Client.__init__() got an unexpected keyword argument 'proxies'。如果你需要为不同目标使用不同代理,请使用mounts:

mounts = {
    "https://api.example.com": httpx.HTTPTransport(proxy="http://user:pass@dc-proxy:8080"),
    "all://": httpx.HTTPTransport(proxy="http://user:pass@resi-proxy:8080"),
}
client = httpx.Client(mounts=mounts)

SOCKS5:pip install "httpx[socks]",然后proxy="socks5://…"。httpx默认通过SOCKS代理解析主机名。HTTP/2:pip install "httpx[http2]"和httpx.Client(http2=True, …)——这很重要,因为代理浏览器无论如何都在HTTP/2上(参见通过代理的HTTP/2和HTTP/3)。

aiohttp

import aiohttp, asyncio

PROXY = "http://user:pass@proxy.example.com:8080"

async def main():
    async with aiohttp.ClientSession() as session:
        async with session.get("https://api.ipify.org?format=json",
                               proxy=PROXY,
                               timeout=aiohttp.ClientTimeout(total=20)) as r:
            print(await r.json())

asyncio.run(main())

代理是每个请求传递的(或通过自定义连接器设置一次)。凭据放在URL中;单独的proxy_auth=aiohttp.BasicAuth(...)形式在3.x中仍然有效,但自3.14起已弃用,并计划在4.0中移除,因此推荐使用URL形式。aiohttp不读取HTTP_PROXY环境变量,除非你使用trust_env=True创建会话。原生aiohttp仅支持HTTP代理;对于SOCKS,请使用aiohttp-socks包及其ProxyConnector。

轮换代理:会话ID模式

使用回连网关(参见轮换网关的工作原理),你很少需要在Python中轮换——你通过更改用户名来轮换。新的会话ID意味着新的出口IP;重复使用则保持相同:

import secrets, requests

def proxy_for(session_id: str, country: str = "de") -> dict:
    user = f"customer-country-{country}-session-{session_id}"   # 提供商特定格式
    url  = f"http://{user}:PASSWORD@gate.provider.com:7777"
    return {"http": url, "https": url}

# 粘性:多步骤流程使用相同IP
sid = secrets.token_hex(4)
s = requests.Session(); s.proxies.update(proxy_for(sid))
s.get("https://shop.example/login"); s.post("https://shop.example/cart", data=...)

# 轮换:每个项目新IP
for url in urls:
    requests.get(url, proxies=proxy_for(secrets.token_hex(4)), timeout=20)

失败时也轮换:捕获403/429和CAPTCHA标记,并使用新的会话ID和新的cookie jar重试——相同IP和相同cookie意味着同一访客。

任何代理都无法修复的错误:TLS指纹

所有三个库都通过Python的ssl模块(OpenSSL)进行TLS连接。反机器人系统对该握手进行指纹识别,并知道它不是浏览器,无论你如何设置User-Agent。在完美的住宅IP后面,脚本仍然被识别为脚本——机制在TLS指纹识别解释中。对于受保护的目标,切换到模拟客户端;curl_cffi保持类似requests的API:

# pip install curl_cffi
from curl_cffi import requests as cffi_requests

r = cffi_requests.get("https://protected.example/",
                      impersonate="chrome",                 # 复制Chrome的TLS + HTTP/2指纹
                      proxies={"https": "http://user:pass@proxy.example.com:8080"},
                      timeout=20)

然后保持故事其余部分一致——Chrome的User-Agent、匹配的sec-ch-ua客户端提示、与代理国家相符的Accept-Language。对于还需要JavaScript挑战的网站,任何HTTP客户端都不够;使用真实浏览器,如Playwright、Puppeteer和Selenium中的代理。

错误及其含义

你看到含义修复
ProxyError … 407 Proxy Authentication Required凭据错误,或你的IP未列入白名单检查用户名/密码(长轮换字符串!),对特殊字符进行百分号编码,验证白名单
InvalidSchema: Missing dependencies for SOCKS supportrequests缺少socks扩展pip install "requests[socks]"
TypeError: … unexpected keyword argument 'proxies'httpx ≥ 0.28,参数已移除使用proxy=或mounts=
ProxyError … Cannot connect to proxy / ConnectTimeout主机/端口错误、代理宕机或防火墙使用curl -x验证;尝试其他协议
IP回显端点返回真实IP代理未应用(字典键错误、环境变量覆盖、aiohttp中缺少每请求参数)打印session.proxies;检查trust_env;在请求中传递proxy=
仅通过代理时出现SSLError代理正在拦截TLS(MITM)或方案错误为代理使用http://方案;询问提供商是否终止TLS
受保护站点上出现403/CAPTCHA,IP看起来正常TLS/HTTP指纹识别出Python使用curl_cffi的impersonate,或使用真实浏览器
工作一段时间后出现429达到每IP速率限制轮换会话ID;放慢速度;参见轮换指南

一个最小、正确的模板

import requests, secrets
from urllib.parse import quote

GATE, PORT = "gate.provider.com", 7777
USER_BASE, PASS = "customer-country-de", quote("p@ss:word", safe="")

def make_session() -> requests.Session:
    sid = secrets.token_hex(4)
    url = f"http://{USER_BASE}-session-{sid}:{PASS}@{GATE}:{PORT}"
    s = requests.Session()
    s.proxies.update({"http": url, "https": url})
    s.headers.update({"User-Agent": "python-requests"})   # 对宽容目标使用诚实的UA
    return s

s = make_session()
r = s.get("https://api.ipify.org?format=json", timeout=20)
r.raise_for_status()
print(r.json())

验证IP是否变化,然后在此基础上构建。有关完整的预检(泄漏、头部、黑名单),请参见如何测试代理;有关为特定目标选择数据中心还是住宅代理,请参见代理类型比较。

常见问题

如何在Python requests中使用代理?

将proxies={"http": url, "https": url}传递给请求或设置在Session上,其中url是http://user:pass@host:port。添加timeout=。对于SOCKS5,安装requests[socks]并使用socks5h://。

为什么httpx报“unexpected keyword argument proxies”?

因为proxies=在httpx 0.28中被移除了。对于单个代理使用proxy="http://…",对于按目标代理使用mounts=。

如何在aiohttp中设置代理?

将proxy="http://user:pass@host:port"传递给每个请求(session.get(url, proxy=…))。proxy_auth=参数已弃用;将凭据放在URL中。对于SOCKS,使用aiohttp-socks。

如何在Python中轮换代理?

使用回连提供商,在代理用户名中更改会话ID,每个请求或每个流程更改一次——无需IP列表。在403/429时轮换,并将每个新IP与新的cookie jar配对。

为什么我的脚本被阻止,而同一代理上的浏览器没有被阻止?

TLS和HTTP/2指纹识别:Python的ssl模块看起来不像浏览器。使用curl_cffi的impersonate=或自动化真实浏览器。

我应该使用socks5还是socks5h?

socks5h。h使代理解析主机名,防止DNS泄漏,并允许你访问只有代理才能解析的主机。