Java ist ein Arbeitspferd für Datenerfassung im Maßstab: ausgereifte HTTP-Clients, echte Threads, und JVM-Tooling, das langlaufende Crawler beobachtbar macht. Einen Residential Proxy in einen der beiden Clients einzubinden, die die meisten Teams nutzen, OkHttp und Apache HttpClient, ist unkompliziert. Die Reibung liegt in den Details, die jede Bibliothek anders handhabt: wie die Proxy-Authentifizierung geliefert wird, ein Verbindungspool-Standard, der Sie still drosselt, und die Regel zum Verbrauch der Antworten, die entscheidet, ob Pooling überhaupt funktioniert.
Dies ist der Java-Beitrag derselben Serie wie Residential Proxies mit Python, mit Playwright und mit Go: der funktionierende Code für beide Clients, plus die Java-spezifischen Fallen.
Alles unten nutzt das Shifter Residential-Gateway: ein Endpunkt, p.shifter.io:443, mit dem gesamten Targeting im Benutzernamen kodiert. Tauschen Sie Host und Zugangsdaten für einen anderen Anbieter; die Form bleibt gleich.
Das Gateway-Modell in einem Absatz
Der Proxy-Benutzername trägt Ihre Authentifizierung und Ihr Targeting. Sie wechseln nicht den Endpunkt, um Land oder Session zu ändern, Sie ändern die Benutzernamen-Zeichenfolge:
customer-USERNAME-country-us-sid-abc123-ttl-600country-us zielt auf die USA, sid fixiert eine Sticky Session, ttl hält diese IP für N Sekunden. Lassen Sie sid/ttl weg, und jede neue Verbindung rotiert. Das Passwort ist konstant. Eine Feinheit gleich vorweg: in beiden Java-Clients werden Proxy-Zugangsdaten nicht in die Proxy-URL gesetzt, sie gehen durch einen eigenen Authentifizierungsmechanismus. Das ist das, was Leute am häufigsten falsch machen.
OkHttp
OkHttp nimmt ein Proxy-Objekt für die Adresse und einen separaten proxyAuthenticator für die Zugangsdaten. Versuchen Sie nicht, user:pass@ in eine URL zu kodieren; OkHttp liest das nicht.
import okhttp3.*;import java.net.InetSocketAddress;import java.net.Proxy;import java.io.IOException;
public class ProxyExample { public static void main(String[] args) throws IOException { String user = System.getenv("SHIFTER_USER") + "-country-us"; String pass = System.getenv("SHIFTER_PASS");
OkHttpClient client = new OkHttpClient.Builder() .proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("p.shifter.io", 443))) .proxyAuthenticator((route, response) -> { // Aufgerufen, wenn der Proxy 407 zurückgibt. Proxy-Authorization anhängen. String credential = Credentials.basic(user, pass); return response.request().newBuilder() .header("Proxy-Authorization", credential) .build(); }) .build();
Request request = new Request.Builder().url("https://api.ipify.org").build(); try (Response response = client.newCall(request).execute()) { // try-with-resources schließt den Body System.out.println(response.body().string()); // eine US-Residential-IP } }}Zwei Dinge zu verinnerlichen. Die user-Zeichenfolge enthält die Targeting-Flags (-country-us), denn dort lebt die Geo. Und das try-with-resources um die Response ist kein optionaler Stil, es schließt den Response-Body, was die Verbindung an den Pool zurückgibt.
Verwenden Sie den Client wieder. OkHttpClient ist darauf ausgelegt, einmal erstellt und geteilt zu werden; er hält den Verbindungspool und den Thread-Dispatcher, und ist thread-safe. Einen pro Anfrage zu erstellen, wirft das Pooling weg und leakt Ressourcen. Um die Identität pro Anfrage zu variieren, leiten Sie eine Variante mit newBuilder() ab, die den zugrunde liegenden Pool und Dispatcher teilt:
// Ein Basis-Client, geteilt. Identitäts-Varianten verwenden seinen Pool + Dispatcher wieder.OkHttpClient forGeo(OkHttpClient base, String country, String sid) { String user = System.getenv("SHIFTER_USER") + "-country-" + country + (sid != null ? "-sid-" + sid + "-ttl-600" : ""); String pass = System.getenv("SHIFTER_PASS"); return base.newBuilder() .proxyAuthenticator((route, resp) -> resp.request().newBuilder() .header("Proxy-Authorization", Credentials.basic(user, pass)) .build()) .build();}Geben Sie jeder logischen Arbeitseinheit ihre eigene sid und rotieren Sie zwischen Einheiten, nicht mitten im Ablauf (Sticky vs Rotierend behandelt die Unterscheidung).
Apache HttpClient (5.x)
Apache HttpClient liefert den Proxy über die Request-Config oder einen Route-Planner, und die Zugangsdaten über einen CredentialsProvider, der auf den Proxy-Host begrenzt ist.
import org.apache.hc.client5.http.classic.methods.HttpGet;import org.apache.hc.client5.http.impl.classic.*;import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;import org.apache.hc.client5.http.auth.*;import org.apache.hc.client5.http.config.RequestConfig;import org.apache.hc.core5.http.HttpHost;import org.apache.hc.core5.util.Timeout;
public class ApacheProxyExample { public static void main(String[] args) throws Exception { HttpHost proxy = new HttpHost("http", "p.shifter.io", 443); String user = System.getenv("SHIFTER_USER") + "-country-us"; char[] pass = System.getenv("SHIFTER_PASS").toCharArray();
BasicCredentialsProvider creds = new BasicCredentialsProvider(); creds.setCredentials(new AuthScope(proxy), new UsernamePasswordCredentials(user, pass));
// Pool: Pro-Route vom Standard 5 anheben (siehe Fallstrick unten). PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); cm.setMaxTotal(200); cm.setDefaultMaxPerRoute(50);
RequestConfig config = RequestConfig.custom() .setProxy(proxy) .setConnectTimeout(Timeout.ofSeconds(10)) // Connect-Phase .setResponseTimeout(Timeout.ofSeconds(30)) // Antwort-Phase .build();
try (CloseableHttpClient client = HttpClients.custom() .setConnectionManager(cm) .setDefaultCredentialsProvider(creds) .setDefaultRequestConfig(config) .build()) {
HttpGet get = new HttpGet("https://api.ipify.org"); // try-with-resources auf der Antwort verbraucht + gibt die Verbindung frei. try (var response = client.execute(get)) { System.out.println(new String(response.getEntity().getContent().readAllBytes())); } } }}Beachten Sie die getrennten Connect- und Response-Timeouts, diese Connect-versus-Response-Aufteilung ist genau das, was Timeouts diagnostizierbar macht, wenn etwas hängt.
Fallstrick 1: Apaches Standard-Max-pro-Route ist 5
Das ist das Java-Äquivalent zu Gos MaxIdleConnsPerHost-Falle, und es beißt hart. PoolingHttpClientConnectionManager hat standardmäßig 2 Verbindungen pro Route und 20 insgesamt in älteren Builds, und ein niedriges Pro-Route-Cap in 5.x. Fahren Sie 50 Threads gegen einen Host, und die meisten blockieren, während sie warten, dass eine Verbindung frei wird, was genau wie ein langsamer Proxy aussieht.
Setzen Sie den Pool mindestens auf Ihre Nebenläufigkeit pro Host:
cm.setMaxTotal(200);cm.setDefaultMaxPerRoute(50); // >= Ihre Nebenläufigkeit pro HostWenn der Durchsatz Ihres Crawlers stagniert, egal wie viele Threads Sie hinzufügen, ist dieser Standard das Erste, was zu prüfen ist.
Fallstrick 2: die Entity verbrauchen, sonst kehrt die Verbindung nie zurück
Beide Clients poolen Verbindungen, und beide geben eine Verbindung nur dann an den Pool zurück, wenn die Antwort vollständig verbraucht und geschlossen wurde. In Apache HttpClient hält eine nicht verbrauchte Entity die Verbindung ausgecheckt; tun Sie genug davon, und Ihr Pool verhungert, obwohl technisch nichts leakt.
Nutzen Sie try-with-resources auf der Antwort (wie oben), oder verbrauchen Sie explizit:
import org.apache.hc.core5.http.io.entity.EntityUtils;// ...EntityUtils.consume(response.getEntity()); // leert + gibt die Verbindung freiDie Falle sind frühe Ausstiege: bei einem Nicht-200-Status auszusteigen, ohne die Entity zu verbrauchen, strandet die Verbindung. Bei einem Scraper, der viele Blocks trifft, ist das der Großteil Ihres Traffics, und der Pool stirbt still.
Fallstrick 3: den Client immer wiederverwenden
Sowohl OkHttpClient als auch CloseableHttpClient sind schwergewichtig, thread-safe und dafür gedacht, über die ganze Anwendung geteilt zu werden. Sie besitzen den Verbindungspool, und ein frischer pro Anfrage zahlt jedes Mal einen vollen TCP- und TLS-Handshake durch den Proxy, der Overhead, den der Latenz-Leitfaden zu eliminieren existiert. Bauen Sie einen beim Start, injizieren Sie ihn, und leiten Sie anfragen-begrenzte Varianten nur ab, wenn Sie die Identität variieren müssen.
Geo und Sessions rotieren
Weil das Targeting im Benutzernamen lebt, ist eine andere Identität eine andere Zugangsdaten-Zeichenfolge.
OkHttp: leiten Sie einen Client mit newBuilder() pro Identität ab (oben gezeigt); er verwendet den geteilten Pool wieder, bleibt also effizient.
Apache HttpClient: hängen Sie einen HttpClientContext pro Anfrage an, der einen CredentialsProvider für diese Identität trägt, sodass ein Client viele Identitäten bedient, ohne neu zu bauen:
HttpClientContext ctx = HttpClientContext.create();BasicCredentialsProvider perCall = new BasicCredentialsProvider();perCall.setCredentials(new AuthScope(proxy), new UsernamePasswordCredentials(userFor("de", "job-42"), pass));ctx.setCredentialsProvider(perCall);client.execute(get, ctx, resp -> { /* verarbeiten */ return null; });Rotieren Sie zwischen logischen Arbeitseinheiten statt innerhalb einer, und bilden Sie Arbeit auf Identitäten ab, wie der Lastverteilungs-Beitrag beschreibt.
Nebenläufigkeit, pro Host
Begrenzen Sie die Nebenläufigkeit pro Zielhost, nicht mit einem globalen Cap, damit ein fragiles Ziel nicht bombardiert und ein permissives nicht ausgehungert wird. Ein Semaphore pro Host ist der einfachste Ausdruck:
Map<String, Semaphore> limits = Map.of( "tough-site.example", new Semaphore(4), "open-site.example", new Semaphore(32));
void fetch(String host, Runnable work) throws InterruptedException { Semaphore sem = limits.get(host); sem.acquire(); try { work.run(); } finally { sem.release(); }}Jenseits der Toleranz eines Ziels kaufen mehr Threads Blocks, keinen Durchsatz (wie man Blocks vermeidet). Dimensionieren Sie das Pro-Route-Limit des Pools und Ihr Semaphore pro Host gemeinsam.
Verifizieren, dass Sie tatsächlich auf dem Proxy sind
Bevor Sie irgendetwas anderes benchmarken oder debuggen, bestätigen Sie die Exit-IP:
Request req = new Request.Builder().url("http://ip-api.com/json").build();try (Response r = client.newCall(req).execute()) { System.out.println(r.body().string()); // erwarte eine Residential-IP im Zielland}Ihre eigene IP heißt, der Client nutzt den Proxy nicht. Ein Hänger heißt, der lokale Egress ist blockiert. Beides behandelt der Timeout-Diagnoseleitfaden.
FAQ
Warum funktioniert http://user:pass@host für den Proxy in Java nicht?
Weder OkHttp noch Apache HttpClient liest inline Proxy-Zugangsdaten aus einer URL. OkHttp nutzt einen proxyAuthenticator, der Proxy-Authorization setzt; Apache nutzt einen auf den Proxy-Host begrenzten CredentialsProvider. Da das Gateway das Targeting im Benutzernamen kodiert, geht dieser Benutzername in den Authenticator/die Credentials, nicht in eine URL.
Mein Java-Scraper stockt unter Last, selbst mit einem Client. Warum?
Höchstwahrscheinlich drosselt Sie das Pro-Route-Limit des Apache-Pools (standardmäßig niedrig), oder Sie verbrauchen Response-Entities nicht, sodass Verbindungen nie in den Pool zurückkehren. Heben Sie setDefaultMaxPerRoute auf Ihre Nebenläufigkeit an und verbrauchen Sie jede Antwort mit try-with-resources.
Wie rotiere ich IPs pro Anfrage in Java?
Variieren Sie den Proxy-Benutzernamen. In OkHttp leiten Sie einen Client pro Identität mit newBuilder() ab (er teilt den Pool). In Apache HttpClient übergeben Sie einen HttpClientContext pro Anfrage mit einem CredentialsProvider für diese Identität. Beide lassen einen geteilten Client viele Identitäten bedienen.
Sollte ich Connect- und Response-Timeouts getrennt setzen? Ja. Ein separater Connect-Timeout und ein Response-/Socket-Timeout lassen Sie einen langsamen Connect (Ihre Seite oder keine passende IP) von einer langsamen Antwort (dem Ziel) unterscheiden, die zentrale Unterscheidung beim Diagnostizieren von Timeouts. Ein einzelner stumpfer Timeout verbirgt, welche Phase scheiterte.
OkHttp oder Apache HttpClient fürs Scraping? Beides funktioniert gut. OkHttp ist leichter und hat eine sauberere API; Apache HttpClient ist konfigurierbarer und lange etabliert. Wählen Sie nach Ergonomie und bestehenden Abhängigkeiten; das Proxy-Setup und die obigen Fallstricke gelten für beide.
Fazit
Java plus Residential Proxies ist solide, sobald Sie die Regeln jedes Clients respektieren: liefern Sie Proxy-Zugangsdaten über den richtigen Mechanismus (OkHttps proxyAuthenticator, Apaches CredentialsProvider), nie inline in einer URL; teilen Sie einen langlebigen Client und leiten Sie Identitäts-Varianten davon ab; heben Sie Apaches Pro-Route-Pool-Limit auf Ihre Nebenläufigkeit an; verbrauchen und schließen Sie Antworten immer, damit Verbindungen in den Pool zurückkehren; und trennen Sie Connect- von Response-Timeouts. Variieren Sie den Proxy-Benutzernamen für Geo oder Session, und begrenzen Sie die Nebenläufigkeit pro Host.
Machen Sie das richtig, und beide Clients performen so, wie die JVM es sollte. Richten Sie sie auf das Residential-Gateway, und denken Sie daran, dass die Poolqualität bestimmt, wie oft Sie überhaupt retryen (IP-Reputation). Die Preisseite hat die Pro-GB-Tarife, um es gegen Ihre eigenen Ziele zu testen.