在爬虫、数据采集和多店铺运营中,代理 IP 是绕过目标站频率限制和地理封锁的常用手段。但直接给 requests.get() 加一个 proxies 参数只是第一步,真正影响稳定性和效率的是代理轮换、Session 复用、超时重试、并发控制和请求指纹。
本文整理一套可直接落地的 Python 代理接入与网络优化方案,适用于 requests、Scrapy 和异步爬虫。
一、最小接入:requests 如何正确配置代理
1.1 基础写法
python
import requests
proxies = {
"http": "http://user:password@proxy.example.com:8080",
"https": "http://user:password@proxy.example.com:8080",
}
resp = requests.get(
"https://target-site.com/api",
proxies=proxies,
timeout=(5, 20), # (connect_timeout, read_timeout)
headers={"User-Agent": "Mozilla/5.0"},
)
print(resp.status_code)
注意:访问 https:// 目标时,代理地址通常仍写成 http://(代理通过 CONNECT 建立隧道)。如果代理本身支持 HTTPS 入口,才写成 https://。
1.2 特殊字符密码
如果密码包含 @、:、# 等特殊字符,直接拼 URL 会解析出错。建议用 urllib.parse.quote 编码:
python
from urllib.parse import quote
user = "myuser"
password = "pa@ss:123"
proxy_url = f"http://{user}:{quote(password, safe='')}@proxy.example.com:8080"
二、Session 复用:减少握手开销
每次请求都新建 TCP/TLS 连接会显著增加延迟。使用 requests.Session() 可以复用连接池:
python
import requests
from requests.adapters import HTTPAdapter
session = requests.Session()
session.proxies = {
"http": "http://user:password@proxy.example.com:8080",
"https": "http://user:password@proxy.example.com:8080",
}
# 限制连接池大小和重试次数
session.mount("http://", HTTPAdapter(pool_connections=10, pool_maxsize=20))
session.mount("https://", HTTPAdapter(pool_connections=10, pool_maxsize=20))
for i in range(100):
try:
resp = session.get(
f"https://target-site.com/items/{i}",
timeout=(5, 20),
headers={"User-Agent": "Mozilla/5.0"},
)
print(i, resp.status_code)
except requests.exceptions.RequestException as e:
print(i, type(e).__name__, e)
对于代理池,建议为每个代理地址维护一个 Session,避免不同认证/出口混在一起导致连接复用失败。
三、代理轮换:避免单节点被封
3.1 简单轮询
python
from itertools import cycle
import requests
proxy_list = [
"http://user:pass@proxy-a.example.com:8080",
"http://user:pass@proxy-b.example.com:8080",
"http://user:pass@proxy-c.example.com:8080",
]
proxy_iter = cycle(proxy_list)
for i in range(100):
proxy = next(proxy_iter)
try:
resp = requests.get(
"https://target-site.com/api",
proxies={"http": proxy, "https": proxy},
timeout=(5, 20),
)
except requests.exceptions.RequestException:
continue
3.2 按失败率动态剔除
更稳健的做法是记录每个代理的成功率和延迟,优先使用表现好的节点。核心思路:维护一个 ProxyStats 对象,每次请求后更新 total/ok/latencies,选择时按 success_rate 和 p95 综合评分排序,避免一直用已经失败的节点。
python
from dataclasses import dataclass, field
from collections import deque
@dataclass
class ProxyStats:
url: str
total: int = 0
ok: int = 0
latencies: deque = field(default_factory=lambda: deque(maxlen=50))
@property
def success_rate(self) -> float:
return self.ok / self.total if self.total else 0.0
选择时按 success_rate * 0.7 + (1 / p95) * 0.3 综合评分排序即可。
四、超时与重试:不要把所有失败都重试
不是所有异常都值得重试。建议只重试连接超时、读取超时和 5xx,403/407/429 应先分析原因:
python
from functools import wraps
import requests
def fetch_with_retry(session, url, max_retries=3, backoff=1.0):
for attempt in range(max_retries + 1):
try:
resp = session.get(url, timeout=(5, 20))
if resp.status_code >= 500:
if attempt == max_retries:
return resp
time.sleep(backoff * (2 ** attempt))
continue
return resp
except (requests.exceptions.ConnectTimeout,
requests.exceptions.ReadTimeout) as e:
if attempt == max_retries:
raise
time.sleep(backoff * (2 ** attempt))
except (requests.exceptions.ProxyError,
requests.exceptions.SSLError) as e:
# 代理认证或 TLS 错误,重试通常无效
raise
注意:重试前最好换代理。同一节点重复请求 429,会加速被封。
五、并发控制:aiohttp 与异步请求
对于 I/O 密集型爬虫,异步能显著提升吞吐:
python
import asyncio
import aiohttp
async def fetch(session, url, proxy):
async with session.get(url, proxy=proxy, timeout=aiohttp.ClientTimeout(total=20)) as resp:
return await resp.text(), resp.status
async def main():
urls = [f"https://target-site.com/items/{i}" for i in range(100)]
proxy = "http://user:password@proxy.example.com:8080"
connector = aiohttp.TCPConnector(limit=20, limit_per_host=5)
async with aiohttp.ClientSession(connector=connector) as session:
tasks = [fetch(session, url, proxy) for url in urls]
results = await asyncio.gather(*tasks, return_exceptions=True)
return results
asyncio.run(main())
关键点:limit 控制全局连接数,limit_per_host 控制单目标站连接数,避免触发目标站限流。并发不是越大越好,先压测到稳定阈值;异步场景下也要做代理轮换和失败统计。
六、请求指纹:降低被识别概率
代理只是绕过 IP 限制的一部分。如果请求头、Cookie、TLS 指纹或行为模式一致,仍然容易被识别。建议:
| 维度 | 建议 |
|---|---|
| User-Agent | 准备 5--10 个常见浏览器 UA,按请求或会话轮换 |
| Accept/Language | 与 UA 所在地区匹配 |
| Cookie/Session | 不同账号/会话使用不同 Cookie 池 |
| 请求间隔 | 加入随机抖动,如 time.sleep(random.uniform(0.5, 2.5)) |
| 并发节奏 | 避免短时间内集中请求同一接口 |
| 请求头顺序 | 尽量贴近真实浏览器,必要时使用真实浏览器驱动 |
七、日志与监控:把问题拆清楚
建议在日志中记录以下字段:
| 字段 | 用途 |
|---|---|
target_url |
判断是否只有某个目标失败 |
proxy_url |
定位问题节点 |
status_code |
区分 403/407/429/5xx |
exception_type |
区分 ConnectTimeout / ReadTimeout / ProxyError |
latency_ms |
判断延迟是否异常 |
retry_count |
评估重试策略是否有效 |
body_preview |
快速判断返回的是正常页还是校验页 |
一个最小日志模板:
python
import logging
import time
logger = logging.getLogger("crawler")
def log_request(url, proxy, start, resp=None, exc=None):
latency = (time.perf_counter() - start) * 1000
extra = {"target": url, "proxy": proxy, "latency_ms": round(latency, 2)}
if exc:
extra["exception"] = type(exc).__name__
logger.warning("req_fail", extra=extra)
else:
extra["status"] = resp.status_code
logger.info("req_ok", extra=extra)
八、常见坑
- 代理地址协议写错 :访问 HTTPS 目标时,代理地址通常仍是
http://,不是https://。 - 密码特殊字符未编码 :含
@、:的密码不编码会导致 URL 解析失败。 - 不复用 Session:每个请求新建连接会显著增加 P95 延迟。
- 重试 403/429:直接重试会加速被封,应先降速或换代理。
- 只换 IP 不换指纹:UA、Cookie、请求节奏一致仍会被识别。
- 并发过高:代理网关和节点都有容量上限,超过后成功率会断崖式下降。
九、总结
Python 爬虫接入代理 IP,核心不是"能不能挂上代理",而是能否把代理稳定地放进工程闭环:
- Session 复用降低握手开销;
- 代理轮换 + 失败统计避免单节点失效拖垮整体;
- 分层超时 + 选择性重试减少无效请求;
- 异步并发控制提升吞吐但不突破代理容量;
- 请求指纹管理降低被识别概率;
- 结构化日志让问题可追踪。
把这些环节串起来,才能把代理从"能跑"变成"稳定跑、可监控、可优化"。如果你使用 IPdodo 的跨境专线或静态住宅 IP,可以把同样的接入逻辑套用到其提供的入口上,结合自身业务目标站做压测和调优。