返回博客

在PHP中通过cURL和Guzzle HTTP客户端设置代理:带代码示例的完整指南

完整的PHP代理设置指南,通过cURL和Guzzle HTTP客户端,包含代码示例、IP轮换和选择解析及自动化代理类型的建议。

📅2026年8月12日
```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_PROXYCURLOPT_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 代码: $httpCode\n";
echo $response;

在这里,CURLOPT_PROXY 接受格式为 host:port 的字符串。对于通过 HTTP 代理的 HTTPS 流量,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 连接到代理的超时

cURL 中的身份验证和 SOCKS5

大多数商业代理需要通过用户名和密码进行身份验证。此外,当需要完全匿名时,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);

cURL 中的 SOCKS5 代理

<?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)。与原始 cURL 相比,它提供了更方便和可读的 API,支持异步请求、中间件和方便的错误处理。

通过 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();

Guzzle 中的代理 URL 格式: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'],
    ],
]);

Guzzle 中的 SOCKS5

<?php

use GuzzleHttp\Client;

// Guzzle 中的 SOCKS5 需要额外的包
// 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 验证开启,否则您将面临中间人攻击的风险。

Guzzle 中的代理轮换

在 Guzzle 中,轮换代理可以通过中间件方便地实现 — 这是一个拦截每个请求并为其添加代理的中间层。这比在每次调用中手动传递代理更优雅。

<?php

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

/**
 * 自动轮换代理的中间件
 */
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++;

            // 将代理添加到请求属性中
            // (通过选项传递给处理程序)
            return $request->withHeader('X-Selected-Proxy', $proxy);
        }
    );
}

// 代理列表
$proxies = [
    'http://user1:[email protected]:8080',
    'http://user2:[email protected]:8080',
    'http://user3:[email protected]:8080',
];

// 简单的方式: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 "成功 [{$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
性能 ✅ 稍快(没有包装) ✅ 可比,有异步
异步请求 ⚠️ curl_multi(复杂) ✅ 内置支持
中间件 / 钩子 ❌ 没有 ✅ HandlerStack、中间件
SOCKS5 支持 ✅ 原生支持 ✅ 通过 curl handler
错误处理 ⚠️ 手动错误代码 ✅ 带类型的异常
在 Laravel/Symfony 中的集成 ⚠️ 手动集成 ✅ 原生支持
适合于 简单脚本,无依赖 基于框架的项目,复杂逻辑

推荐: 如果您正在编写简单的脚本或在没有 Composer 的主机上工作 — 使用 cURL。对于 Laravel、Symfony 或任何现代 PHP 应用程序中的项目 — Guzzle 将更加方便,并提供更多扩展的可能性。

🚀 启动前的快速检查清单

  • ✅ 检查代理是否工作:curl -x http://login:pass@host:port https://httpbin.org/ip
  • ✅ 设置超时:timeoutconnect_timeout
  • ✅ 添加真实的 User-Agent
  • ✅ 在请求之间实现暂停(至少 500 毫秒)
  • ✅ 考虑错误处理和回退到其他代理
  • ✅ 对于 SOCKS5 使用 CURLPROXY_SOCKS5_HOSTNAME(而不是 SOCKS5)
  • ✅ 在生产环境中不要禁用 SSL 验证

结论

在 PHP 中设置代理并不复杂,只要知道正确的选项和格式。对于基本任务,cURL 结合 CURLOPT_PROXYCURLOPT_PROXYUSERPWD 就足够了。对于框架项目,Guzzle 提供了更方便的 API、类型化异常和中间件支持以进行代理轮换。

可靠工作的关键原则:使用通过代理解析 DNS 的 SOCKS5(CURLPROXY_SOCKS5_HOSTNAME),始终设置超时,在请求之间添加暂停,并实现代理轮换以处理大规模任务。错误处理和回退到其他代理是任何生产代码的必备元素。

通过遵循这些原则,您将能够有效地在 PHP 中使用代理,确保您的数据收集和自动化任务顺利进行。

```