بازگشت به وبلاگ

تنظیم پروکسی در PHP با استفاده از cURL و Guzzle HTTP Client: راهنمای کامل با مثال‌های کد

راهنمای کامل تنظیم پروکسی در PHP از طریق cURL و Guzzle HTTP Client — با مثال‌های کد، چرخش IP و نکاتی برای انتخاب نوع پروکسی برای پارسینگ و اتوماسیون.

📅۲۱ مرداد ۱۴۰۵
```html

اگر شما یک پارسر می‌نویسید، جمع‌آوری داده‌ها را اتوماسیون می‌کنید یا محدودیت‌های IP را دور می‌زنید — بدون پروکسی در PHP نمی‌توانید. در این راهنما دو ابزار اصلی را بررسی می‌کنیم: cURL و کتابخانه محبوب Guzzle HTTP Client — با مثال‌های آماده کد که می‌توانید به راحتی در پروژه خود استفاده کنید.

چرا پروکسی در پروژه‌های PHP

PHP یکی از رایج‌ترین زبان‌ها برای اتوماسیون سرور باقی مانده است و وظایفی که نیاز به پروکسی دارند، به طور مداوم وجود دارند. در اینجا سناریوهای اصلی وجود دارد که بدون پروکسی نمی‌توان انجام داد:

  • پارس کردن وب‌سایت‌ها و بازارها — Wildberries، Ozon، Avito، AliExpress IP را پس از چند ده درخواست مسدود می‌کنند. پروکسی‌ها اجازه می‌دهند بار را بین آدرس‌های مختلف توزیع کنید و 403/429 دریافت نکنید.
  • جمع‌آوری داده‌ها از منابع وابسته به جغرافیا — قیمت‌ها، نتایج موتورهای جستجو، محتوا ممکن است بسته به کشور متفاوت باشد. پروکسی‌ها با موقعیت جغرافیایی مناسب این مشکل را حل می‌کنند.
  • دور زدن محدودیت نرخ — بسیاری از API‌ها تعداد درخواست‌ها از یک IP را محدود می‌کنند. چرخش پروکسی‌ها اجازه می‌دهد این محدودیت‌ها را دور بزنید.
  • آزمایش محتوای وابسته به جغرافیا — بررسی اینکه وب‌سایت برای کاربران از کشورهای مختلف چگونه به نظر می‌رسد، بدون خروج از دفتر.
  • ناشناس بودن در هنگام اتوماسیون — پنهان کردن IP واقعی سرور در هنگام درخواست‌های انبوه به منابع خارجی.
  • نظارت بر رقبا — جمع‌آوری منظم قیمت‌ها، تنوع، تبلیغات از وب‌سایت‌های رقبای بدون خطر مسدود شدن.

در PHP برای درخواست‌های HTTP معمولاً از دو ابزار استفاده می‌شود: گسترش داخلی cURL و کتابخانه Guzzle HTTP Client. هر دو از پروکسی‌های HTTP، HTTPS، SOCKS4 و SOCKS5 پشتیبانی می‌کنند — هر کدام را به تفصیل بررسی می‌کنیم.

کدام نوع پروکسی را برای PHP انتخاب کنیم

قبل از نوشتن کد، مهم است که بفهمید کدام نوع پروکسی برای وظیفه شما مناسب است. انواع مختلف ویژگی‌های متفاوتی از نظر سرعت، قابلیت اطمینان و درجه ناشناس بودن دارند.

نوع پروکسی سرعت ناشناس بودن بهترین برای
پروکسی‌های دیتاسنتر بسیار بالا متوسط پارس کردن بدون سیستم‌های ضد ربات سخت، درخواست‌های API
پروکسی‌های مقیم متوسط بالا پارس کردن وب‌سایت‌های محافظت شده، بازارها، داده‌های جغرافیایی
پروکسی‌های موبایل متوسط حداکثری وب‌سایت‌های با حفاظت سخت ضد ربات، شبکه‌های اجتماعی

برای اکثر وظایف پارس داده‌ها از بازارهایی مانند Wildberries یا Ozon، پروکسی‌های مقیم بهترین انتخاب خواهند بود — IP‌های آن‌ها متعلق به کاربران واقعی خانگی است، که درخواست‌ها را تقریباً غیرقابل تشخیص از ترافیک مرورگر معمولی می‌کند. پروکسی‌های دیتاسنتر در جایی که سرعت مهم‌تر از نامحسوس بودن است و حفاظت ضعیف است، مناسب هستند.

هر سه نوع پروکسی از پروتکل‌های HTTP/HTTPS و SOCKS5 پشتیبانی می‌کنند، بنابراین از نظر کد — تنظیمات یکسان است. تفاوت فقط در رشته اتصال است.

پروکسی در cURL: تنظیمات پایه

گسترش cURL به طور پیش‌فرض در PHP موجود است و روش استانداردی برای انجام درخواست‌های HTTP در سرور است. برای اتصال پروکسی از دو گزینه اصلی استفاده می‌شود: CURLOPT_PROXY و CURLOPT_PROXYTYPE.

پروکسی HTTP بدون احراز هویت

<?php

$ch = curl_init();

curl_setopt_array($ch, [
    CURLOPT_URL            => 'https://httpbin.org/ip',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_TIMEOUT        => 30,

    // آدرس پروکسی را مشخص می‌کنیم
    CURLOPT_PROXY          => '185.199.100.1:8080',

    // نوع پروکسی: HTTP (به طور پیش‌فرض)
    CURLOPT_PROXYTYPE      => CURLPROXY_HTTP,
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo "HTTP Code: $httpCode\n";
echo $response;

در اینجا CURLOPT_PROXY رشته‌ای در فرمت host:port را می‌پذیرد. برای ترافیک HTTPS از طریق پروکسی HTTP، cURL به طور خودکار از روش CONNECT — تونل‌سازی استفاده می‌کند، بنابراین محتوای درخواست رمزگذاری شده باقی می‌ماند.

گزینه‌های مفید cURL برای کار با پروکسی

گزینه توضیحات
CURLOPT_PROXY آدرس سرور پروکسی (host:port)
CURLOPT_PROXYTYPE نوع: CURLPROXY_HTTP, CURLPROXY_SOCKS4, CURLPROXY_SOCKS5
CURLOPT_PROXYUSERPWD نام کاربری و رمز عبور به فرمت user:password
CURLOPT_HTTPPROXYTUNNEL فعال کردن تونل‌سازی از طریق HTTP CONNECT
CURLOPT_SSL_VERIFYPEER بررسی گواهی SSL (false — غیرفعال کردن)
CURLOPT_TIMEOUT تایم‌اوت درخواست به ثانیه
CURLOPT_CONNECTTIMEOUT تایم‌اوت اتصال به پروکسی

احراز هویت و SOCKS5 در cURL

بیشتر پروکسی‌های تجاری نیاز به احراز هویت با نام کاربری و رمز عبور دارند. همچنین SOCKS5 — پروتکل ترجیحی است زمانی که نیاز به ناشناس بودن کامل دارید، زیرا هدرهایی مانند X-Forwarded-For را اضافه نمی‌کند که می‌تواند استفاده از پروکسی را فاش کند.

پروکسی HTTP با نام کاربری و رمز عبور

<?php

$proxyHost = '185.199.100.1';
$proxyPort = '8080';
$proxyUser = 'your_login';
$proxyPass = 'your_password';

$ch = curl_init();

curl_setopt_array($ch, [
    CURLOPT_URL            => 'https://httpbin.org/ip',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 30,
    CURLOPT_CONNECTTIMEOUT => 10,

    // پروکسی با احراز هویت
    CURLOPT_PROXY          => "$proxyHost:$proxyPort",
    CURLOPT_PROXYTYPE      => CURLPROXY_HTTP,
    CURLOPT_PROXYUSERPWD   => "$proxyUser:$proxyPass",

    // تونل‌سازی برای HTTPS
    CURLOPT_HTTPPROXYTUNNEL => true,

    // هدرهای مرورگر برای پنهان‌سازی
    CURLOPT_USERAGENT      => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    CURLOPT_HTTPHEADER     => [
        'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Language: ru-RU,ru;q=0.9,en;q=0.8',
    ],
]);

$response = curl_exec($ch);

if (curl_errno($ch)) {
    echo 'خطا در cURL: ' . curl_error($ch);
} else {
    echo $response;
}

curl_close($ch);

پروکسی SOCKS5 در cURL

<?php

$ch = curl_init();

curl_setopt_array($ch, [
    CURLOPT_URL            => 'https://httpbin.org/ip',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 30,

    CURLOPT_PROXY          => '185.199.100.1:1080',

    // SOCKS5 با حل DNS از طریق پروکسی (توصیه می‌شود!)
    CURLOPT_PROXYTYPE      => CURLPROXY_SOCKS5_HOSTNAME,

    // احراز هویت (در صورت نیاز)
    CURLOPT_PROXYUSERPWD   => 'login:password',
]);

$response = curl_exec($ch);
curl_close($ch);

echo $response;

💡 مهم: SOCKS5 در مقابل SOCKS5_HOSTNAME

از CURLPROXY_SOCKS5_HOSTNAME به جای CURLPROXY_SOCKS5 استفاده کنید. تفاوت این است که در SOCKS5_HOSTNAME درخواست‌های DNS نیز از طریق پروکسی عبور می‌کنند، که از نشت DNS جلوگیری می‌کند و ناشناس بودن را افزایش می‌دهد. در SOCKS5 معمولی DNS به صورت محلی حل می‌شود — این می‌تواند IP واقعی شما را فاش کند.

چرخش پروکسی در cURL

یک پروکسی به سرعت در صورت درخواست‌های انبوه مسدود خواهد شد. استراتژی صحیح — داشتن یک استخر پروکسی و چرخش آن‌ها است. در اینجا یک پیاده‌سازی ساده اما کاربردی وجود دارد:

<?php

class ProxyRotator
{
    private array $proxies;
    private int $currentIndex = 0;

    public function __construct(array $proxies)
    {
        $this->proxies = $proxies;
        shuffle($this->proxies); // برای ترتیب تصادفی مخلوط می‌کنیم
    }

    /**
     * دریافت پروکسی بعدی از استخر
     */
    public function getNext(): string
    {
        $proxy = $this->proxies[$this->currentIndex];
        $this->currentIndex = ($this->currentIndex + 1) % count($this->proxies);
        return $proxy;
    }

    /**
     * دریافت پروکسی تصادفی
     */
    public function getRandom(): string
    {
        return $this->proxies[array_rand($this->proxies)];
    }
}

// لیست پروکسی به فرمت login:password@host:port
$proxyList = [
    'user1:[email protected]:8080',
    'user2:[email protected]:8080',
    'user3:[email protected]:8080',
    'user4:[email protected]:8080',
];

$rotator = new ProxyRotator($proxyList);

/**
 * تابع درخواست با انتخاب خودکار پروکسی
 */
function fetchWithProxy(string $url, ProxyRotator $rotator): ?string
{
    $proxyStr = $rotator->getNext();

    // رشته پروکسی را پارس می‌کنیم
    preg_match('/^(.+):(.+)@(.+):(\d+)$/', $proxyStr, $m);
    [, $user, $pass, $host, $port] = $m;

    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL            => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 20,
        CURLOPT_CONNECTTIMEOUT => 8,
        CURLOPT_PROXY          => "$host:$port",
        CURLOPT_PROXYTYPE      => CURLPROXY_HTTP,
        CURLOPT_PROXYUSERPWD   => "$user:$pass",
        CURLOPT_USERAGENT      => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
        CURLOPT_SSL_VERIFYPEER => false,
    ]);

    $response = curl_exec($ch);
    $error    = curl_error($ch);
    curl_close($ch);

    if ($error) {
        error_log("پروکسی $host:$port در دسترس نیست: $error");
        return null;
    }

    return $response;
}

// مثال: پارس کردن 10 صفحه با چرخش پروکسی
$urls = [
    'https://example.com/page/1',
    'https://example.com/page/2',
    'https://example.com/page/3',
    // ...
];

foreach ($urls as $url) {
    $html = fetchWithProxy($url, $rotator);
    if ($html) {
        echo "دریافت شده: " . strlen($html) . " بایت\n";
    }
    usleep(500000); // وقفه 0.5 ثانیه بین درخواست‌ها
}

به usleep(500000) توجه کنید — وقفه بین درخواست‌ها به شدت مهم است. حتی با چرخش پروکسی، درخواست‌های بسیار مکرر می‌توانند منجر به مسدود شدن بر اساس الگوهای رفتاری شوند. فاصله پیشنهادی — از 500 میلی‌ثانیه تا 2 ثانیه بسته به وب‌سایت.

پروکسی در Guzzle HTTP Client: تنظیمات پایه

Guzzle یک کتابخانه قدرتمند PHP برای درخواست‌های HTTP است که در اکثر فریم‌ورک‌های مدرن PHP (Laravel، Symfony) استفاده می‌شود. این کتابخانه API‌ای راحت‌تر و خواناتر نسبت به cURL خام ارائه می‌دهد، از درخواست‌های غیرهمزمان، middleware و مدیریت راحت خطاها پشتیبانی می‌کند.

نصب از طریق Composer

composer require guzzlehttp/guzzle

پروکسی HTTP بدون احراز هویت

<?php

require 'vendor/autoload.php';

use GuzzleHttp\Client;

$client = new Client([
    // URL پایه (اختیاری)
    'base_uri' => 'https://httpbin.org',

    // تنظیمات پیش‌فرض برای تمام درخواست‌ها
    'timeout'  => 30,
    'connect_timeout' => 10,

    // پروکسی برای تمام درخواست‌های کلاینت
    'proxy' => 'http://185.199.100.1:8080',
]);

$response = $client->get('/ip');

echo $response->getStatusCode() . "\n";  // 200
echo $response->getBody() . "\n";        // {"origin": "185.199.100.1"}

در Guzzle پروکسی از طریق گزینه proxy به فرمت URL مشخص می‌شود. این روش نسبت به cURL شهودی‌تر است. پروکسی را می‌توان هم در سطح کلاینت (برای تمام درخواست‌ها) و هم برای هر درخواست به طور جداگانه مشخص کرد.

پروکسی HTTP با احراز هویت

<?php

use GuzzleHttp\Client;

$login    = 'your_login';
$password = 'your_password';
$host     = '185.199.100.1';
$port     = '8080';

$client = new Client([
    'timeout' => 30,
]);

// پروکسی با احراز هویت به فرمت URL مشخص می‌شود
$response = $client->get('https://httpbin.org/ip', [
    'proxy' => "http://$login:$password@$host:$port",
    'headers' => [
        'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Accept'     => 'text/html,application/xhtml+xml,application/xml;q=0.9',
    ],
]);

echo $response->getBody();

فرمت URL پروکسی در Guzzle: scheme://user:password@host:port. برای پروکسی HTTP از http:// و برای SOCKS5 از socks5:// استفاده می‌کنیم.

کار پیشرفته با Guzzle: تایم‌اوت‌ها، هدرها، SOCKS5

سناریوهای پیچیده‌تری را بررسی می‌کنیم: پروکسی‌های مختلف برای HTTP و HTTPS، SOCKS5، غیرفعال کردن بررسی SSL و تنظیم هدرها برای شبیه‌سازی مرورگر.

پروکسی‌های مختلف برای HTTP و HTTPS

<?php

use GuzzleHttp\Client;

$client = new Client([
    'timeout' => 30,
    'proxy'   => [
        // پروکسی برای درخواست‌های HTTP
        'http'  => 'http://login:[email protected]:8080',
        // پروکسی برای درخواست‌های HTTPS
        'https' => 'http://login:[email protected]:8080',
        // استثناها — این میزبان‌ها بدون پروکسی می‌روند
        'no'    => ['localhost', '127.0.0.1', '.internal.corp'],
    ],
]);

SOCKS5 در Guzzle

<?php

use GuzzleHttp\Client;

// برای SOCKS5 در Guzzle به یک بسته اضافی نیاز دارید
// composer require clue/socks-react (یا از curl handler استفاده کنید)

$client = new Client([
    'timeout' => 30,
    'proxy'   => 'socks5://login:[email protected]:1080',
    'curl'    => [
        CURLOPT_PROXYTYPE => CURLPROXY_SOCKS5_HOSTNAME,
    ],
]);

$response = $client->get('https://httpbin.org/ip');
echo $response->getBody();

پیکربندی کامل با هدرهای مرورگر

<?php

use GuzzleHttp\Client;
use GuzzleHttp\RequestOptions;

$client = new Client([
    'timeout'         => 30,
    'connect_timeout' => 10,
    'verify'          => false, // غیرفعال کردن بررسی SSL (برای اشکال‌زدایی)
    'allow_redirects' => [
        'max'       => 5,
        'strict'    => false,
        'referer'   => true,
        'protocols' => ['http', 'https'],
    ],
    'headers' => [
        'User-Agent'      => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
        'Accept'          => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
        'Accept-Language' => 'ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7',
        'Accept-Encoding' => 'gzip, deflate, br',
        'Cache-Control'   => 'no-cache',
        'Connection'      => 'keep-alive',
    ],
]);

$response = $client->get('https://example.com/catalog', [
    'proxy' => 'http://login:[email protected]:8080',
]);

$statusCode = $response->getStatusCode();
$body       = (string) $response->getBody();

echo "وضعیت: $statusCode, اندازه: " . strlen($body) . " بایت\n";

⚠️ درباره verify => false

غیرفعال کردن بررسی SSL ('verify' => false) تنها در هنگام اشکال‌زدایی یا کار با سرویس‌های داخلی مجاز است. در کد تولید، همیشه بررسی SSL را فعال نگه دارید، در غیر این صورت در برابر حملات نوع man-in-the-middle آسیب‌پذیر خواهید بود.

چرخش پروکسی در Guzzle

در Guzzle، چرخش به راحتی از طریق middleware — لایه میانی که هر درخواست را ضبط کرده و پروکسی را به آن اضافه می‌کند، پیاده‌سازی می‌شود. این روش نسبت به ارسال پروکسی به صورت دستی در هر فراخوانی، زیباتر است.

<?php

use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use Psr\Http\Message\RequestInterface;

/**
 * Middleware برای چرخش خودکار پروکسی
 */
function proxyRotationMiddleware(array $proxies): callable
{
    $index = 0;
    $total = count($proxies);

    return Middleware::mapRequest(
        function (RequestInterface $request) use ($proxies, &$index, $total) {
            $proxy = $proxies[$index % $total];
            $index++;

            // پروکسی را به ویژگی‌های درخواست اضافه می‌کنیم
            // (از طریق options در handler ارسال می‌کنیم)
            return $request->withHeader('X-Selected-Proxy', $proxy);
        }
    );
}

// لیست پروکسی
$proxies = [
    'http://user1:[email protected]:8080',
    'http://user2:[email protected]:8080',
    'http://user3:[email protected]:8080',
];

// گزینه ساده: کلاس-wrapper بر روی Guzzle با چرخش
class GuzzleWithRotation
{
    private Client $client;
    private array $proxies;
    private int $index = 0;

    public function __construct(array $proxies, array $clientConfig = [])
    {
        $this->proxies = $proxies;
        $this->client  = new Client($clientConfig);
    }

    public function get(string $url, array $options = []): \GuzzleHttp\Psr7\Response
    {
        $options['proxy'] = $this->proxies[$this->index % count($this->proxies)];
        $this->index++;

        return $this->client->get($url, $options);
    }

    public function post(string $url, array $options = []): \GuzzleHttp\Psr7\Response
    {
        $options['proxy'] = $this->proxies[$this->index % count($this->proxies)];
        $this->index++;

        return $this->client->post($url, $options);
    }
}

// استفاده
$guzzle = new GuzzleWithRotation($proxies, [
    'timeout' => 30,
    'headers' => [
        'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
    ],
]);

$urls = [
    'https://example.com/product/1',
    'https://example.com/product/2',
    'https://example.com/product/3',
];

foreach ($urls as $url) {
    try {
        $response = $guzzle->get($url);
        echo "OK [{$response->getStatusCode()}]: $url\n";
        sleep(1);
    } catch (\Exception $e) {
        echo "خطا: " . $e->getMessage() . "\n";
    }
}

مدیریت خطاها و اشکال‌زدایی

هنگام کار با پروکسی، خطاها اجتناب‌ناپذیر هستند: پروکسی ممکن است در دسترس نباشد، به آرامی پاسخ دهد، یا وب‌سایت هدف ممکن است خطا برگرداند. مهم است که تمام این وضعیت‌ها را به درستی مدیریت کنید.

مدیریت خطاها در cURL

<?php

function fetchWithErrorHandling(string $url, string $proxy): array
{
    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL            => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 20,
        CURLOPT_CONNECTTIMEOUT => 8,
        CURLOPT_PROXY          => $proxy,
        CURLOPT_PROXYTYPE      => CURLPROXY_HTTP,
    ]);

    $body     = curl_exec($ch);
    $errno    = curl_errno($ch);
    $error    = curl_error($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $totalTime = curl_getinfo($ch, CURLINFO_TOTAL_TIME);
    curl_close($ch);

    // کدهای خطای cURL برای پروکسی
    $proxyErrors = [
        CURLE_COULDNT_CONNECT    => 'عدم توانایی در اتصال به پروکسی',
        CURLE_OPERATION_TIMEDOUT => 'تایم‌اوت اتصال به پروکسی',
        CURLE_RECV_ERROR         => 'خطا در دریافت داده‌ها',
        CURLE_SSL_CONNECT_ERROR  => 'خطای SSL از طریق پروکسی',
    ];

    if ($errno) {
        $message = $proxyErrors[$errno] ?? "خطای cURL #$errno: $error";
        return ['success' => false, 'error' => $message, 'code' => $errno];
    }

    if ($httpCode >= 400) {
        return ['success' => false, 'error' => "HTTP $httpCode", 'code' => $httpCode];
    }

    return [
        'success' => true,
        'body'    => $body,
        'code'    => $httpCode,
        'time'    => round($totalTime, 3),
    ];
}

// استفاده با تلاش‌های مجدد
function fetchWithRetry(string $url, array $proxies, int $maxRetries = 3): ?string
{
    foreach ($proxies as $proxy) {
        for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
            $result = fetchWithErrorHandling($url, $proxy);

            if ($result['success']) {
                echo "موفقیت از طریق $proxy (تلاش $attempt, {$result['time']}ثانیه)\n";
                return $result['body'];
            }

            echo "خطا از طریق $proxy: {$result['error']}\n";

            if ($attempt < $maxRetries) {
                sleep(2); // وقفه قبل از تکرار
            }
        }
    }

    return null; // تمام پروکسی‌ها کار نکردند
}

مدیریت خطاها در Guzzle

<?php

use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\ServerException;
use GuzzleHttp\Exception\ClientException;

$client = new Client(['timeout' => 30]);

$proxies = [
    'http://user1:[email protected]:8080',
    'http://user2:[email protected]:8080',
];

function fetchGuzzleWithFallback(Client $client, string $url, array $proxies): ?string
{
    foreach ($proxies as $proxy) {
        try {
            $response = $client->get($url, [
                'proxy'   => $proxy,
                'timeout' => 20,
            ]);

            return (string) $response->getBody();

        } catch (ConnectException $e) {
            // پروکسی در دسترس نیست یا تایم‌اوت اتصال
            echo "پروکسی در دسترس نیست ($proxy): " . $e->getMessage() . "\n";

        } catch (ClientException $e) {
            // HTTP 4xx — خطای مشتری (403، 404، 429)
            $code = $e->getResponse()->getStatusCode();
            echo "HTTP $code برای $url از طریق $proxy\n";

            if ($code === 429) {
                echo "محدودیت نرخ — وقفه 5 ثانیه\n";
                sleep(5);
            }

        } catch (ServerException $e) {
            // HTTP 5xx — خطای سرور
            echo "خطای سرور: " . $e->getResponse()->getStatusCode() . "\n";

        } catch (RequestException $e) {
            // سایر خطاهای درخواست
            echo "خطای درخواست: " . $e->getMessage() . "\n";
        }
    }

    return null;
}

cURL در مقابل Guzzle: کدام را انتخاب کنیم

هر دو ابزار به خوبی با پروکسی کار می‌کنند، اما نقاط قوت متفاوتی دارند. در اینجا مقایسه‌ای بر اساس معیارهای کلیدی وجود دارد:

معیار cURL Guzzle
نصب ✅ در PHP گنجانده شده است ⚠️ نیاز به Composer دارد
خوانایی کد ⚠️ گزینه‌های زیاد، پرحرفی ✅ API تمیز و راحت
عملکرد ✅ کمی سریع‌تر (بدون پوشش) ✅ مقایسه‌پذیر، دارای async
درخواست‌های غیرهمزمان ⚠️ curl_multi (سخت) ✅ پشتیبانی داخلی
Middleware / hooks ❌ ندارد ✅ HandlerStack، Middleware
پشتیبانی SOCKS5 ✅ بومی ✅ از طریق curl handler
مدیریت خطاها ⚠️ کدهای خطا به صورت دستی ✅ استثناها با نوع‌ها
ادغام در Laravel/Symfony ⚠️ به صورت دستی ✅ پشتیبانی بومی
مناسب برای اسکریپت‌های ساده، بدون وابستگی‌ها پروژه‌های مبتنی بر فریم‌ورک، منطق پیچیده

توصیه: اگر شما یک اسکریپت ساده می‌نویسید یا در هاستینگ بدون Composer کار می‌کنید — از cURL استفاده کنید. برای پروژه‌های مبتنی بر Laravel، Symfony یا هر برنامه PHP مدرن — Guzzle به مراتب راحت‌تر خواهد بود و امکانات بیشتری برای مقیاس‌پذیری فراهم می‌کند.

🚀 چک‌لیست سریع قبل از راه‌اندازی

  • ✅ بررسی کنید که پروکسی کار می‌کند: curl -x http://login:pass@host:port https://httpbin.org/ip
  • ✅ تایم‌اوت‌ها را تنظیم کنید: timeout و connect_timeout
  • ✅ یک User-Agent واقعی اضافه کنید
  • ✅ وقفه‌هایی بین درخواست‌ها (حداقل 500 میلی‌ثانیه) پیاده‌سازی کنید
  • ✅ مدیریت خطاها و fallback به پروکسی دیگر را پیش‌بینی کنید
  • ✅ برای SOCKS5 از CURLPROXY_SOCKS5_HOSTNAME استفاده کنید (نه SOCKS5)
  • ✅ در تولید verify SSL را غیرفعال نکنید

نتیجه‌گیری

تنظیم پروکسی در PHP کار دشواری نیست، اگر گزینه‌ها و فرمت‌های صحیح را بدانید. برای وظایف پایه کافی است از cURL با CURLOPT_PROXY و CURLOPT_PROXYUSERPWD استفاده کنید. برای پروژه‌های مبتنی بر فریم‌ورک، Guzzle API راحت‌تری را ارائه می‌دهد، استثناهای نوع‌دار و پشتیبانی از middleware برای چرخش پروکسی فراهم می‌کند.

اصول کلیدی برای کار مطمئن: از SOCKS5 با حل DNS از طریق پروکسی (CURLPROXY_SOCKS5_HOSTNAME) استفاده کنید، همیشه تایم‌اوت‌ها را تنظیم کنید، وقفه‌هایی بین درخواست‌ها اضافه کنید و چرخش پروکسی را برای وظایف بزرگ پیاده‌سازی کنید. مدیریت خطاها با fallback به پروکسی دیگر — عنصر ضروری هر کد تولیدی است.

```