Si estás escribiendo un scraper, automatizando la recolección de datos o eludiendo restricciones de IP, no puedes prescindir de un proxy en PHP. En esta guía, analizaremos dos herramientas principales: la extensión integrada cURL y la popular biblioteca Guzzle HTTP Client — con ejemplos de código listos para usar en tu proyecto.
¿Por qué usar proxies en proyectos PHP?
PHP sigue siendo uno de los lenguajes más utilizados para la automatización del servidor, y las tareas que requieren proxies son comunes. Aquí están los principales escenarios donde no puedes prescindir de un proxy:
- Scraping de sitios web y marketplaces — Wildberries, Ozon, Avito, AliExpress bloquean IP después de unas pocas decenas de solicitudes. Los proxies permiten distribuir la carga entre diferentes direcciones y evitar recibir 403/429.
- Recolección de datos de recursos geodependientes — los precios, los resultados de los motores de búsqueda y el contenido pueden variar según el país. Los proxies con la geolocalización adecuada resuelven esta tarea.
- Eludir el rate limiting — muchas API limitan la cantidad de solicitudes desde una sola IP. La rotación de proxies permite eludir estos límites.
- Pruebas de contenido geodependiente — verificar cómo se ve un sitio para usuarios de diferentes países sin salir de la oficina.
- Anonimato en la automatización — ocultar la IP real del servidor al realizar solicitudes masivas a recursos externos.
- Monitoreo de competidores — recolección regular de precios, surtido y promociones de sitios de competidores sin riesgo de ser bloqueado.
En PHP, para las solicitudes HTTP, se utilizan principalmente dos herramientas: la extensión integrada cURL y la biblioteca Guzzle HTTP Client. Ambas soportan proxies HTTP, HTTPS, SOCKS4 y SOCKS5 — analizaremos cada una en detalle.
Qué tipo de proxy elegir para PHP
Antes de escribir código, es importante entender qué tipo de proxy se adapta a tu tarea. Diferentes tipos tienen diferentes características en cuanto a velocidad, fiabilidad y grado de anonimato.
| Tipo de proxy | Velocidad | Anonimato | Mejor para |
|---|---|---|---|
| Proxies de centros de datos | Muy alta | Media | Scraping sin sistemas anti-bot estrictos, solicitudes API |
| Proxies residenciales | Media | Alta | Scraping de sitios protegidos, marketplaces, datos geográficos |
| Proxies móviles | Media | Máxima | Sitios con protección anti-bot estricta, redes sociales |
Para la mayoría de las tareas de scraping de datos de marketplaces como Wildberries o Ozon, la mejor opción son los proxies residenciales — sus IP pertenecen a usuarios domésticos reales, lo que hace que las solicitudes sean prácticamente indistinguibles del tráfico normal de un navegador. Los proxies de centros de datos son adecuados donde la velocidad es más importante que la discreción y la protección es débil.
Los tres tipos de proxies soportan los protocolos HTTP/HTTPS y SOCKS5, por lo que desde el punto de vista del código, la configuración es la misma. La única diferencia está en la cadena de conexión.
Proxy en cURL: configuración básica
La extensión cURL está disponible en PHP de forma predeterminada y es el método estándar para realizar solicitudes HTTP en el servidor. Para conectar un proxy, se utilizan dos opciones principales: CURLOPT_PROXY y CURLOPT_PROXYTYPE.
Proxy HTTP sin autenticación
<?php
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://httpbin.org/ip',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => 30,
// Especificamos la dirección del proxy
CURLOPT_PROXY => '185.199.100.1:8080',
// Tipo de proxy: HTTP (por defecto)
CURLOPT_PROXYTYPE => CURLPROXY_HTTP,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo "Código HTTP: $httpCode\n";
echo $response;
Aquí CURLOPT_PROXY acepta una cadena en el formato host:port. Para el tráfico HTTPS a través de un proxy HTTP, cURL utiliza automáticamente el método CONNECT — tunelización, por lo que el contenido de la solicitud permanece cifrado.
Opciones útiles de cURL para trabajar con proxies
| Opción | Descripción |
|---|---|
CURLOPT_PROXY |
Dirección del servidor proxy (host:port) |
CURLOPT_PROXYTYPE |
Tipo: CURLPROXY_HTTP, CURLPROXY_SOCKS4, CURLPROXY_SOCKS5 |
CURLOPT_PROXYUSERPWD |
Usuario y contraseña en el formato user:password |
CURLOPT_HTTPPROXYTUNNEL |
Habilitar tunelización a través de HTTP CONNECT |
CURLOPT_SSL_VERIFYPEER |
Verificación del certificado SSL (false — desactivar) |
CURLOPT_TIMEOUT |
Timeout de la solicitud en segundos |
CURLOPT_CONNECTTIMEOUT |
Timeout de conexión al proxy |
Autenticación y SOCKS5 en cURL
La mayoría de los proxies comerciales requieren autenticación con usuario y contraseña. Además, SOCKS5 es el protocolo preferido cuando se necesita anonimato completo, ya que no añade encabezados como X-Forwarded-For, que pueden revelar el uso de un proxy.
Proxy HTTP con usuario y contraseña
<?php
$proxyHost = '185.199.100.1';
$proxyPort = '8080';
$proxyUser = 'tu_login';
$proxyPass = 'tu_contraseña';
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://httpbin.org/ip',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_CONNECTTIMEOUT => 10,
// Proxy con autenticación
CURLOPT_PROXY => "$proxyHost:$proxyPort",
CURLOPT_PROXYTYPE => CURLPROXY_HTTP,
CURLOPT_PROXYUSERPWD => "$proxyUser:$proxyPass",
// Tunelización para HTTPS
CURLOPT_HTTPPROXYTUNNEL => true,
// Encabezados del navegador para disfrazar
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: es-ES,es;q=0.9,en;q=0.8',
],
]);
$response = curl_exec($ch);
if (curl_errno($ch)) {
echo 'Error de cURL: ' . curl_error($ch);
} else {
echo $response;
}
curl_close($ch);
Proxy SOCKS5 en 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 con resolución DNS a través del proxy (¡recomendado!)
CURLOPT_PROXYTYPE => CURLPROXY_SOCKS5_HOSTNAME,
// Autenticación (si es necesario)
CURLOPT_PROXYUSERPWD => 'login:password',
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
💡 Importante: SOCKS5 vs SOCKS5_HOSTNAME
Usa CURLPROXY_SOCKS5_HOSTNAME en lugar de CURLPROXY_SOCKS5. La diferencia es que con SOCKS5_HOSTNAME, las solicitudes DNS también pasan a través del proxy, lo que evita la filtración de DNS y aumenta el anonimato. Con SOCKS5 normal, la resolución DNS se realiza localmente, lo que puede revelar tu IP real.
Rotación de proxies en cURL
Un solo proxy será rápidamente bloqueado con solicitudes masivas. La estrategia correcta es tener un grupo de proxies y alternarlos. Aquí hay una implementación simple pero efectiva:
<?php
class ProxyRotator
{
private array $proxies;
private int $currentIndex = 0;
public function __construct(array $proxies)
{
$this->proxies = $proxies;
shuffle($this->proxies); // Mezclamos para un orden aleatorio
}
/**
* Obtener el siguiente proxy del grupo
*/
public function getNext(): string
{
$proxy = $this->proxies[$this->currentIndex];
$this->currentIndex = ($this->currentIndex + 1) % count($this->proxies);
return $proxy;
}
/**
* Obtener un proxy aleatorio
*/
public function getRandom(): string
{
return $this->proxies[array_rand($this->proxies)];
}
}
// Lista de proxies en formato 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);
/**
* Función de solicitud con selección automática de proxy
*/
function fetchWithProxy(string $url, ProxyRotator $rotator): ?string
{
$proxyStr = $rotator->getNext();
// Analizamos la cadena del proxy
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("Proxy $host:$port no disponible: $error");
return null;
}
return $response;
}
// Ejemplo: scraping de 10 páginas con rotación de proxies
$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 "Recibido: " . strlen($html) . " bytes\n";
}
usleep(500000); // Pausa de 0.5 seg entre solicitudes
}
Presta atención a usleep(500000) — la pausa entre solicitudes es críticamente importante. Incluso con la rotación de proxies, solicitudes demasiado frecuentes pueden llevar a bloqueos por patrones de comportamiento. El intervalo recomendado es de 500 ms a 2 segundos, dependiendo del sitio.
Proxy en Guzzle HTTP Client: configuración básica
Guzzle es una poderosa biblioteca PHP para solicitudes HTTP, que se utiliza en la mayoría de los frameworks PHP modernos (Laravel, Symfony). Proporciona una API más conveniente y legible en comparación con cURL puro, soporta solicitudes asíncronas, middleware y un manejo de errores conveniente.
Instalación a través de Composer
composer require guzzlehttp/guzzle
Proxy HTTP sin autenticación
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
$client = new Client([
// URL base (opcional)
'base_uri' => 'https://httpbin.org',
// Configuraciones por defecto para todas las solicitudes
'timeout' => 30,
'connect_timeout' => 10,
// Proxy para todas las solicitudes del cliente
'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"}
En Guzzle, el proxy se establece a través de la opción proxy en formato URL. Esto es más intuitivo que en cURL. El proxy se puede establecer tanto a nivel de cliente (para todas las solicitudes) como para cada solicitud individualmente.
Proxy HTTP con autenticación
<?php
use GuzzleHttp\Client;
$login = 'tu_login';
$password = 'tu_contraseña';
$host = '185.199.100.1';
$port = '8080';
$client = new Client([
'timeout' => 30,
]);
// Proxy con autenticación se pasa en formato 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();
El formato de URL del proxy en Guzzle es: scheme://user:password@host:port. Para proxies HTTP usamos http://, para SOCKS5 — socks5://.
Trabajo avanzado con Guzzle: timeouts, encabezados, SOCKS5
Consideremos escenarios más complejos: diferentes proxies para HTTP y HTTPS, SOCKS5, desactivación de la verificación SSL y configuración de encabezados para simular un navegador.
Diferentes proxies para HTTP y HTTPS
<?php
use GuzzleHttp\Client;
$client = new Client([
'timeout' => 30,
'proxy' => [
// Proxy para solicitudes HTTP
'http' => 'http://login:[email protected]:8080',
// Proxy para solicitudes HTTPS
'https' => 'http://login:[email protected]:8080',
// Excepciones — estos hosts van sin proxy
'no' => ['localhost', '127.0.0.1', '.internal.corp'],
],
]);
SOCKS5 en Guzzle
<?php
use GuzzleHttp\Client;
// Para SOCKS5 en Guzzle se necesita un paquete adicional
// composer require clue/socks-react (o usa el manejador de cURL)
$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();
Configuración completa con encabezados de navegador
<?php
use GuzzleHttp\Client;
use GuzzleHttp\RequestOptions;
$client = new Client([
'timeout' => 30,
'connect_timeout' => 10,
'verify' => false, // Desactivar verificación SSL (para depuración)
'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' => 'es-ES,es;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 "Estado: $statusCode, Tamaño: " . strlen($body) . " bytes\n";
⚠️ Sobre verify => false
Desactivar la verificación SSL ('verify' => false) es aceptable solo para depuración o trabajo con servicios internos. En el código de producción, siempre mantén la verificación SSL habilitada, de lo contrario, serás vulnerable a ataques de tipo man-in-the-middle.
Rotación de proxies en Guzzle
En Guzzle, la rotación se puede implementar fácilmente a través de middleware — una capa intermedia que intercepta cada solicitud y le añade un proxy. Esto es más elegante que pasar el proxy manualmente en cada llamada.
<?php
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use Psr\Http\Message\RequestInterface;
/**
* Middleware para rotación automática de proxies
*/
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++;
// Añadimos el proxy a los atributos de la solicitud
// (pasamos a través de options en el manejador)
return $request->withHeader('X-Selected-Proxy', $proxy);
}
);
}
// Lista de proxies
$proxies = [
'http://user1:[email protected]:8080',
'http://user2:[email protected]:8080',
'http://user3:[email protected]:8080',
];
// Opción simple: clase envoltura sobre Guzzle con rotación
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);
}
}
// Uso
$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 "Error: " . $e->getMessage() . "\n";
}
}
Manejo de errores y depuración
Al trabajar con proxies, los errores son inevitables: el proxy puede no estar disponible, responder lentamente, o el sitio objetivo puede devolver un error. Es importante manejar correctamente todas estas situaciones.
Manejo de errores en 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);
// Códigos de error de cURL para proxies
$proxyErrors = [
CURLE_COULDNT_CONNECT => 'No se pudo conectar al proxy',
CURLE_OPERATION_TIMEDOUT => 'Timeout de conexión al proxy',
CURLE_RECV_ERROR => 'Error al recibir datos',
CURLE_SSL_CONNECT_ERROR => 'Error SSL a través del proxy',
];
if ($errno) {
$message = $proxyErrors[$errno] ?? "Error de 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),
];
}
// Uso con reintentos
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 "Éxito a través de $proxy (intento $attempt, {$result['time']}s)\n";
return $result['body'];
}
echo "Error a través de $proxy: {$result['error']}\n";
if ($attempt < $maxRetries) {
sleep(2); // Pausa antes de reintentar
}
}
}
return null; // Todos los proxies fallaron
}
Manejo de errores en 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) {
// Proxy no disponible o timeout de conexión
echo "Proxy no disponible ($proxy): " . $e->getMessage() . "\n";
} catch (ClientException $e) {
// HTTP 4xx — error del cliente (403, 404, 429)
$code = $e->getResponse()->getStatusCode();
echo "HTTP $code para $url a través de $proxy\n";
if ($code === 429) {
echo "Límite de tasa — pausa de 5 segundos\n";
sleep(5);
}
} catch (ServerException $e) {
// HTTP 5xx — error del servidor
echo "Error del servidor: " . $e->getResponse()->getStatusCode() . "\n";
} catch (RequestException $e) {
// Otros errores de solicitud
echo "Error de solicitud: " . $e->getMessage() . "\n";
}
}
return null;
}
cURL vs Guzzle: ¿qué elegir?
Ambas herramientas funcionan excelentemente con proxies, pero tienen diferentes fortalezas. Aquí hay una comparación según criterios clave:
| Criterio | cURL | Guzzle |
|---|---|---|
| Instalación | ✅ Integrado en PHP | ⚠️ Necesita Composer |
| Legibilidad del código | ⚠️ Muchas opciones, verboso | ✅ API limpia y conveniente |
| Rendimiento | ✅ Un poco más rápido (sin envolturas) | ✅ Comparable, tiene async |
| Solicitudes asíncronas | ⚠️ curl_multi (difícil) | ✅ Soporte integrado |
| Middleware / hooks | ❌ No | ✅ HandlerStack, Middleware |
| Soporte SOCKS5 | ✅ Nativo | ✅ A través del manejador de cURL |
| Manejo de errores | ⚠️ Códigos de error manualmente | ✅ Excepciones con tipos |
| Integración en Laravel/Symfony | ⚠️ Manualmente | ✅ Soporte nativo |
| Adecuado para | Scripts simples, sin dependencias | Proyectos en frameworks, lógica compleja |
Recomendación: Si estás escribiendo un script simple o trabajando en un hosting sin Composer, utiliza cURL. Para proyectos en Laravel, Symfony o cualquier aplicación PHP moderna, Guzzle será significativamente más conveniente y ofrecerá más posibilidades de escalabilidad.
🚀 Lista de verificación rápida antes de lanzar
- ✅ Verifica que el proxy funcione:
curl -x http://login:pass@host:port https://httpbin.org/ip - ✅ Establece timeouts:
timeoutyconnect_timeout - ✅ Añade un User-Agent realista
- ✅ Implementa pausas entre solicitudes (mínimo 500 ms)
- ✅ Prevé el manejo de errores y fallback a otro proxy
- ✅ Para SOCKS5 utiliza CURLPROXY_SOCKS5_HOSTNAME (no SOCKS5)
- ✅ No desactives la verificación SSL en producción
Conclusión
Configurar un proxy en PHP no es una tarea difícil si conoces las opciones y formatos correctos. Para tareas básicas, cURL con CURLOPT_PROXY y CURLOPT_PROXYUSERPWD es suficiente. Para proyectos en frameworks, Guzzle proporciona una API más conveniente, excepciones tipificadas y soporte para middleware para la rotación de proxies.
Los principios clave para un funcionamiento fiable: utiliza SOCKS5 con resolución DNS a través del proxy (CURLPROXY_SOCKS5_HOSTNAME), siempre establece timeouts, añade pausas entre solicitudes y realiza rotación de proxies para tareas a gran escala. El manejo de errores con fallback a otro proxy es un elemento obligatorio de cualquier código de producción.
¡Buena suerte con tus proyectos de scraping y automatización!
```