خانه/مقالات/راهنمای عملی وب اسکریپینگ با پراکسی در Java — OkHttp و Apache HttpClient
برنامه نویسی
پروکسی و چرخش IP
Playwright
برگشت به صفحه مقاله ها
راهنمای عملی وب اسکریپینگ با پراکسی در Java — OkHttp و Apache HttpClient

راهنمای عملی وب اسکریپینگ با پراکسی در Java — OkHttp و Apache HttpClient

این راهنمای فارسی جامع نشان می‌دهد چگونه در پروژه‌های Java با استفاده از OkHttp و Apache HttpClient پراکسی‌ها را یکپارچه، احراز هویت کنید و در سه فرم رایج (لیست IP، گیت‌وی و API) آن‌ها را مدیریت و چرخش دهید — همراه با مثال‌های کد، نکات امنیتی، مدیریت خطا و بهترین‌روش‌های عملی.
امیر حسین حسینیان
امیر حسین حسینیان
1404-09-18

مقدمه

در این مقاله قدم‌به‌قدم یاد می‌گیریم چگونه در پروژه‌های Java که از OkHttp یا Apache HttpClient برای وب اسکریپینگ استفاده می‌کنند، پراکسی را یکپارچه کنیم و روش‌های مختلف چرخش (rotate) پراکسی را پیاده‌سازی کنیم. در پایان این مطلب شما با سه فرم رایج پراکسی (لیست IP، دروازه/گِیت‌وی و API پراکسی) آشنا می‌شوید، نحوهٔ احراز هویت پراکسی را می‌دانید و مثال‌های عملی و نکات مربوط به مانیتورینگ، امنیت و مدیریت خطا را خواهید دید.

استفاده از Proxy IPها با OkHttp

ایدهٔ کلی: یک نمونهٔ Proxy از بستهٔ java.net می‌سازیم و آن را به OkHttpClient.Builder می‌دهیم تا هر درخواست از طریق آن پراکسی ارسال شود. این روش برای لیست سادهٔ پراکسی یا گیت‌وی‌هایی که آدرس ثابت دارند مناسب است.

مراحل عملی:

  1. ساخت InetSocketAddress با hostname و port پراکسی.
  2. ساخت یک Proxy با نوع Proxy.Type.HTTP.
  3. تنظیم .proxy() روی OkHttpClient.Builder و ساخت کلاینت.

مثال ساده (OkHttp):

import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class OkHttpProxyExample {
    public static void main(String[] args) throws Exception {
        String proxyHost = "111.43.105.50";
        int proxyPort = 9091;

        Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort));

        OkHttpClient client = new OkHttpClient.Builder()
                .proxy(proxy)
                .readTimeout(30, TimeUnit.SECONDS)
                .build();

        Request request = new Request.Builder()
                .url("https://httpbin.org/ip")
                .build();

        Response response = client.newCall(request).execute();
        System.out.println("Response body: " + response.body().string());
        response.close();
    }
}

توضیح اجزای مهم:

  • proxyHost / proxyPort: ورودی که آدرس پراکسی را مشخص می‌کند.
  • Proxy proxy: آبجکت پراکسی که به کلاینت داده می‌شود تا همهٔ درخواست‌ها از آن عبور کنند.
  • Request: درخواست HTTP که ارسال می‌شود. خروجی این بلوک، پاسخ (Response) است که متن بدنه را با response.body().string() می‌خوانیم.

نکات عملی و بهترین‌روش‌ها: همیشه response.close() را فراخوانی کنید تا منابع آزاد شوند. از متغیرهای محیطی یا سرویس‌های امن برای نگهداری host/port ناتیفیکیشن استفاده کنید، نه قرار دادن credentialها در کد.

استفاده از Proxy با Apache HttpClient (نمونهٔ async)

ایدهٔ کلی مشابه است ولی در Apache HttpClient معمولاً یک HttpHost برای پراکسی می‌سازیم و آن را به builder می‌دهیم.

import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;

