开始之前

  • JDK 11 或更高版本。HttpClient 和用 java 命令直接运行单个源文件都是 Java 11 引入的;示例在 25 上测试。
  • 一个空文件夹和一个终端,不需要 Maven 或 Gradle 项目。
  • 控制台服务页面上列出的 HOST、PORT、USERNAME 和 PASSWORD。

你的连接信息

登录控制台,打开你购买的服务。服务页面上会显示该服务的主机、端口、用户名和密码。ProxyPanda 没有一个统一的固定地址,所以每次都请从那里复制。指南中的代码使用下面这些占位符,请逐一替换成你自己的值。

HOST
该服务的代理地址,按服务页面上显示的原样填写。
PORT
要连接的端口。请和主机一起复制,因为不同服务的端口可能不同。
USERNAME
你的代理登录名。它与你登录控制台所用的邮箱是分开的。
PASSWORD
请完整复制。住宅代理在控制台中选择的选项会附加在密码末尾,凭记忆手动输入的密码会丢失这些选项。

把你用来连接的 IP 地址加入服务的白名单,就可以不用用户名和密码。这时代码里只需要 HOST 和 PORT。

示例都使用 HTTP,它可以通过加密隧道访问 https 网站。SOCKS5 同样可用:住宅代理的端口两种都接受,ISP 和数据中心代理在控制台里切换协议。

步骤

  1. 检查 JDK 并新建文件夹

    java -version 会显示负责编译和运行文件的版本。每个示例都是完整的源文件:保存到这个文件夹,用 java 加文件名启动。

    terminal
    java -version
    mkdir proxycheck && cd proxycheck
  2. 创建带代理和 Authenticator 的客户端

    ProxySelector.of 把所有请求发往同一个代理地址。Authenticator 只在请求方类型为 PROXY 时交出登录信息,网站要求输入密码时拿不到代理的密码。connectTimeout 限制连上代理的时间,请求上的 timeout 限制等待响应的总时长。

    CheckIp.java
    import java.net.Authenticator;
    import java.net.InetSocketAddress;
    import java.net.PasswordAuthentication;
    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 CheckIp {
        public static void main(String[] args) throws Exception {
            HttpClient client = HttpClient.newBuilder()
                    .proxy(ProxySelector.of(new InetSocketAddress("HOST", PORT)))
                    .authenticator(new Authenticator() {
                        @Override
                        protected PasswordAuthentication getPasswordAuthentication() {
                            if (getRequestorType() != RequestorType.PROXY) return null;
                            return new PasswordAuthentication("USERNAME", "PASSWORD".toCharArray());
                        }
                    })
                    .connectTimeout(Duration.ofSeconds(10))
                    .build();
    
            HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.ipify.org?format=json"))
                    .timeout(Duration.ofSeconds(30))
                    .build();
    
            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
            System.out.println(response.statusCode() + " " + response.body());
        }
    }
  3. 允许隧道使用 Basic 认证后运行

    JDK 的 conf/net.properties 把 jdk.http.auth.tunneling.disabledSchemes 设为 Basic,客户端因此不肯在建立 HTTPS 隧道的 CONNECT 请求里附上用户名和密码。在命令行里清空这个列表,登录信息就能发出,程序会打印 200 和 api.ipify.org 返回的 JSON。不加这个参数时,同一个文件打印的是 407 null。

    terminal
    java -Djdk.http.auth.tunneling.disabledSchemes="" CheckIp.java
  4. 或者在代码里清空这个属性

    无法修改启动命令时,把 System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "") 写成 main 的第一行。后面的轮换示例就是这样做的。直接修改 conf/net.properties 里的那一行在测试中也有效,但这个 JDK 运行的所有程序都会受影响。

  5. 把请求分散到多个静态 IP

    ISP 和数据中心服务给你的是一组固定地址。把它们写进 proxies.txt,每行一个 HOST:PORT:USERNAME:PASSWORD,然后用 java Rotate.java 启动。程序为每一行建一个 HttpClient,各自带一个 Authenticator,第 i 个请求交给第 i 除以列表长度取余对应的客户端。某个代理出错时会打印异常,循环继续往下走。

    Rotate.java
    import java.net.Authenticator;
    import java.net.InetSocketAddress;
    import java.net.PasswordAuthentication;
    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.nio.file.Files;
    import java.nio.file.Path;
    import java.time.Duration;
    import java.util.ArrayList;
    import java.util.List;
    
    public class Rotate {
        static HttpClient clientFor(String line) {
            String[] p = line.trim().split(":", 4);
            return HttpClient.newBuilder()
                    .proxy(ProxySelector.of(new InetSocketAddress(p[0], Integer.parseInt(p[1]))))
                    .authenticator(new Authenticator() {
                        @Override
                        protected PasswordAuthentication getPasswordAuthentication() {
                            if (getRequestorType() != RequestorType.PROXY) return null;
                            return new PasswordAuthentication(p[2], p[3].toCharArray());
                        }
                    })
                    .connectTimeout(Duration.ofSeconds(10))
                    .build();
        }
    
        public static void main(String[] args) throws Exception {
            System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "");
    
            List<HttpClient> clients = new ArrayList<>();
            for (String line : Files.readAllLines(Path.of("proxies.txt"))) {
                if (!line.isBlank()) clients.add(clientFor(line));
            }
    
            for (int i = 0; i < 6; i++) {
                HttpClient client = clients.get(i % clients.size());
                HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.ipify.org"))
                        .timeout(Duration.ofSeconds(30))
                        .build();
                try {
                    System.out.println(i + " " + client.send(request, HttpResponse.BodyHandlers.ofString()).body());
                } catch (Exception e) {
                    System.out.println(i + " " + e);
                }
            }
        }
    }

