做网络开发这些年,代理IP几乎是绕不开的话题。但很多同学用了很久代理,却说不清代理到底是怎么工作的——为什么配置了代理就能隐藏IP?HTTPS代理为什么需要CONNECT方法?代理池又是怎么一回事?
这篇文章从协议原理讲起,最后手写一个简易代理池,带你把HTTP代理彻底搞明白。
很多人一开始会混淆这两个概念,其实很简单:
类型 | 代理对象 | 典型场景 |
|---|---|---|
正向代理 | 代理客户端 | 翻墙、爬虫隐藏IP、访问控制 |
反向代理 | 代理服务端 | Nginx负载均衡、CDN、网关 |
正向代理:客户端知道自己要访问哪个服务器,但不直接访问,而是让代理服务器去访问,代理把结果拿回来给客户端。服务器不知道真实客户端是谁。
反向代理:客户端不知道真实服务器是谁,只访问代理服务器,代理服务器决定把请求转发给哪个后端。客户端以为代理就是真实服务器。
我们平时说的"代理IP""HTTP代理",都是正向代理。
当你配置了HTTP代理后,发请求时发生了什么?
没有代理时:
客户端 → 直接连接目标服务器 → 发送 HTTP 请求有代理时:
客户端 → 连接代理服务器 → 发送完整URL的HTTP请求 → 代理服务器 → 连接目标服务器 → 转发请求关键区别:代理模式下,客户端发送的请求行里是完整URL,而不是路径。
# 没有代理
GET /index.html HTTP/1.1
Host: example.com
# 有代理
GET http://example.com/index.html HTTP/1.1
Host: example.com代理服务器解析完整URL,知道要访问哪个服务器,然后发起请求,把响应返回给客户端。
HTTPS就不一样了。HTTPS是加密的,代理服务器不能解密内容,那它怎么转发?
答案是:CONNECT方法建立TCP隧道。
流程是这样的:
CONNECT example.com:443 HTTP/1.1200 Connection Established这就是为什么HTTPS代理叫"隧道代理"——代理只是建立了一条管道,里面流的是什么,它不管。
代理服务器可以要求认证,最常见的是Basic Auth:
GET http://example.com/ HTTP/1.1
Proxy-Authorization: Basic dXNlcjpwYXNzProxy-Authorization 头和普通的 Authorization 头类似,只是作用于代理服务器。
理解了协议原理,再来看代理IP池。
为什么需要IP池?因为单个IP有这些问题:
IP池的本质就是:管理一批代理IP,在需要的时候提供一个可用的IP,失败了自动换下一个。
一个合格的代理池要解决四个问题:
不是所有IP都能用。需要定期检测IP是否可用,把不可用的剔除。
来了一个请求,给哪个IP?常见策略:
用这个IP请求失败了怎么办?自动换一个IP重试。注意:重试一定要换IP,不然重试100次也是失败。
IP不是永久有效的。住宅IP可能几分钟就变了,机房IP可能稳定一些。需要定期刷新IP池,淘汰过期的IP。
理论讲完了,来写代码。用Python实现一个最小可用的代理池,核心逻辑不超过100行。
import time
import random
import requests
from dataclasses import dataclass
from typing import List
@dataclass
class Proxy:
ip: str
port: int
protocol: str = "http"
last_check: float = 0
fail_count: int = 0
success_count: int = 0
@property
def url(self) -> str:
return f"{self.protocol}://{self.ip}:{self.port}"
@property
def success_rate(self) -> float:
total = self.success_count + self.fail_count
return self.success_count / total if total > 0 else 0class ProxyPool:
def __init__(self, check_url: str = "http://httpbin.org/ip"):
self.proxies: List[Proxy] = []
self.check_url = check_url
self.blacklist = set() # 失败次数过多的IP加入黑名单
def add_proxy(self, proxy: Proxy):
"""添加代理IP"""
if proxy.url not in self.blacklist:
self.proxies.append(proxy)
def get_proxy(self) -> Proxy | None:
"""获取一个可用代理(加权随机策略)"""
available = [p for p in self.proxies if p.fail_count < 5]
if not available:
return None
# 按成功率加权,成功率高的被选中概率大
weights = [max(p.success_rate, 0.1) for p in available]
return random.choices(available, weights=weights, k=1)[0]
def report_result(self, proxy: Proxy, success: bool):
"""上报使用结果,更新统计"""
if success:
proxy.success_count += 1
else:
proxy.fail_count += 1
if proxy.fail_count >= 10:
self.blacklist.add(proxy.url)
self.proxies.remove(proxy)
def check_proxy(self, proxy: Proxy) -> bool:
"""检测代理是否可用"""
try:
resp = requests.get(
self.check_url,
proxies={"http": proxy.url, "https": proxy.url},
timeout=5
)
proxy.last_check = time.time()
return resp.status_code == 200
except Exception:
proxy.last_check = time.time()
return False
def health_check(self):
"""定期健康检查,剔除不可用IP"""
for proxy in self.proxies[:]:
if not self.check_proxy(proxy):
proxy.fail_count += 3 # 检测失败加重惩罚
if proxy.fail_count >= 10:
self.proxies.remove(proxy)def fetch_with_retry(url: str, pool: ProxyPool, max_retries: int = 3) -> str | None:
for attempt in range(max_retries):
proxy = pool.get_proxy()
if not proxy:
print("没有可用代理了")
return None
try:
resp = requests.get(
url,
proxies={"http": proxy.url, "https": proxy.url},
timeout=10
)
if resp.status_code == 200:
pool.report_result(proxy, success=True)
return resp.text
else:
pool.report_result(proxy, success=False)
except Exception as e:
pool.report_result(proxy, success=False)
print(f"尝试 {attempt+1} 失败: {e}")
return Noneif __name__ == "__main__":
pool = ProxyPool()
# 假设从API获取了一批代理IP
raw_proxies = [
("1.2.3.4", 8080),
("5.6.7.8", 3128),
# ...
]
for ip, port in raw_proxies:
pool.add_proxy(Proxy(ip=ip, port=port))
# 先做一轮健康检查
pool.health_check()
print(f"可用代理数: {len(pool.proxies)}")
# 使用
result = fetch_with_retry("http://example.com", pool)
if result:
print("请求成功")上面的代码只是教学用的最小实现。真正的生产级代理池,架构会复杂得多:
┌─────────────┐
│ 代理源API │ 从供应商获取IP
└──────┬──────┘
▼
┌─────────────┐
│ 清洗模块 │ 去重、格式校验
└──────┬──────┘
▼
┌─────────────┐
│ 检测模块 │ 异步并发检测可用性
└──────┬──────┘
▼
┌─────────────┐
│ 存储层 │ Redis(有序集合,按质量打分)
└──────┬──────┘
▼
┌─────────────┐
│ 调度API │ 提供get/report接口
└──────┬──────┘
▼
┌─────────────┐
│ 客户端SDK │ 自动重试、换IP、熔断
└─────────────┘几个关键设计点:
# 正确写法
proxies = {
"http": "http://user:pass@1.2.3.4:8080",
"https": "http://user:pass@1.2.3.4:8080" # 注意:https代理也是写http://
}很多人把https代理写成 https://,这是错的。代理协议和目标协议是两回事,大部分HTTP代理都支持HTTPS隧道,所以写 http:// 就对了。
requests默认会复用连接。但代理IP场景下,你可能希望每次请求都用新连接(因为IP可能变了)。
session = requests.Session()
# 禁用连接复用
session.keep_alive = False用了代理,DNS解析也应该走代理,不然会泄漏真实IP。
requests默认是本地解析DNS的。如果在意这个,可以用 trust_env=True 或者换用支持远程DNS解析的库(如curl_cffi)。
HTTP代理的原理其实不复杂:
理解了原理,再用代理的时候就不会只知其然不知其所以然了。出了问题也能快速定位是代理的问题、代码的问题,还是目标网站的问题。
如果这篇文章对你有帮助,点个赞呗。有不同看法或者补充,评论区交流。
参考资料:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。