public class ApacheProxyExample {
    public static void main(String[] args) throws Exception {
        HttpHost proxyHost = new HttpHost("http", "proxy.example.com", 80);

        CloseableHttpAsyncClient client = HttpAsyncClients.custom()
                .setProxy(proxyHost)
                .build();

        client.start();
        var request = SimpleRequestBuilder.get("https://httpbin.org/ip").build();
        SimpleHttpResponse response = client.execute(request, null).get();
        System.out.println("Response body: " + response.getBodyText());
        client.close();
    }
}

توضیح: HttpHost نشان‌دهندهٔ scheme/host/port پراکسی است و setProxy آن را به کلاینت متصل می‌کند.

احراز هویت پراکسی (Proxy Authentication)

بسیاری از پراکسی‌ها نیاز به username/password دارند. در OkHttp با یک Authenticator و در Apache با یک CredentialsProvider این کار انجام می‌شود. توجه کنید که پاسخ‌های پراکسی معمولاً کد وضعیت 407 (Proxy Authentication Required) برمی‌گردانند؛ در برخی منابع 401 ذکر شده اما برای پراکسی، 407 رایج‌تر است.

مثال احراز هویت در OkHttp:

import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.concurrent.TimeUnit;
import okhttp3.Authenticator;
import okhttp3.Credentials;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.Route;

public class OkHttpAuthProxy {
    public static void main(String[] args) throws Exception {
        String proxyHost = "example.com";
        int proxyPort = 80;
        String username = "USERNAME";
        String password = "PASSWORD";

        Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort));

        Authenticator authenticator = new Authenticator() {
            @Override
            public Request authenticate(Route route, Response response) {
                String credential = Credentials.basic(username, password);
                return response.request().newBuilder()
                        .header("Proxy-Authorization", credential)
                        .build();
            }
        };

        OkHttpClient client = new OkHttpClient.Builder()
                .proxy(proxy)
                .proxyAuthenticator(authenticator)
                .readTimeout(30, TimeUnit.SECONDS)
                .build();

        Request request = new Request.Builder().url("https://httpbin.org/ip").build();
        Response response = client.newCall(request).execute();
        System.out.println("Response body: " + response.body().string());
        response.close();
    }
}

توضیح خط‌به‌خط:

  • Credentials.basic: ترکیب username/password را به قالب base64 برای هدر می‌سازد.
  • در authenticate هدر Proxy-Authorization به درخواست اضافه می‌شود تا پراکسی بتواند هویت را تأیید کند.

مثال معادل در Apache HttpClient با CredentialsProvider (خطوط اصلی):

HttpHost proxyHost = new HttpHost("http", proxyHostname, proxyPort);
CredentialsProvider credsProvider = CredentialsProviderBuilder.create()
        .add(new AuthScope(proxyHostname, proxyPort), BRIGHTDATA_USERNAME, BRIGHTDATA_PASSWORD.toCharArray())
        .build();
CloseableHttpAsyncClient client = HttpAsyncClients.custom()
        .setProxy(proxyHost)
        .setDefaultCredentialsProvider(credsProvider)
        .build();

نکات امنیتی: هرگز نام‌کاربری/رمز را مستقیم در سورس کنترل نگه ندارید؛ از متغیرهای محیطی، فایل‌های پیکربندی با مجوز محدود یا سرویس‌های محرمانه (vault) استفاده کنید. همچنین همیشه کانکشن HTTPS را بین شما و سرویس پراکسی حفظ کنید تا credentials در شبکه لو نرود.

فرمت‌های رایج پراکسی

سه روش مرسوم برای دسترسی به پراکسی وجود دارد:

  • چرخش از لیست IP — شما فهرستی از سرورها دارید و خودتان آن‌ها را مدیریت می‌کنید.
  • پراکسی گِیت‌وی (Proxy Gateway) — یک آدرس ثابت می‌گیرید و ارائه‌دهنده خودش انتخاب و چرخش IP را انجام می‌دهد.
  • API پراکسی — شما آدرس هدف را به API ارسال می‌کنید و پاسخ HTML را دریافت می‌کنید؛ مدیریت پراکسی کاملاً در سمت ارائه‌دهنده است.

