在数据采集项目中,最常见的问题就是请求量一大就被目标站点封禁IP,轻则返回403,重则弹出验证码甚至直接拉黑。反反爬虫的核心思路之一,就是让每一次请求看起来来自不同的客户端,而IP代理池正是实现这一目标的基础设施。本文将以Spring Boot为骨架,完整讲解代理池的搭建、维护与使用。

代理池的整体架构设计
一个可长期运行的代理池,通常由四个模块组成:采集器、校验器、存储层和调度接口。采集器负责从各个免费或付费代理源抓取原始代理;校验器通过实际请求测试代理的可用性和匿名度;存储层一般选用Redis的ZSet结构,以分数表示代理的可用程度;调度接口则向业务方提供获取、删除、查询代理的HTTP接口。
这里之所以推荐Redis的ZSet,是因为它天然支持按分数排序。每次代理请求成功就加分,失败就减分,分数低于阈值的代理自动剔除,整个淘汰流程无需额外的定时清理逻辑,实现起来非常优雅。另外,代理池应该作为独立服务部署,业务系统通过接口调用,避免业务代码与代理管理逻辑耦合。
代理采集与校验模块的实现
采集模块可以使用HttpClient或JSoup抓取公开代理站点,也可以接入付费代理API。校验是关键环节:向一个能回显来源IP的接口发起请求,如果返回的IP不是本机出口IP,说明代理生效;再检查请求头中是否携带了X-Forwarded-For,以此判断是高匿还是透明代理。透明代理会暴露真实IP,反反爬场景下应只保留高匿代理。
@Component
public class ProxyFetcher {
private final RestTemplate restTemplate = new RestTemplate();
// 校验代理是否可用,返回耗时(毫秒),失败返回-1
public long checkProxy(String host, int port) {
try {
long start = System.currentTimeMillis();
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
Proxy proxy = new Proxy(Proxy.Type.HTTP,
new InetSocketAddress(host, port));
factory.setProxy(proxy);
factory.setConnectTimeout(3000);
factory.setReadTimeout(3000);
RestTemplate rt = new RestTemplate(factory);
String body = rt.getForObject("https://httpbin.org/ip", String.class);
if (body != null && body.contains("origin")) {
return System.currentTimeMillis() - start;
}
return -1;
} catch (Exception e) {
return -1;
}
}
}上述代码通过动态创建RequestFactory的方式为单次请求绑定代理,适合校验场景。生产中建议维护一个HttpClient连接池并使用连接租借模式,避免频繁创建连接带来的开销。校验时还应记录响应耗时,耗时越短的代理评分越高,这样调度时能优先把优质代理分给业务使用。
Redis存储与评分淘汰机制
存储层使用两个ZSet:一个存放高分可用代理,一个存放低分待观察代理。初始入库分数设为10,请求成功加1分,上限100;请求失败减10分,降到0以下直接移除。这种不对称的加减策略能保证混入池子的坏代理被快速清理,而稳定的好代理不会因为偶发网络抖动被误杀。
@Service
public class ProxyPoolService {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String POOL_KEY = "proxy:pool";
// 添加代理,初始分数10
public void addProxy(String host, int port) {
redisTemplate.opsForZSet().add(POOL_KEY, host + ":" + port, 10);
}
// 请求成功,加分
public void reward(String proxy) {
Double score = redisTemplate.opsForZSet().score(POOL_KEY, proxy);
if (score != null) {
redisTemplate.opsForZSet().incrementScore(POOL_KEY, proxy, 1);
}
}
// 请求失败,减分,归零则移除
public void punish(String proxy) {
redisTemplate.opsForZSet().incrementScore(POOL_KEY, proxy, -10);
Double score = redisTemplate.opsForZSet().score(POOL_KEY, proxy);
if (score == null || score <= 0) {
redisTemplate.opsForZSet().remove(POOL_KEY, proxy);
}
}
// 随机获取一个可用代理,避免热点集中
public String getRandomProxy() {
Set<String> range = redisTemplate.opsForZSet()
.reverseRange(POOL_KEY, 0, 9);
if (range == null || range.isEmpty()) {
return null;
}
int idx = ThreadLocalRandom.current().nextInt(range.size());
return new ArrayList<>(range).get(idx);
}
}取代理时从分数最高的前10个中随机挑选,而不是永远取第一名,这样可以让请求分散到多个优质代理上,避免单个代理被打爆。如果业务对失败敏感,还可以在punish方法中记录连续失败次数,达到阈值时立即移除而不是等分数耗尽。
在爬虫业务中动态切换代理
业务侧的核心是封装一个带代理绑定和自动重试的请求方法:每次请求前从池中取代理,成功则reward,失败则punish并换代理重试,最多重试3次。同时建议配合随机User-Agent、随机休眠时间一起使用,单纯换IP而不换请求特征,仍然很容易被指纹识别命中。
@Service
public class CrawlService {
@Autowired
private ProxyPoolService poolService;
public String fetchWithProxy(String url) {
int maxRetry = 3;
for (int i = 0; i < maxRetry; i++) {
String proxy = poolService.getRandomProxy();
if (proxy == null) {
throw new IllegalStateException("代理池为空,请检查采集器");
}
try {
// 构造带代理的请求并执行
String result = doRequest(url, proxy);
poolService.reward(proxy);
Thread.sleep(500 + ThreadLocalRandom.current().nextInt(1500));
return result;
} catch (Exception e) {
poolService.punish(proxy);
}
}
throw new RuntimeException("重试耗尽,目标可能整体封禁");
}
}并发场景下还要注意代理池的容量问题。免费代理的可用率通常只有两三成,采集任务建议每5到10分钟执行一次;如果业务请求量大,更稳妥的做法是接入付费代理商的提取API,并按提取规则控制调用频率。此外,可结合Hystrix或Resilience4j对单个目标域名做熔断,当失败率突然飙升时暂停任务,避免无效消耗代理资源。
最后要提醒的是,代理池只是反反爬体系中的一环。真正稳健的采集系统还需要模拟正常用户的行为节奏,控制请求频率,处理Cookie会话,必要时使用无头浏览器渲染页面。把IP轮换、请求特征伪装和礼貌抓取策略结合起来,才能让爬虫长期稳定地运行下去。
Spring BootIP代理池反反爬虫修改时间:2026-08-31 17:40:37