# 如何在 Python requests、httpx 和 aiohttp 中使用代理

每个库只需三行配置，随后各处都使用相同的用户名语法：国家/地区、城市和会话都写在代理 URL 中，因此一个网关就能满足所有任务。


## 代理 URL

每个 Python HTTP 库都以 URL 的形式接收代理。使用 ProxShift 时，凭据和[目标定位参数](https://proxshift.com/zh/docs/username-parameters)都包含在 URL 的用户名部分；主机名指定网关，端口则决定协议。

```text
# 轮换：每次建立新连接时获得一个新的美国住宅出口
http://USER-cc-us:PASS@res.proxshift.com:9000

# 粘性会话：以 "job42" 为标识，将一个柏林住宅出口保持 30 分钟
http://USER-cc-de-city-berlin-sid-job42-ttl-30m:PASS@res.proxshift.com:9000

# 使用远程 DNS 的 SOCKS5（socks5h），参数相同
socks5h://USER-cc-us:PASS@res.proxshift.com:9001
```


## requests

为 `http` 和 `https` 传入同一个 URL；requests 会自动通过 CONNECT 隧道连接 HTTPS 目标。`Session` 会复用连接，因此在连接关闭前始终使用同一个出口；每个粘性任务应使用独立的 `Session`，若希望每次请求都获得新出口，则直接调用 `requests.get`。

```python
import requests

PROXY = "http://USER-cc-us:PASS@res.proxshift.com:9000"
proxies = {"http": PROXY, "https": PROXY}

# 一次请求，一个出口
r = requests.get("https://api.ipify.org", proxies=proxies, timeout=30)
print(r.text)

# 粘性流程：此会话的每次请求都使用同一个出口
sticky = "http://USER-cc-de-city-berlin-sid-cart42-ttl-15m:PASS@res.proxshift.com:9000"
s = requests.Session()
s.proxies = {"http": sticky, "https": sticky}
s.get("https://example.com/product/1", timeout=30)
s.post("https://example.com/cart", json={"id": 1}, timeout=30)
```

> Tip：如需使用 SOCKS5，请安装扩展依赖：`pip install \"requests[socks]\"`，然后使用 `socks5h://`，让 DNS 在出口端解析。


## httpx

httpx 接收单个 `proxy=` 参数（旧版本使用 `proxies=`）。它支持通过 CONNECT 隧道以 HTTP/2 连接目标，并且在异步代码中的用法相同。

```python
import httpx

PROXY = "http://USER-cc-us:PASS@res.proxshift.com:9000"

with httpx.Client(proxy=PROXY, timeout=30) as client:
    print(client.get("https://api.ipify.org").text)

# 异步模式：每个任务使用一个粘性出口
import asyncio

async def fetch(sid: str, url: str) -> str:
    proxy = f"http://USER-cc-us-sid-{sid}-ttl-10m:PASS@res.proxshift.com:9000"
    async with httpx.AsyncClient(proxy=proxy, timeout=30) as client:
        return (await client.get(url)).text

asyncio.run(fetch("task1", "https://api.ipify.org"))
```


## aiohttp

aiohttp 可按请求或按会话接收代理，并原生支持 HTTP 代理；SOCKS5 则需要 `aiohttp-socks` 连接器。

```python
import aiohttp, asyncio

PROXY = "http://USER-cc-us:PASS@res.proxshift.com:9000"

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

asyncio.run(main())
```


## 轮换、连接保持与重试

- 新的 TCP 连接意味着新的出口。不使用 `Session` 的 `requests.get` 每次调用都会新建连接；`Session` 或 httpx `Client` 会保持连接，因此在连接池回收连接前都会沿用同一出口。
- 在会话内更改 `-sid-` 值可强制使用新出口；发送 `Connection: close` 则可让每次请求都使用新出口。
- 遇到 403、429 和连接错误时重试一次：在轮换网关上，重试会表现为另一个访问者。粘性流程进行期间，切勿跨出口重试。
- 根据出口类型设置超时：住宅代理建议连接超时 15 秒、读取超时 45 秒；移动代理应设置得更长。


## 节省流量

住宅代理的双向流量都按 GB 计费。请请求压缩响应（这些库默认启用 `Accept-Encoding: gzip, br`），优先使用 JSON 接口而非 HTML；除非图片、字体或媒体本身就是所需数据，否则不要下载。一个 350 KB 的页面成本很低，而加载资源后达到 4 MB 的页面成本并不低。


## 问题

**为什么 requests 总是获得相同的 IP？**

Session 会复用 TCP 连接，而出口是按连接选择的。若要获得新出口，请直接调用 requests.get、发送 Connection: close，或更改 -sid- 的值。

**访问 HTTPS 网站需要使用不同的代理吗？**

不需要。同一个 http:// 代理 URL 会通过 CONNECT 隧道处理 HTTPS 目标；TLS 在你的代码与目标网站之间保持端到端加密。

**同一套代码可以用于移动代理吗？**

可以：将 res.proxshift.com 替换为 mob.proxshift.com 即可。端口、凭据和参数完全相同。

来源：https://proxshift.com/zh/guides/python-requests-httpx
