先看一段我当时写的代理设置代码,自以为能跑,结果第一天就被反爬系统教做人:
CloseableHttpClient client = HttpClients.custom()
.setProxy(new HttpHost("111.222.333.444", 8080))
.build();
HttpGet request = new HttpGet("https://target-site.com/page?id=123");
CloseableHttpResponse response = client.execute(request);
代码没问题,问题在于这个IP在三小时内被请求了1200次,目标站直接返回403,并且封了C段。老板脸色不好看,因为内容审核系统要监控竞品和客户网站,每天50万页面,不能停。
我一开始以为只要买个大IP池,用Java定时调API提取就完事,结果踩了一堆坑。后来发现,动态代理和隧道代理的选择,比池子大小重要得多。
需求拆解:每天50万页面需要多少IP?
内容审核爬虫的逻辑是:取回网页HTML,用规则引擎和NLP模型判断是否含违规词、违法内容。目标站分散在几十个域名,反爬强度不一,有的站一个IP请求30次就拉黑,有的能扛500次。
按最保守估算:假设每个IP平均可完成200次请求(不触发封禁),50万页面需要2500个IP/天。但实际考虑并发、失败重试、IP可用率99.9%的话,必须准备3000个以上IP才够。另外,部分目标站要求HTTPS,部分只支持HTTP,还有少量需要SOCKS5,协议兼容也不能忽视。
我对比了市面上的三种接入方式:API提取、账密认证、白名单。API提取最灵活但需要自己维护IP池;账密认证方便但每次请求要代理认证;白名单只能固定服务器IP,不适合大规模轮换。动态代理按量计费,0.0022元/IP起;隧道代理按并发或流量,16元/天起。具体参数如下表:
| 维度 | 动态代理(API提取) | 隧道代理(账密/白名单) |
|---|
| IP切换粒度 | 每次提取一个IP,可控制多久换一次 | 每次请求自动轮换,无需手工管理 |
| 并发稳定性 | 取决于IP质量,延迟波动较大 | 固定入口,延迟低,平均响应120ms |
| 开发成本 | 高,需实现IP池、健康检查、剔除 | 低,只需设置代理地址和认证 |
| 单位成本(按50万页/天) | 约55元/天(3000IP) | 约80元/天(按并发16) |
光看价格动态代理便宜,但后面我才明白,省下的钱都变成了加班费。
Java接入动态代理的坑:线程安全与健康检查
我先用动态代理+API提取实现了一版。核心逻辑是:一个后台线程每10秒从API提取5个IP放入队列,爬虫线程从队列取IP使用。队列用LinkedList,结果并发一上来就出IndexOutOfBoundsException。我一开始以为简单的ArrayList就够了,程序崩了好几次才换成ConcurrentLinkedQueue,这个坑真是低级但致命。
更大的问题是IP有效性。API声称可用率99.9%,但实际在目标站上,约15%的IP请求超时或直接被拒。原因包括:目标站识别出代理机房IP、延迟过高(>800ms)、或者IP在提取前已被封。我需要实现健康检查:每隔60秒对队列中所有IP发送一次Head请求到百度首页,超时或非200就剔除。代码如下:
public boolean checkProxy(HttpHost proxy) {
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(3000)
.setSocketTimeout(3000)
.build();
HttpGet test = new HttpGet("https://www.baidu.com");
test.setConfig(config);
try (CloseableHttpResponse resp = client.execute(proxy, test)) {
return resp.getStatusLine().getStatusCode() == 200;
} catch (Exception e) {
return false;
}
}
加上健康检查和重试后,失败率降到2%,但单个IP的延迟波动依然大:最快60ms,最慢2秒,导致爬虫整体吞吐不稳定。而且我每天要花半小时监控IP池数量,不够了手动补提取,心累。
改用隧道代理后的部署与效果验证
有一天我烦了,试了试隧道代理。它的原理是:客户端只需要连接一个固定的代理入口(例如proxy.mayihttp.com:8080),每次请求由服务端自动分配一个不同的出口IP,无需本地维护IP池。Java代码非常简洁:
CredentialsProvider creds = new BasicCredentialsProvider();
creds.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("user", "pass"));
CloseableHttpClient client = HttpClients.custom()
.setDefaultCredentialsProvider(creds)
.setProxy(new HttpHost("proxy.mayihttp.com", 8080))
.build();
// 正常发请求,无需手动更换IP
部署后我用相同的50万页面任务跑了三天,对比数据如下:
| 指标 | 动态代理(自维护) | 隧道代理 |
|---|
| 平均响应时间 | 320ms | 120ms |
| 请求失败率 | 2.1% | 0.8% |
| 开发维护耗时(每周) | 6小时 | 0.5小时 |
| 每日成本(50万页) | 55元 | 80元 |
说实话,我一开始坚持动态代理为了每天省25元,结果跑了一周发现,多花的开发时间折算成工资远超差额。老板还吐槽:“怎么爬虫又崩了?” 换隧道代理后,一次都没崩过。
当然,隧道代理并非完美:它不能指定具体IP城市(内容审核不需要城市级精度),而且单并发上限有限,如果超过100并发需要加钱。我们控制在并发16,完全够用。如果你的业务需要精确控制每个请求的IP(比如注册、投票),动态代理更合适;但像我这种批量内容检测,隧道代理的稳定性和低延迟带来的收益更大。
最终结论:别再迷信IP池子的大小,先想清楚你的业务是“高频短连接”还是“低频长连接”。内容审核属于前者,隧道代理是更优解。如果你还在纠结选型,可以去 官网 看下他们隧道代理的实测延迟和可用率,结合自己的并发量算一笔账。别学我,为了省小钱熬了三个夜。