上周帮朋友看一个采集程序,本机测试一切正常,放到服务器上跑了半小时就开始大面积超时。代码翻来覆去检查没毛病,最后把请求出口改走代理IP,问题立刻消失了。这种场景在公开数据采集、市场研究这些活儿里太常见了——程序本身不难,难的是让请求稳定地发出去、收回来。这篇文章把Java里设置代理IP的几种写法一次讲全,代码都能直接抄走改,文末再集中处理那些让人头疼的连接报错。
最省事的写法:JVM系统属性,四行代码全局生效
如果只是个小工具、临时脚本,想让整个程序的所有HTTP请求都走代理,系统属性是最快的路子。在程序入口处加上这几行:
public class GlobalProxy {
public static void main(String[] args) {
System.setProperty("http.proxyHost", "123.45.67.89"); // 代理服务器IP
System.setProperty("http.proxyPort", "8000"); // 代理端口
System.setProperty("https.proxyHost", "123.45.67.89");
System.setProperty("https.proxyPort", "8000");
// 不走代理的地址,多个用竖线分隔,支持通配符
System.setProperty("http.nonProxyHosts", "localhost|127.0.0.1");
}
}
这种写法作用于整个JVM,所有基于 HttpURLConnection 的请求都会自动走代理,包括底层用到它的那些库。好处是零侵入,坏处是粒度太粗——做不到"这几个请求走代理、那几个直连"的精细控制。另外有个时间点要注意:系统属性必须在第一次发起请求之前设置,请求发出去之后再改就不生效了。
精确到单个连接:Proxy对象写法
项目稍微正式一点,就建议用 Proxy 对象。哪个连接走代理、走哪个代理,代码里写得明明白白:
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
public class SingleProxy {
public static void main(String[] args) throws Exception {
// 构造代理对象:HTTP类型,地址加端口
Proxy proxy = new Proxy(Proxy.Type.HTTP,
new InetSocketAddress("123.45.67.89", 8000));
URL url = new URL("https://example.com/api");
// 关键:把proxy传进openConnection
HttpURLConnection conn = (HttpURLConnection) url.openConnection(proxy);
conn.setConnectTimeout(5000);
conn.setReadTimeout(10000);
conn.setRequestMethod("GET");
int code = conn.getResponseCode();
System.out.println("状态码:" + code);
}
}
两个细节值得记一下。第一,openConnection() 不带参数就是直连,带上 proxy 参数才走代理,不少人报错就出在这里——代理对象建了,却忘了传进去。第二,如果拿到的是SOCKS5代理,把 Proxy.Type.HTTP 换成 Proxy.Type.SOCKS 就行,其他代码一行不用动。
新项目首选:Java 11自带的HttpClient
JDK 11 之后自带的 java.net.http.HttpClient 是现在新项目的首选,写法更清爽,连接复用也做得更好:
import java.net.InetSocketAddress;
import java.net.ProxySelector;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public class NewHttpClient {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newBuilder()
.proxy(ProxySelector.of(new InetSocketAddress("123.45.67.89", 8000)))
.connectTimeout(Duration.ofSeconds(5))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://example.com/ip"))
.GET()
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
跑采集任务的时候,HttpClient 的性能优势能明显感觉出来,尤其是请求量大、目标接口多的场景,连接建立的开销省得很可观。
代理需要账号密码时的认证写法
正规的付费代理服务一般有两种认证方式:IP白名单绑定和账号密码。走账号密码的话,JDK里要通过 Authenticator 来处理:
import java.net.Authenticator;
import java.net.PasswordAuthentication;
public class ProxyAuth {
public static void main(String[] args) {
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
// 用户名和密码,注意别带多余空格
return new PasswordAuthentication("yourname",
"yourpass".toCharArray());
}
});
// 之后按前面的写法正常 openConnection(proxy) 即可
}
}
有个坑要提醒:这套 Authenticator 对 HttpURLConnection 有效,但新版 HttpClient 并不会自动拿它做代理认证,需要手动往请求头里塞认证信息:
String auth = java.util.Base64.getEncoder()
.encodeToString("yourname:yourpass".getBytes());
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://example.com/api"))
.header("Proxy-Authorization", "Basic " + auth)
.GET()
.build();
Base64编码的原始字符串里,用户名和密码中间是英文冒号,这个格式错一个字符都会收到407。
常见连接报错逐个排查
代码写对了还报错,问题多半出在代理本身。下面这张表是我踩坑多年整理的高频报错对照,遇到问题先对号入座:
| 报错信息 | 多半是这几个原因 | 处理办法 |
|---|---|---|
| Connection refused | IP已失效、端口写错 | 重新提取新IP,逐字核对端口 |
| connect timed out | 代理响应慢、并发太高 | 调大超时时间,降低并发,换个IP重试 |
| Read timed out | 目标站响应慢或链路拥堵 | 加大读取超时,加上失败重试 |
| 407 Proxy Authentication Required | 认证信息缺失或格式错误 | 核对账号密码,检查Base64拼接 |
| UnknownHostException | 协议类型用错,SOCKS5当HTTP用了 | 确认代理协议,对应修改Proxy.Type |
| SSLHandshakeException | HTTPS证书校验失败 | 核对系统时间,必要时导入证书 |
展开说两个最常见的。Connection refused 基本可以断定是代理端的问题——IP过期了、端口不对,或者代理服务器压根没开。短效动态IP的存活时间本来就短,几分钟到几十分钟不等,程序里最好加一层"请求前先验证、失败自动换新IP重试"的逻辑,别让一个失效IP卡住整个任务。验证代码很简单:
import java.net.InetSocketAddress;
import java.net.Socket;
public class ProxyChecker {
// 发正式请求前,先花3秒探一下代理通不通
public static boolean isProxyAlive(String host, int port) {
try (Socket socket = new Socket()) {
socket.connect(new InetSocketAddress(host, port), 3000);
return true;
} catch (Exception e) {
return false;
}
}
}
407认证失败则九成出在写法上:用 Authenticator 时检查用户名密码有没有混入空格;手动拼 Basic 认证时确认冒号是英文的;还有部分服务商默认走IP白名单认证,账号密码模式需要在后台手动开启,这种情况传了密码也没用,去后台把白名单绑上自己服务器的出口IP就好。
再啰嗦一句超时参数一定要设。不设超时的程序,碰到质量差的代理会无限挂起,线程越积越多,最后整个服务被拖垮。connectTimeout 建议5秒以内,readTimeout 视业务10到30秒,再配合失败重试,稳定性会上一个台阶。
报错反复出现?先看看IP质量
排查到最后你会发现,Java代理报错有一大半根源不在代码,而在IP本身。网上随便找的免费IP列表,十个里能用的常常不到两个,程序写得再稳也扛不住这种可用率。想省心,还是得用正规服务商的IP资源。
我们团队现在用的是神龙HTTP,主要看中两点。一是可用率能做到99.9%,背后是3000万+正规授权代理IP储备、覆盖国内300多个城市,提取出来的IP基本拿来就能用,很少出现连不上的情况,重试逻辑都省了不少事。二是API提取对Java很友好,兼容主流爬虫编程语言,文档里带现成的示例代码,把提取到的IP和端口填进上面任何一种写法里,几分钟就能跑通。它支持HTTP/HTTPS/SOCKS5三种协议,跟前面讲的 Proxy.Type 正好对得上,个人中心还有可视化的用量统计和实时监控,哪个时段消耗异常一眼就能看出来,遇到问题技术团队7×24小时在线。
常见问题
Q: 设置了代理,请求还是走的本机IP,怎么回事?
三个方向查:系统属性是否在第一次请求前设置;openConnection 是否传入了 proxy 参数;如果用了第三方网络库,它们大多不读JVM系统属性,要在库自己的配置里单独设置代理。最直接的验证办法是请求一个能返回出口IP的接口,看返回的是不是代理IP。
Q: 短效动态IP和长效静态IP该怎么选?
高频采集、需要不断获取新IP的任务用短效动态,配合"预检加重试"的逻辑跑得很顺;需要长时间维持同一出口IP的场景,比如要保持会话状态的调试工作,用长效静态更省事,不用频繁改代码里的地址。
Q: 怎么在请求前判断代理IP是否可用?
就用上面的 ProxyChecker 思路:先用 Socket 去连代理的IP和端口,几百毫秒内连得上再发正式请求,连不上直接丢弃换下一个。这个预检动作开销很小,但能把整体成功率提上去不少。
Q: 收到407报错,但账号密码确认没写错?
检查三点:认证字符串里用户名和密码之间必须是英文冒号;Base64编码别对整段"Basic xxx"重复编码;部分服务商默认IP白名单认证,需要登录后台绑定服务器出口IP,或者手动开启密码认证模式,二选一生效。





