Laravel 中封装一个支持动态代理池的通用 HTTP 请求类
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Http\Client\RequestException;
class ProxyHttpClient
{
// 代理配置池
protected array $proxies;
// 当前使用的代理索引
protected int $currentProxyIndex = -1;
// 最大重试次数
protected int $maxRetries;
// 重试延迟(毫秒)
protected int $retryDelay;
public function __construct(
array $proxies,
int $maxRetries = 3,
int $retryDelay = 1000
) {
$this->proxies = $proxies;
$this->maxRetries = $maxRetries;
$this->retryDelay = $retryDelay;
}
/**
* 发送 HTTP 请求
*
* @throws RequestException
*/
public function request(
string $method,
string $url,
array $options = []
) {
$attempts = 0;
while ($attempts < $this->maxRetries) {
$proxy = $this->getNextProxy();
try {
return $this->buildClient($proxy)
->{$method}($url, $options)
->throw();
} catch (RequestException $e) {
Log::warning("Proxy request failed [{$proxy['name']}]: {$e->getMessage()}");
// 标记失败代理(可选)
$this->handleFailedProxy($proxy);
$attempts++;
usleep($this->retryDelay * 1000);
}
}
throw new RequestException("All proxies failed after {$this->maxRetries} attempts");
}
/**
* 获取下一个代理(随机策略)
*/
protected function getNextProxy(): array
{
// 简单随机选择(可扩展为其他策略)
return $this->proxies[array_rand($this->proxies)];
// 或者使用轮询策略:
// $this->currentProxyIndex = ($this->currentProxyIndex + 1) % count($this->proxies);
// return $this->proxies[$this->currentProxyIndex];
}
/**
* 构建 HTTP 客户端
*/
protected function buildClient(array $proxy): PendingRequest
{
return Http::withOptions([
'proxy' => $this->formatProxyUrl($proxy),
'connect_timeout' => 10,
'timeout' => 30,
])->withHeaders([
'User-Agent' => 'Your-Custom-UA/1.0',
]);
}
/**
* 格式化代理 URL
*/
protected function formatProxyUrl(array $proxy): string
{
return sprintf(
'%s://%s:%s@%s:%d',
$proxy['protocol'] ?? 'http',
$proxy['username'],
$proxy['password'],
$proxy['host'],
$proxy['port']
);
}
/**
* 处理失败代理(可扩展)
*/
protected function handleFailedProxy(array $proxy): void
{
// 示例:减少权重、标记不可用等
// 可结合数据库记录失败次数
Log::debug("Marking proxy as problematic: {$proxy['name']}");
}
/**
* 快捷方法:GET 请求
*/
public function get(string $url, array $query = [])
{
return $this->request('get', $url, ['query' => $query]);
}
/**
* 快捷方法:POST 请求
*/
public function post(string $url, array $data = [])
{
return $this->request('post', $url, ['form_params' => $data]);
}
}
配置示例(config/proxies.php):
return [
'proxies' => [
[
'name' => 'proxy1',
'protocol' => 'http',
'host' => '1.2.3.4',
'port' => 8080,
'username' => 'user1',
'password' => 'pass1'
],
[
'name' => 'proxy2',
'protocol' => 'https',
'host' => '5.6.7.8',
'port' => 3128,
'username' => 'user2',
'password' => 'pass2'
],
// 更多代理配置...
]
];
使用示例:
// 注入配置
$proxies = config('proxies.proxies');
// 创建客户端实例
$client = new ProxyHttpClient($proxies);
try {
// GET 请求
$response = $client->get('https://api.example.com/data', ['page' => 1]);
// POST 请求
$response = $client->post('https://api.example.com/submit', ['key' => 'value']);
// 直接使用 request 方法
$response = $client->request('put', 'https://api.example.com/update', [
'json' => ['field' => 'new_value']
]);
$data = $response->json();
} catch (RequestException $e) {
// 处理异常
}
选择策略增强:
protected function getNextProxy(): array
{
// 示例:基于权重的随机选择
$weights = array_column($this->proxies, 'weight');
$total = array_sum($weights);
$rand = mt_rand(1, $total);
foreach ($this->proxies as $index => $proxy) {
$total -= $weights[$index];
if ($rand > $total) {
return $proxy;
}
}
}
代理健康检查:
public function checkProxyHealth(array $proxy): bool
{
try {
$this->buildClient($proxy)
->get('https://www.google.com', ['timeout' => 5])
->throw();
return true;
} catch (\Exception $e) {
return false;
}
}
数据库集成示例:
class DatabaseProxyProvider
{
public function getActiveProxies(): array
{
return Proxy::where('is_active', true)
->get()
->map(function ($proxy) {
return [
'host' => $proxy->ip,
'port' => $proxy->port,
'username' => $proxy->username,
'password' => $proxy->password,
'weight' => $proxy->success_rate
];
})->toArray();
}
}
使用中间件记录统计信息:
protected function buildClient(array $proxy): PendingRequest
{
return parent::buildClient($proxy)
->withMiddleware(function ($handler) use ($proxy) {
return function ($request, $options) use ($handler, $proxy) {
$start = microtime(true);
return $handler($request, $options)->then(
function ($response) use ($proxy, $start) {
// 记录成功统计
$this->logProxyPerformance(
$proxy,
microtime(true) - $start
);
return $response;
},
function ($error) use ($proxy) {
// 记录失败统计
$this->logProxyFailure($proxy);
throw $error;
}
);
};
});
}
注意事项:
-
代理协议需要与实际代理类型匹配(HTTP/HTTPS/Socks5)
-
建议添加代理IP的自动验证和失效剔除机制
-
重要操作建议记录详细的请求日志
-
根据目标网站的防爬策略调整请求频率
-
考虑使用连接池保持长连接提升性能
-
对敏感代理凭证建议使用加密存储
可以根据实际需求继续扩展以下功能:
-
自动代理采购接口集成
-
代理质量评分系统
-
地域选择策略(根据目标网站选择对应地区的代理)
-
请求频率限制
-
障代理自动隔离和恢复机制