轮换 IP 与粘性 IP

HttpClient 会在请求之间保持隧道。测试中,同一个客户端的三次请求只经过一次 CONNECT,复用的隧道出口地址不变。这正适合“粘性 IP”以及 ISP、数据中心 IP。

住宅代理选“随机 IP”时,新隧道才会带来新地址。在 JDK 25 上加 -Djdk.httpclient.keepalive.timeout=0 启动后,三次请求各自建立了一条新隧道。每个请求新建一个 HttpClient 也能达到同样效果,只是每次都要重新握手。

HttpClient 创建后不可变,可以发送任意多个请求,所以启动时为每个代理建一个,之后共用即可。

常见错误与解决办法

程序打印 407 null

代理要求登录,而客户端没有提供,因为隧道仍然禁用 Basic。把 -Djdk.http.auth.tunneling.disabledSchemes="" 放在文件名前面:放在 CheckIp.java 后面时,它会作为参数传给 main,不起作用。

收到几次 407 之后抛出 IOException

Authenticator 已经回应,但代理一直拒绝它发送的登录信息。在测试代理上,客户端尝试了四次后抛出异常,JDK 25 给出的原因是一个看不出头绪的 NullPointerException。从服务页面重新粘贴 USERNAME 和 PASSWORD。

没有附带信息的 ConnectException

HOST 和 PORT 上没有任何程序接受连接。把两者和服务页面对照一遍,再到控制台确认服务仍然有效。

HttpConnectTimeoutException: HTTP connect timed out

连接或隧道没能按时建立。如果是响应太慢,请求超时过后抛出的会是 HttpTimeoutException。住宅出口是家庭网络,可能有延迟,给它们多一些时间,并只重试失败的网址。

选择哪条产品线

面向接受服务器流量的网站,Java 爬虫和 API 轮询程序用数据中心 IP 成本最低,Rotate.java 会在这些 IP 之间分摊负载。来自服务器 IP 段的请求开始失败时换成住宅代理;某个服务需要连续几周保持同一地址时,给它一个 ISP IP。

卡在某一步了?

把命令和报错贴到 Discord,记得先删掉密码。那里的人大多遇到过这些错误。

加入 Discorddiscord.gg/proxypanda
$5 起步去 Discord 问