为什么"一行 proxies 参数"往往不够用?
一行 proxies 参数只解决单次请求的出口问题,实际业务里往往还要处理连接复用、异常重试、多协议兼容三件事。
Requests 默认底层是 urllib3 的连接池。不走 Session,每次调用 requests.get() 都会新建一次 TCP 连接和 TLS 握手。一个网站采集器如果一天跑 10 万次请求,重复握手带来的开销比想象中大。再叠加代理链路,握手时延还会翻倍。
更常见的坑是"配了 proxies 但请求还是走了本地 IP"。原因通常有三种:
proxies字典只写了http,目标站是https,代理配置没匹配上- 环境变量里已经有
HTTP_PROXY,代码里覆盖失败 Session在某个mount之后丢了原有代理配置
理清 7 种集成方法的差异,是写健壮采集脚本的前提。
用 requests.get 传 proxies 参数怎么写?
最基础的方式:把 proxies 传给单次请求方法,key 是协议,value 是代理地址。适合一次性脚本、快速调试。
python
import requests
proxies = {
'http': 'http://user:pass@proxy.example.com:8000',
'https': 'http://user:pass@proxy.example.com:8000',
}
resp = requests.get(
'https://httpbin.org/ip',
proxies=proxies,
timeout=10,
)
print(resp.json())三点提醒:
http和https两个 key 都要写,只写一个另一种请求就会走本地 IP- 代理认证有两种写法,URL 内联
user:pass@或改用HTTPProxyAuth - SOCKS5 代理要单独装
pip install requests[socks],key 写成socks5://或socks5h://(后者让代理解析 DNS)
这种写法的缺点是每次调用都要传一次 proxies,函数多起来就容易漏。
什么时候该改用 Session 级代理?
当同一个代理要被多次请求复用时,改用 Session 级代理。Session 会保持 TCP 连接和 cookies,多次请求走同一个连接池,握手成本摊薄。
python
import requests
session = requests.Session()
session.proxies = {
'http': 'http://proxy.example.com:8000',
'https': 'http://proxy.example.com:8000',
}
session.headers.update({'User-Agent': 'data-collector/1.0'})
for url in url_list:
resp = session.get(url, timeout=10)
process(resp)适配场景:网站采集器长时间跑批、舆情监测里针对同一站点的翻页采集、需要保持登录态的接口调用。一个 Session 内的所有 get / post 都自动带上 proxies,代码量小很多。
注意:session.proxies 是可以被单次请求参数覆盖的。想临时切换代理,直接 session.get(url, proxies=other_proxy) 即可,不会污染 Session 本身的配置。
环境变量方式在什么场景下更省心?
不想改代码就给整个进程加代理时,环境变量最合适。Requests 会自动读取 HTTP_PROXY、HTTPS_PROXY、NO_PROXY 三个环境变量。
Linux/macOS:
bash
export HTTP_PROXY=http://proxy.example.com:8000
export HTTPS_PROXY=http://proxy.example.com:8000
export NO_PROXY=localhost,127.0.0.1,.internal.com
python collector.pyWindows PowerShell:
powershell
$env:HTTP_PROXY = "http://proxy.example.com:8000"
$env:HTTPS_PROXY = "http://proxy.example.com:8000"
python collector.py三类典型场景:
- 临时调试:本地跑一次采集验证代理是否可用,不想动源码
- 容器化部署:Docker 或 Kubernetes 里通过 env 注入,采集镜像本身无代理配置
- CI 环境:流水线里的 job 统一走出口代理
如果既设了环境变量又传了 proxies 参数,参数优先级更高。想让某次请求绕过代理,除了改 NO_PROXY,也可以在代码里 session.get(url, proxies={}) 强制清空。
用 HTTPAdapter 怎么统一管理连接池、超时和重试?
把连接池大小、重试策略、代理配置绑到一个 HTTPAdapter 上,一次配好全局生效。这是长跑批采集脚本最推荐的写法。
python
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
retry = Retry(
total=5, # 总重试 5 次
backoff_factor=0.5, # 指数退避 0.5s, 1s, 2s...
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=['GET', 'POST'],
)
adapter = HTTPAdapter(
pool_connections=20, # 连接池数量
pool_maxsize=50, # 每池最大连接
max_retries=retry,
)
session = requests.Session()
session.mount('http://', adapter)
session.mount('https://', adapter)
session.proxies = {
'http': 'http://proxy.example.com:8000',
'https': 'http://proxy.example.com:8000',
}
resp = session.get('https://target.com/api', timeout=(3, 10))三个要点:
timeout=(3, 10)是(连接超时, 读超时)两段式,比单个数字更精确Retry的backoff_factor决定退避节奏,0.5 起步对访问频率控制类的限制比较友好pool_maxsize别调太大,代理服务端也有并发上限
APP 大数据分析里跑周期性批量采集,这套写法能在代理偶发抖动、目标站临时返回 502 时自动恢复,不用外层再包一层 try 循环。
代理池模式怎么每次请求随机切换 IP?
从代理列表里随机取一个,每次请求前动态注入 proxies。适合需要频繁换出口 IP 的采集场景。
python
import random
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
PROXY_POOL = [
'http://p1.example.com:8000',
'http://p2.example.com:8000',
'http://p3.example.com:8000',
# ...
]
def get_proxy():
p = random.choice(PROXY_POOL)
return {'http': p, 'https': p}
session = requests.Session()
adapter = HTTPAdapter(max_retries=Retry(total=3, backoff_factor=0.3))
session.mount('http://', adapter)
session.mount('https://', adapter)
for url in url_list:
try:
resp = session.get(url, proxies=get_proxy(), timeout=10)
process(resp)
except requests.exceptions.RequestException as e:
logger.warning(f'proxy failed on {url}: {e}')配套要处理三件事:
- 可用性检查:定期用一个校验接口测试池里每个代理的连通性,把挂掉的踢出去
- 失败重投:某个代理连续失败几次自动降权,别一直用坏的
- 协议匹配:目标是 HTTPS 就别塞只支持 HTTP 的代理进池
自维护代理池的隐性成本是"运维负担"。舆情监测团队如果任务量不算特别大,评估下自建池的 IP 采买 + 检活 + 淘汰的时间投入,往往会发现直接用现成隧道代理更划算。
隧道代理(Tunnel Proxy)在 Requests 里怎么接?
隧道代理只暴露一个固定入口,服务端自动为每次请求分配不同出口 IP,客户端代码只需要设一次 proxies。
python
import requests
# 隧道代理典型配置:固定域名+端口,服务端自动换 IP
tunnel = {
'http': 'http://user:pass@tunnel.example.com:8000',
'https': 'http://user:pass@tunnel.example.com:8000',
}
session = requests.Session()
session.proxies = tunnel
for url in url_list:
resp = session.get(url, timeout=10)
# 每个请求由隧道服务端换 IP,客户端无需感知
process(resp)与自建代理池相比,隧道代理省掉了池维护、检活、淘汰这套流程。适配场景:
- 舆情监测里对同一批站点持续采集,出口 IP 不需要客户端主动调度
- 网站采集器跑分布式任务,隧道入口固定,各节点无需同步代理列表
- 团队里没有专职做代理运维的角色
隧道代理有个使用细节:同一 TCP 连接内的多次请求可能会复用同一个出口 IP。想每个请求都换 IP,要么关闭 Keep-Alive session.headers['Connection'] = 'close',要么按服务端说明配置切换头。
urllib3 ProxyManager 什么时候值得直接调?
需要精细控制 TLS、SNI、SOCKS5 UDP 等底层协议时,直接用 urllib3 的 ProxyManager 更合适。Requests 是 urllib3 的高层封装,绝大多数场景用 Requests 就够了。
python
import urllib3
http = urllib3.ProxyManager(
'http://proxy.example.com:8000',
proxy_headers={'Proxy-Authorization': 'Basic ...'},
cert_reqs='CERT_REQUIRED',
ca_certs='/path/to/ca-bundle.crt',
)
resp = http.request('GET', 'https://target.com/api', timeout=10.0)
print(resp.data)三种典型情况值得直接下沉到 urllib3:
- 需要自定义 TLS 上下文(客户端证书、SNI 指定 host、TLS 版本锁定)
- 走 SOCKS5 的 UDP 数据(Requests 侧支持不完整)
- 在 asyncio 之外做非常轻量的直连请求,不想引入 Requests 整套依赖
边界要说清:这条路复杂度陡升,异常处理、重试、cookies 都得自己写。除非有明确的底层控制需求,否则一线业务代码还是留在 Requests 层。
7 种方法怎么选?一张对比表看清适配场景
先看粒度、复用、复杂度、典型场景四个维度:
| 方法 | 配置粒度 | 连接复用 | 复杂度 | 典型场景 |
|---|---|---|---|---|
| 直接传 proxies | 单请求 | 无 | 极低 | 一次性脚本、快速验证 |
| Session 级代理 | 会话 | 有 | 低 | 长时间采集、翻页任务 |
| 环境变量 | 进程 | 视代码 | 极低 | 容器部署、CI 环境 |
| HTTPAdapter + Retry | 会话 | 有 + 池化 | 中 | 长跑批、429/5xx 自动恢复 |
| 代理池随机 | 单请求 + 动态 | 部分 | 中高 | 多站点分布式采集 |
| 隧道代理 | 会话 | 有 | 低 | 不想维护代理池 |
| urllib3 ProxyManager | 请求级 | 手动控 | 高 | TLS/SOCKS5 底层控制 |
按业务量级快速匹配:
- 日请求量 < 1 万、单点采集:Session 级代理 或 环境变量
- 日请求量 1 万 - 100 万、多站点:HTTPAdapter + Retry + 隧道代理
- 日请求量 > 100 万、需精细路由:HTTPAdapter + 代理池 或 隧道代理 + 分片调度
- 底层协议有特殊要求:urllib3 ProxyManager 兜底
用代理 IP 时最容易踩的 5 个坑是什么?
这 5 个坑几乎每个爬虫工程师都踩过,写下来当自检清单。
- proxies 字典只写 http,https 请求走本地 IP。修法:http 和 https 都写,指向同一代理即可
- 没设 timeout,遇到坏代理会挂住整个进程。修法:
timeout=(连接超时, 读超时),两段式更精确 - Session.mount 之后代理配置丢失。修法:先
session.proxies = {...},再mount(adapter);顺序颠倒会导致 adapter 覆盖代理 - 环境变量和代码同时设置,优先级理解错。规则:代码里的
proxies参数 >session.proxies> 环境变量。想强制关掉代理,proxies={}空字典就行 - HTTPS 走代理时忽略证书链。有些代理需要额外的 CA bundle,用
verify='/path/to/ca.crt'显式指定;关掉verify=False只在测试环境用,生产严禁
FAQ
Q:Requests 的 proxies 参数只写 http 可以吗,为什么 https 请求还是失败?
不可以。proxies 字典按目标 URL 的协议匹配,只写 http key 时,所有 HTTPS 请求都会跳过代理直接走本地网络。正确写法是 http 和 https 两个 key 都指向代理地址,即使目标只有一种协议也建议都写,避免后续新增请求时漏掉。
Q:Session 级代理和请求级代理有什么区别,什么时候用哪种?
区别在作用域和复用性。请求级 proxies 只对当次调用生效,每次都要重复传;Session 级 session.proxies 对该 Session 内所有请求生效,同时享受 TCP 连接复用。规则是:一次性脚本或需要动态切换代理的场景用请求级;长时间批量采集、需要保持登录态或复用连接的场景用 Session 级。两者可以叠加,请求级会临时覆盖 Session 级。
Q:Requests 支持 SOCKS5 代理吗,怎么配?
支持,但要额外装依赖包:pip install requests[socks]。之后 proxies 的 value 写成 socks5://host:port 或 socks5h://host:port。两者区别是前者由客户端本地解析 DNS、后者让代理服务端解析。爬虫场景多数选 socks5h,避免本地 DNS 泄漏访问目标。SOCKS5 相比 HTTP 代理的优势是支持任意 TCP 协议,不局限于 HTTP/HTTPS。
Q:用代理 IP 采集时超时和自动重试怎么处理最优雅?
推荐 HTTPAdapter + Retry 组合。Retry(total=5, backoff_factor=0.5, status_forcelist=[429, 500, 502, 503, 504]) 覆盖了访问频率控制和服务端 5xx 两类临时故障,backoff_factor 决定指数退避节奏。超时用 timeout=(3, 10) 两段式,分别控制连接和读取。这套配置能自动吞掉大部分瞬时抖动,业务代码只需要 try 一层兜底日志。
Q:HTTP_PROXY 环境变量和代码里的 proxies 参数哪个优先级高?
代码里的参数优先级更高。Requests 的解析顺序是:请求级 proxies 参数 → Session 的 session.proxies → 环境变量 → 系统代理设置。想让某次请求强制绕过代理,传 proxies={} 空字典;想让整个 Session 无视环境变量,session.trust_env = False。这个开关在容器里排查代理问题很好用。
Q:隧道代理和自建代理池比,中小团队怎么选?
看两个维度:任务量级和运维投入。自建代理池的隐性成本包括 IP 采买、检活、失败降权、协议匹配、并发调度,长期维护需要专人;隧道代理把这些搬到服务端,客户端只管一个入口。日请求量在百万级以下、没有专职代理运维的团队,隧道代理综合成本更低。规模大到出口 IP 需要按业务分片调度时,才有必要考虑自建。