پراکسی یک: چرخش از لیست IP (Rotating Through Proxy IP List)

ایدهٔ کلی: یک لیست از URLهای پراکسی دارید (مثلاً scheme://username:password@host:port) و برای هر درخواست یکی را به‌صورت تصادفی یا با استراتژی Round-Robin انتخاب می‌کنید.

نمونهٔ ساده برای انتخاب تصادفی و استفادهٔ OkHttp:

import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import okhttp3.Authenticator;
import okhttp3.Credentials;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.Route;

public class ProxyRotation {
    static String[] proxyList = {
        "http://username:password@85.237.57.198:20000",
        "http://username:password@85.237.57.198:21000",
        "http://username:password@85.237.57.198:22000",
        "http://username:password@85.237.57.198:23000"
    };

    static String getRandomProxy(String[] list) {
        int rnd = new Random().nextInt(list.length);
        return list[rnd];
    }

    public static void main(String[] args) throws Exception {
        URL proxyUrl = new URL(getRandomProxy(proxyList));
        String userInfo = proxyUrl.getUserInfo(); // username:password
        int idx = userInfo.indexOf(":");
        String proxyHost = proxyUrl.getHost();
        int proxyPort = proxyUrl.getPort();
        String username = userInfo.substring(0, idx);
        String password = userInfo.substring(idx + 1);

        Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort));

        Authenticator authenticator = new Authenticator() {
            @Override
            public Request authenticate(Route route, Response response) {
                return response.request().newBuilder()
                        .header("Proxy-Authorization", Credentials.basic(username, password))
                        .build();
            }
        };

        OkHttpClient client = new OkHttpClient.Builder()
                .proxy(proxy)
                .proxyAuthenticator(authenticator)
                .readTimeout(30, TimeUnit.SECONDS)
                .build();

        Request request = new Request.Builder().url("https://httpbin.org/ip").build();
        Response response = client.newCall(request).execute();
        System.out.println("Response body: " + response.body().string());
        response.close();
    }
}

محدودیت‌ها و توصیه‌ها:

  • در مقیاس بزرگ باید سلامتی (health) هر IP را مانیتور کنید: نرخ خطا، زمان پاسخ، تعداد banها و نرخ رد درخواست‌ها.
  • در صورت مشاهدهٔ الگوی ban یا تعداد 429/403 افزایش، آن IP را موقتاً از روتیشن خارج کنید.
  • برای آمار و مانیتورینگ از صف‌ها یا دیتابیس سبک (Redis، SQLite) و هش‌برداری استفاده کنید تا تصمیم‌های انتخاب پراکسی مبتنی بر داده باشند.

پراکسی دو: استفاده از Proxy Gateway

ایدهٔ کلی: ارائه‌دهنده یک آدرس ثابت و جزئیات ورود به شما می‌دهد و خودش مدیریت استخر پراکسی را انجام می‌دهد. شما فقط یک پراکسی کانفیگ می‌کنید و درخواست‌ها را از طریق آن می‌فرستید.

مزایا:

  • سادگی پیاده‌سازی (نیاز به مدیریت لیست پراکسی ندارید).
  • ارائه‌دهنده می‌تواند هوشمندانه IPها را انتخاب و پاک‌سازی کند.

معایب:

  • وابستگی به کیفیت ارائه‌دهنده و نرخ هزینه.
  • کاهش کنترل روی انتخاب IP و رفتار دقیق روتیشن.

مثال ادغام BrightData (OkHttp):

// ساخت Proxy با hostname و port گیت‌وی و قرار دادن نام کاربری/پسورد در هدر
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("zproxy.lum-superproxy.io", 22225));
// سپس همان الگوی Authenticator برای اضافه کردن Proxy-Authorization و ساخت OkHttpClient استفاده می‌شود.

