在爬虫、数据采集和多店铺运营中代理 IP 是绕过目标站频率限制和地理封锁的常用手段。但直接给requests.get()加一个proxies参数只是第一步真正影响稳定性和效率的是代理轮换、Session 复用、超时重试、并发控制和请求指纹。本文整理一套可直接落地的 Python 代理接入与网络优化方案适用于 requests、Scrapy 和异步爬虫。一、最小接入requests 如何正确配置代理1.1 基础写法import requests proxies { http: http://user:passwordproxy.example.com:8080, https: http://user:passwordproxy.example.com:8080, } resp requests.get( https://target-site.com/api, proxiesproxies, 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编码from urllib.parse import quote user myuser password pass:123 proxy_url fhttp://{user}:{quote(password, safe)}proxy.example.com:8080二、Session 复用减少握手开销每次请求都新建 TCP/TLS 连接会显著增加延迟。使用requests.Session()可以复用连接池import requests from requests.adapters import HTTPAdapter session requests.Session() session.proxies { http: http://user:passwordproxy.example.com:8080, https: http://user:passwordproxy.example.com:8080, } # 限制连接池大小和重试次数 session.mount(http://, HTTPAdapter(pool_connections10, pool_maxsize20)) session.mount(https://, HTTPAdapter(pool_connections10, pool_maxsize20)) for i in range(100): try: resp session.get( fhttps://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 简单轮询from itertools import cycle import requests proxy_list [ http://user:passproxy-a.example.com:8080, http://user:passproxy-b.example.com:8080, http://user:passproxy-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: continue3.2 按失败率动态剔除更稳健的做法是记录每个代理的成功率和延迟优先使用表现好的节点。核心思路维护一个ProxyStats对象每次请求后更新total/ok/latencies选择时按success_rate和p95综合评分排序避免一直用已经失败的节点。from dataclasses import dataclass, field from collections import deque dataclass class ProxyStats: url: str total: int 0 ok: int 0 latencies: deque field(default_factorylambda: deque(maxlen50)) 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综合评分排序即可。四、超时与重试不要把所有失败都重试不是所有异常都值得重试。建议只重试连接超时、读取超时和 5xx403/407/429 应先分析原因from functools import wraps import requests def fetch_with_retry(session, url, max_retries3, backoff1.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 密集型爬虫异步能显著提升吞吐import asyncio import aiohttp async def fetch(session, url, proxy): async with session.get(url, proxyproxy, timeoutaiohttp.ClientTimeout(total20)) as resp: return await resp.text(), resp.status async def main(): urls [fhttps://target-site.com/items/{i} for i in range(100)] proxy http://user:passwordproxy.example.com:8080 connector aiohttp.TCPConnector(limit20, limit_per_host5) async with aiohttp.ClientSession(connectorconnector) as session: tasks [fetch(session, url, proxy) for url in urls] results await asyncio.gather(*tasks, return_exceptionsTrue) 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/5xxexception_type区分 ConnectTimeout / ReadTimeout / ProxyErrorlatency_ms判断延迟是否异常retry_count评估重试策略是否有效body_preview快速判断返回的是正常页还是校验页一个最小日志模板import logging import time logger logging.getLogger(crawler) def log_request(url, proxy, start, respNone, excNone): 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, extraextra) else: extra[status] resp.status_code logger.info(req_ok, extraextra)八、常见坑代理地址协议写错访问 HTTPS 目标时代理地址通常仍是http://不是https://。密码特殊字符未编码含、:的密码不编码会导致 URL 解析失败。不复用 Session每个请求新建连接会显著增加 P95 延迟。重试 403/429直接重试会加速被封应先降速或换代理。只换 IP 不换指纹UA、Cookie、请求节奏一致仍会被识别。并发过高代理网关和节点都有容量上限超过后成功率会断崖式下降。九、总结Python 爬虫接入代理 IP核心不是能不能挂上代理而是能否把代理稳定地放进工程闭环Session 复用降低握手开销代理轮换 失败统计避免单节点失效拖垮整体分层超时 选择性重试减少无效请求异步并发控制提升吞吐但不突破代理容量请求指纹管理降低被识别概率结构化日志让问题可追踪。把这些环节串起来才能把代理从能跑变成稳定跑、可监控、可优化。如果你使用 IPdodo 的跨境专线或静态住宅 IP可以把同样的接入逻辑套用到其提供的入口上结合自身业务目标站做压测和调优。参考资料Requests 官方文档Proxiesaiohttp Client ReferenceScrapy Downloader MiddlewarePython urllib.parse.quote