Se stai scrivendo uno scraper, automatizzando la raccolta di dati o aggirando le limitazioni IP, non puoi fare a meno dei proxy in PHP. In questa guida esamineremo due strumenti principali: l'estensione integrata cURL e la popolare libreria Guzzle HTTP Client — con esempi di codice pronti all'uso nel tuo progetto.
Perché usare proxy nei progetti PHP
PHP rimane uno dei linguaggi più diffusi per l'automazione server-side, e le attività che richiedono proxy si presentano costantemente. Ecco i principali scenari in cui non puoi fare a meno dei proxy:
- Scraping di siti web e marketplace — Wildberries, Ozon, Avito, AliExpress bloccano gli IP dopo alcune decine di richieste. I proxy consentono di distribuire il carico tra diversi indirizzi e di evitare errori 403/429.
- Raccolta di dati da risorse geolocalizzate — i prezzi, i risultati dei motori di ricerca e i contenuti possono variare a seconda del paese. I proxy con la geolocalizzazione necessaria risolvono questo problema.
- Bypassare il rate limiting — molte API limitano il numero di richieste da un singolo IP. La rotazione dei proxy consente di aggirare questi limiti.
- Testare contenuti geolocalizzati — controllare come appare un sito per gli utenti di diversi paesi senza uscire dall'ufficio.
- Anonimato durante l'automazione — nascondere il vero IP del server durante le richieste massicce a risorse esterne.
- Monitoraggio della concorrenza — raccolta regolare di prezzi, assortimento e promozioni dai siti dei concorrenti senza rischiare di essere bloccati.
In PHP, per le richieste HTTP si utilizzano più frequentemente due strumenti: l'estensione integrata cURL e la libreria Guzzle HTTP Client. Entrambi supportano proxy HTTP, HTTPS, SOCKS4 e SOCKS5 — esaminiamo ciascuno in dettaglio.
Quale tipo di proxy scegliere per PHP
Prima di scrivere il codice, è importante capire quale tipo di proxy è adatto per il tuo compito. I diversi tipi hanno caratteristiche diverse in termini di velocità, affidabilità e grado di anonimato.
| Tipo di proxy | Velocità | Anonimato | Migliore per |
|---|---|---|---|
| Proxy di data center | Molto alta | Media | Scraping senza sistemi anti-bot rigorosi, richieste API |
| Proxy residenziali | Media | Alta | Scraping di siti protetti, marketplace, dati geolocalizzati |
| Proxy mobili | Media | Massima | Siti con protezione anti-bot rigorosa, social media |
Per la maggior parte delle attività di scraping di dati da marketplace come Wildberries o Ozon, la scelta ottimale saranno i proxy residenziali — i loro IP appartengono a veri utenti domestici, rendendo le richieste praticamente indistinguibili dal traffico normale del browser. I proxy di data center sono adatti dove la velocità è più importante della discrezione e la protezione è debole.
Tutti e tre i tipi di proxy supportano i protocolli HTTP/HTTPS e SOCKS5, quindi dal punto di vista del codice, la configurazione è la stessa. L'unica differenza è nella stringa di connessione.
Proxy in cURL: configurazione di base
L'estensione cURL è disponibile in PHP "out of the box" ed è il modo standard per effettuare richieste HTTP sul server. Per connettersi a un proxy si utilizzano due opzioni principali: CURLOPT_PROXY e CURLOPT_PROXYTYPE.
Proxy HTTP senza autenticazione
<?php
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://httpbin.org/ip',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => 30,
// Indica l'indirizzo del proxy
CURLOPT_PROXY => '185.199.100.1:8080',
// Tipo di proxy: HTTP (predefinito)
CURLOPT_PROXYTYPE => CURLPROXY_HTTP,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo "Codice HTTP: $httpCode\n";
echo $response;
Qui CURLOPT_PROXY accetta una stringa nel formato host:port. Per il traffico HTTPS tramite proxy HTTP, cURL utilizza automaticamente il metodo CONNECT — tunneling, quindi il contenuto della richiesta rimane crittografato.
Opzioni utili di cURL per lavorare con proxy
| Opzione | Descrizione |
|---|---|
CURLOPT_PROXY |
Indirizzo del server proxy (host:port) |
CURLOPT_PROXYTYPE |
Tipo: CURLPROXY_HTTP, CURLPROXY_SOCKS4, CURLPROXY_SOCKS5 |
CURLOPT_PROXYUSERPWD |
Nome utente e password nel formato user:password |
CURLOPT_HTTPPROXYTUNNEL |
Abilita il tunneling tramite HTTP CONNECT |
CURLOPT_SSL_VERIFYPEER |
Verifica del certificato SSL (false — disabilita) |
CURLOPT_TIMEOUT |
Timeout della richiesta in secondi |
CURLOPT_CONNECTTIMEOUT |
Timeout di connessione al proxy |
Autenticazione e SOCKS5 in cURL
La maggior parte dei proxy commerciali richiede autenticazione tramite nome utente e password. Inoltre, SOCKS5 è il protocollo preferito quando è necessaria la massima anonimato, poiché non aggiunge intestazioni come X-Forwarded-For, che potrebbero rivelare l'uso del proxy.
Proxy HTTP con nome utente e password
<?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,
// Proxy con autenticazione
CURLOPT_PROXY => "$proxyHost:$proxyPort",
CURLOPT_PROXYTYPE => CURLPROXY_HTTP,
CURLOPT_PROXYUSERPWD => "$proxyUser:$proxyPass",
// Tunneling per HTTPS
CURLOPT_HTTPPROXYTUNNEL => true,
// Intestazioni del browser per mascheramento
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 'Errore cURL: ' . curl_error($ch);
} else {
echo $response;
}
curl_close($ch);
Proxy SOCKS5 in 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 risoluzione DNS tramite proxy (consigliato!)
CURLOPT_PROXYTYPE => CURLPROXY_SOCKS5_HOSTNAME,
// Autenticazione (se necessaria)
CURLOPT_PROXYUSERPWD => 'login:password',
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
💡 Importante: SOCKS5 vs SOCKS5_HOSTNAME
Usa CURLPROXY_SOCKS5_HOSTNAME invece di CURLPROXY_SOCKS5. La differenza è che con SOCKS5_HOSTNAME le richieste DNS passano anche attraverso il proxy, prevenendo la perdita di DNS e aumentando l'anonimato. Con SOCKS5 normale, DNS viene risolto localmente — questo può rivelare il tuo vero IP.
Rotazione dei proxy in cURL
Un singolo proxy verrà rapidamente bloccato durante richieste massicce. La strategia corretta è avere un pool di proxy e alternarli. Ecco una semplice ma efficace implementazione:
<?php
class ProxyRotator
{
private array $proxies;
private int $currentIndex = 0;
public function __construct(array $proxies)
{
$this->proxies = $proxies;
shuffle($this->proxies); // Mescola per ordine casuale
}
/**
* Ottieni il prossimo proxy dal pool
*/
public function getNext(): string
{
$proxy = $this->proxies[$this->currentIndex];
$this->currentIndex = ($this->currentIndex + 1) % count($this->proxies);
return $proxy;
}
/**
* Ottieni un proxy casuale
*/
public function getRandom(): string
{
return $this->proxies[array_rand($this->proxies)];
}
}
// Lista di proxy nel 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);
/**
* Funzione di richiesta con selezione automatica del proxy
*/
function fetchWithProxy(string $url, ProxyRotator $rotator): ?string
{
$proxyStr = $rotator->getNext();
// Analizza la stringa 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 non disponibile: $error");
return null;
}
return $response;
}
// Esempio: scraping di 10 pagine con rotazione dei proxy
$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 "Ricevuto: " . strlen($html) . " byte\n";
}
usleep(500000); // Pausa di 0.5 secondi tra le richieste
}
Fai attenzione a usleep(500000) — la pausa tra le richieste è criticamente importante. Anche con la rotazione dei proxy, richieste troppo frequenti possono portare a un blocco per modelli comportamentali. L'intervallo consigliato è di 500 ms a 2 secondi a seconda del sito.
Proxy in Guzzle HTTP Client: configurazione di base
Guzzle è una potente libreria PHP per le richieste HTTP, utilizzata nella maggior parte dei moderni framework PHP (Laravel, Symfony). Fornisce un'API più comoda e leggibile rispetto al cURL nudo, supporta richieste asincrone, middleware e una comoda gestione degli errori.
Installazione tramite Composer
composer require guzzlehttp/guzzle
Proxy HTTP senza autenticazione
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
$client = new Client([
// URL di base (opzionale)
'base_uri' => 'https://httpbin.org',
// Impostazioni predefinite per tutte le richieste
'timeout' => 30,
'connect_timeout' => 10,
// Proxy per tutte le richieste del client
'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"}
In Guzzle, il proxy è impostato tramite l'opzione proxy nel formato URL. Questo è più intuitivo rispetto a cURL. Puoi impostare il proxy sia a livello di client (per tutte le richieste) che per ciascuna richiesta separatamente.
Proxy HTTP con autenticazione
<?php
use GuzzleHttp\Client;
$login = 'your_login';
$password = 'your_password';
$host = '185.199.100.1';
$port = '8080';
$client = new Client([
'timeout' => 30,
]);
// Proxy con autenticazione passato in 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();
Il formato dell'URL del proxy in Guzzle è: scheme://user:password@host:port. Per i proxy HTTP utilizziamo http://, per SOCKS5 — socks5://.
Lavorare con Guzzle: timeout, intestazioni, SOCKS5
Esaminiamo scenari più complessi: diversi proxy per HTTP e HTTPS, SOCKS5, disabilitazione della verifica SSL e impostazione delle intestazioni per simulare un browser.
Diversi proxy per HTTP e HTTPS
<?php
use GuzzleHttp\Client;
$client = new Client([
'timeout' => 30,
'proxy' => [
// Proxy per richieste HTTP
'http' => 'http://login:[email protected]:8080',
// Proxy per richieste HTTPS
'https' => 'http://login:[email protected]:8080',
// Eccezioni — questi host vanno senza proxy
'no' => ['localhost', '127.0.0.1', '.internal.corp'],
],
]);
SOCKS5 in Guzzle
<?php
use GuzzleHttp\Client;
// Per SOCKS5 in Guzzle è necessario un pacchetto aggiuntivo
// composer require clue/socks-react (o usa 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();
Configurazione completa con intestazioni del browser
<?php
use GuzzleHttp\Client;
use GuzzleHttp\RequestOptions;
$client = new Client([
'timeout' => 30,
'connect_timeout' => 10,
'verify' => false, // Disabilita la verifica SSL (per debug)
'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 "Stato: $statusCode, Dimensione: " . strlen($body) . " byte\n";
⚠️ Riguardo a verify => false
Disabilitare la verifica SSL ('verify' => false) è accettabile solo per il debug o per lavorare con servizi interni. Nel codice di produzione, assicurati sempre di mantenere attiva la verifica SSL, altrimenti sei vulnerabile ad attacchi di tipo man-in-the-middle.
Rotazione dei proxy in Guzzle
In Guzzle, la rotazione è comodamente implementata tramite middleware — uno strato intermedio che intercetta ogni richiesta e aggiunge il proxy. Questo è più elegante rispetto a passare manualmente il proxy in ogni chiamata.
<?php
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use Psr\Http\Message\RequestInterface;
/**
* Middleware per la rotazione automatica dei proxy
*/
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++;
// Aggiungi il proxy agli attributi della richiesta
// (passato tramite options nel handler)
return $request->withHeader('X-Selected-Proxy', $proxy);
}
);
}
// Lista di proxy
$proxies = [
'http://user1:[email protected]:8080',
'http://user2:[email protected]:8080',
'http://user3:[email protected]:8080',
];
// Variante semplice: classe wrapper su Guzzle con rotazione
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);
}
}
// Utilizzo
$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 "Errore: " . $e->getMessage() . "\n";
}
}
Gestione degli errori e debug
Quando si lavora con i proxy, gli errori sono inevitabili: il proxy potrebbe non essere disponibile, rispondere lentamente o il sito di destinazione potrebbe restituire un errore. È importante gestire correttamente tutte queste situazioni.
Gestione degli errori in 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);
// Codici di errore cURL per i proxy
$proxyErrors = [
CURLE_COULDNT_CONNECT => 'Impossibile connettersi al proxy',
CURLE_OPERATION_TIMEDOUT => 'Timeout di connessione al proxy',
CURLE_RECV_ERROR => 'Errore nella ricezione dei dati',
CURLE_SSL_CONNECT_ERROR => 'Errore SSL tramite proxy',
];
if ($errno) {
$message = $proxyErrors[$errno] ?? "Errore 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),
];
}
// Utilizzo con tentativi ripetuti
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 "Successo tramite $proxy (tentativo $attempt, {$result['time']}s)\n";
return $result['body'];
}
echo "Errore tramite $proxy: {$result['error']}\n";
if ($attempt < $maxRetries) {
sleep(2); // Pausa prima di ripetere
}
}
}
return null; // Tutti i proxy non hanno funzionato
}
Gestione degli errori in 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 non disponibile o timeout di connessione
echo "Proxy non disponibile ($proxy): " . $e->getMessage() . "\n";
} catch (ClientException $e) {
// HTTP 4xx — errore del client (403, 404, 429)
$code = $e->getResponse()->getStatusCode();
echo "HTTP $code per $url tramite $proxy\n";
if ($code === 429) {
echo "Rate limit — pausa di 5 secondi\n";
sleep(5);
}
} catch (ServerException $e) {
// HTTP 5xx — errore del server
echo "Errore del server: " . $e->getResponse()->getStatusCode() . "\n";
} catch (RequestException $e) {
// Altri errori di richiesta
echo "Errore di richiesta: " . $e->getMessage() . "\n";
}
}
return null;
}
cURL vs Guzzle: cosa scegliere
Entrambi gli strumenti funzionano bene con i proxy, ma hanno punti di forza diversi. Ecco un confronto basato su criteri chiave:
| Criterio | cURL | Guzzle |
|---|---|---|
| Installazione | ✅ Integrato in PHP | ⚠️ Richiede Composer |
| Leggibilità del codice | ⚠️ Molte opzioni, verbose | ✅ API pulita e comoda |
| Prestazioni | ✅ Un po' più veloce (senza wrapper) | ✅ Comparabile, supporta async |
| Richieste asincrone | ⚠️ curl_multi (complesso) | ✅ Supporto integrato |
| Middleware / hook | ❌ No | ✅ HandlerStack, Middleware |
| Supporto SOCKS5 | ✅ Nativo | ✅ Tramite curl handler |
| Gestione degli errori | ⚠️ Codici di errore manuali | ✅ Eccezioni con tipi |
| Integrazione in Laravel/Symfony | ⚠️ Manuale | ✅ Supporto nativo |
| Adatto per | Script semplici, senza dipendenze | Progetti su framework, logica complessa |
Raccomandazione: Se stai scrivendo uno script semplice o lavori su un hosting senza Composer, usa cURL. Per progetti su Laravel, Symfony o qualsiasi moderna applicazione PHP, Guzzle sarà significativamente più comodo e offrirà maggiori possibilità di scalabilità.
🚀 Checklist rapida prima del lancio
- ✅ Controlla che il proxy funzioni:
curl -x http://login:pass@host:port https://httpbin.org/ip - ✅ Imposta i timeout:
timeouteconnect_timeout - ✅ Aggiungi un User-Agent realistico
- ✅ Implementa pause tra le richieste (minimo 500 ms)
- ✅ Prevedi la gestione degli errori e il fallback su un altro proxy
- ✅ Per SOCKS5 usa CURLPROXY_SOCKS5_HOSTNAME (non SOCKS5)
- ✅ Non disabilitare la verifica SSL in produzione
Conclusione
Configurare i proxy in PHP è un compito non difficile se si conoscono le opzioni e i formati corretti. Per compiti di base, è sufficiente cURL con CURLOPT_PROXY e CURLOPT_PROXYUSERPWD. Per progetti su framework, Guzzle offre un'API più comoda, eccezioni tipizzate e supporto middleware per la rotazione dei proxy.
I principi chiave per un funzionamento affidabile: utilizza SOCKS5 con risoluzione DNS tramite proxy (CURLPROXY_SOCKS5_HOSTNAME), imposta sempre i timeout, aggiungi pause tra le richieste e implementa la rotazione dei proxy per attività su larga scala. La gestione degli errori con fallback su un altro proxy è un elemento obbligatorio di qualsiasi codice di produzione.
```