نکات عملی: هنگام استفاده از gateway، بررسی کنید که ارائه‌دهنده قابلیت‌هایی مثل sticky sessions (برای حفظ session با یک IP) یا پارامترهای منطقه/ISP را ارائه می‌دهد یا خیر.

پراکسی سه: استفاده از Proxy API Endpoint

ایدهٔ کلی: به‌جای ارسال درخواست مستقیم به سایت هدف از طرف شما، شما یک درخواست به API ارائه‌دهندهٔ پراکسی می‌زنید (معمولاً شامل آدرس هدف و کلید API) و آن‌ها محتوای HTML را برای شما بازمی‌گردانند. این روش مدیریتی‌ترین فرم است.

مثال ساده با OkHttp (ارسال URL هدف به API پراکسی):

import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class ScrapeOpsProxyAPI {
    public static void main(String[] args) throws Exception {
        String SCRAPEOPS_API_KEY = "your_api_key";
        String targetUrl = "https://httpbin.org/ip";
        String proxyAPIUrl = String.format("https://proxy.scrapeops.io/v1?api_key=%s&url=%s", SCRAPEOPS_API_KEY, targetUrl);

        OkHttpClient client = new OkHttpClient.Builder()
                .readTimeout(30, TimeUnit.SECONDS)
                .build();

        Request request = new Request.Builder().url(proxyAPIUrl).build();
        Response response = client.newCall(request).execute();
        System.out.println("Response body: " + response.body().string());
        response.close();
    }
}

مزایا: ساده‌ترین پیاده‌سازی، ارائه‌دهنده مسئول تمامی پیچیدگی‌های چرخش، ریت لیمیت و header management است.

معایب و احتیاط‌ها: این روش به معنی فرستادن URL هدف و محتوا به سرویس سوم شخص است؛ از نظر حریم خصوصی و قوانین ممکن است محدود‌کننده باشد. بعلاوه باید به نرخ استفاده و هزینه توجه کنید.

نکات مرتبط با عملکرد، خطا و پایداری

  • Timeouts: همیشه زمان‌برخوانی (readTimeout) و کانکشن‌تایم‌ها را تنظیم کنید تا Threads بلاک نشوند.
  • Retries: مکانیزم retry با backoff نمایی برای خطاهای گذرا اضافه کنید، اما برای خطاهای احراز هویت یا بلوکه‌شدن (ban) retry بی‌فایدست.
  • همزمانی: برای کار در مقیاس، از poolهای کانکشن/async clients استفاده کنید تا سربار threadها کاهش یابد.
  • مانیتورینگ: هر IP یا gateway را متر کنید (latency، error-rate، success-rate) و بر اساس آن تصمیم بگیرید که کدام منابع را حذف یا اضافه کنید.
  • قوانین و اخلاق: پیش از اسکریپ کردن یک سایت قوانین robots.txt و شرایط استفاده را بررسی کنید و از قوانین ملی و بین‌المللی پیروی کنید.

جمع‌بندی

خلاصهٔ عملی: برای شروع سریع و ساده از Proxy Gateway یا Proxy API استفاده کنید؛ اگر به کنترل بیشتری نیاز دارید و می‌خواهید هزینه را کاهش دهید، از لیست پراکسی با سیستم مانیتورینگ و چرخش استفاده کنید. در هر حالت احراز هویت را با مکانیزم امن پیاده‌سازی کنید و هشدارها، timeouts و retryهای مناسب را اضافه نمایید. به‌علاوه مانیتورینگ پیوستهٔ کیفیت پراکسی‌ها برای پایدار نگه داشتن اسکریپرهای تولیدی ضروری است.

اگر سوال خاصی در مورد یک سناریو دارید (مثلاً اسکریپ کردن سایت خاصی با نیاز به login یا با استفاده از پراکسی موبایل)، بگویید تا مثال اختصاصی‌تر و تنظیمات بهینه ارائه شود.

مقاله‌های مرتبط