# PHP cURL Proxy Scraping: CURLOPT_PROXY, Auth e Guzzle

La maggior parte degli snippet PHP proxy online presenta ancora gli stessi tre bug del 2011. Ecco una configurazione pulita di CURLOPT_PROXY — autenticazione, SOCKS5, rotazione curl_multi, Guzzle e timeout — che funziona in produzione.

Il codice proxy PHP cURL ha un problema particolare: gli snippet più in alto nei risultati sono vecchi di un decennio e molti contengono bug reali — chiamate doppie a `curl_exec()` che eseguono ogni richiesta due volte, `CURLOPT_SSL_VERIFYPEER` disabilitato "per far funzionare HTTPS", opzioni impostate su valori che erano già predefiniti. Questa guida è la versione attuale e corretta: `CURLOPT_PROXY` con autenticazione, SOCKS5, rotazione parallela con `curl_multi`, la stessa configurazione in Guzzle e timeout che impediscono a un worker di bloccarsi tutta la notte.

## Un esempio minimo e corretto di proxy PHP cURL

```php
<?php
function fetch(string $url): string|false {
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_PROXY          => 'http://residentialpremium.quantumproxies.io:8080',
        CURLOPT_PROXYUSERPWD   => 'USER:PASS',
        CURLOPT_RETURNTRANSFER => true,  // return the body, don't echo it
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS      => 3,
        CURLOPT_CONNECTTIMEOUT => 10,    // seconds to reach the proxy
        CURLOPT_TIMEOUT        => 30,    // seconds for the whole transfer
        CURLOPT_ENCODING       => '',    // accept gzip/br, decode automatically
    ]);
    $body = curl_exec($ch);
    if ($body === false) {
        error_log('cURL error ' . curl_errno($ch) . ': ' . curl_error($ch));
    }
    curl_close($ch);
    return $body;
}

echo fetch('https://httpbin.org/ip'); // -> the proxy exit IP
```

Le credenziali possono essere inserite in `CURLOPT_PROXYUSERPWD` come mostrato, o in linea nell'URL del proxy (`http://USER:PASS@host:port`) — entrambi funzionano. `CURLOPT_RETURNTRANSFER` è quello che i principianti dimenticano: senza di esso, `curl_exec()` restituisce la risposta direttamente in output e ritorna `true`, cosa che uno scraper non desidera mai. Su PHP 8 l'handle è un oggetto `CurlHandle` anziché una risorsa; le chiamate alle opzioni rimangono invariate.

## Le opzioni che le persone copiano senza riflettere (e un vero errore di sicurezza)

- `CURLOPT_HTTPPROXYTUNNEL` — non necessario per obiettivi HTTPS. cURL stabilisce automaticamente il tunnel CONNECT ogni volta che l'obiettivo è `https://`; questa opzione forza il tunneling solo per obiettivi plain-HTTP, che lo scraping quasi mai richiede.
- `CURLOPT_CUSTOMREQUEST => 'GET'` — GET è già il valore predefinito; impostarlo non fa altro che interrompere la gestione dei reindirizzamenti in casi limite.
- Chiamare `curl_exec()` due volte — il classico bug di Stack Overflow: la richiesta viene eseguita due volte e il secondo valore di ritorno sovrascrive il primo. Una volta per handle.
- `CURLOPT_SSL_VERIFYPEER => false` — quello veramente pericoloso. "Risolve" gli errori di certificato consentendo attacchi man-in-the-middle su ogni richiesta. La vera soluzione, secondo il manuale PHP, è puntare `curl.cainfo` in php.ini a un bundle `cacert.pem` aggiornato.

![Un curl_setopt_array PHP con le sei opzioni da impostare: CURLOPT_PROXY, PROXYUSERPWD, RETURNTRANSFER, CONNECTTIMEOUT 10, TIMEOUT 30 ed ENCODING, poi il debito da copia e incolla da eliminare: HTTPPROXYTUNNEL, CUSTOMREQUEST GET, SSL_VERIFYPEER false e un doppio curl_exec. La scheda dice di sistemare invece i certificati con curl.cainfo.](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335147/blog/generated/qp-b9-008-diag1.png)
*Sei opzioni coprono tutto il lavoro; la colonna di destra è un debito di copia-incolla dai tutorial del 2011.*

## Proxy SOCKS5 in PHP

```php
<?php
// scheme style: socks5h = DNS resolved by the proxy (use for scraping)
curl_setopt($ch, CURLOPT_PROXY, 'socks5h://USER:PASS@residentialpremium.quantumproxies.io:1080');

// split style: same result via CURLOPT_PROXYTYPE
curl_setopt($ch, CURLOPT_PROXY, 'residentialpremium.quantumproxies.io:1080');
curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME);
curl_setopt($ch, CURLOPT_PROXYUSERPWD, 'USER:PASS');
```

Lo schema decide dove avviene la risoluzione DNS: `socks5://` (e `CURLPROXY_SOCKS5`) risolve i nomi host sul tuo server, rivelando ogni dominio target al tuo resolver locale; `socks5h://` (`CURLPROXY_SOCKS5_HOSTNAME`) risolve all'uscita del proxy. Preferisci quest'ultimo. QuantumProxies offre [HTTP e SOCKS5 in ogni piano](https://quantumproxies.io/socks5-proxies), quindi cambiare protocollo è una modifica di una riga, non un nuovo abbonamento. La stessa regola DNS si applica sulla riga di comando — consulta le nostre [ricette curl proxy](https://quantumproxies.io/blog/curl-with-proxy-examples) per gli equivalenti shell di tutto ciò che è su questa pagina.

## Rotazione e richieste parallele con curl_multi

PHP non ha async/await, ma non ne ha bisogno per lo scraping parallelo — `curl_multi` esegue dozzine di trasferimenti contemporaneamente in un singolo processo. Punta ogni handle a un gateway rotante e ogni richiesta esce automaticamente da un diverso [IP residenziale](https://quantumproxies.io/residential-proxies), senza alcuna gestione delle liste proxy nel tuo codice:

```php
<?php
$urls = ['https://example.com/p/1', 'https://example.com/p/2', 'https://example.com/p/3'];

$mh = curl_multi_init();
$handles = [];

foreach ($urls as $url) {
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        // one gateway, a fresh residential exit per request
        CURLOPT_PROXY          => 'http://USER:PASS@residentialpremium.quantumproxies.io:8080',
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_TIMEOUT        => 30,
        CURLOPT_ENCODING       => '',
    ]);
    curl_multi_add_handle($mh, $ch);
    $handles[$url] = $ch;
}

do {
    curl_multi_exec($mh, $running);
    curl_multi_select($mh);        // wait for activity instead of spinning
} while ($running > 0);

$results = [];
foreach ($handles as $url => $ch) {
    $results[$url] = curl_multi_getcontent($ch);
    curl_multi_remove_handle($mh, $ch);
    curl_close($ch);
}
curl_multi_close($mh);
```

Mantieni i batch modesti — 8 a 16 handle concorrenti sono sufficienti per la maggior parte degli obiettivi, e il ritmo conta più della velocità pura: un `usleep()` di qualche centinaio di millisecondi tra i batch ti costa poco e mantiene i tassi di richiesta al di sotto delle soglie che attivano i 429. Controlla `curl_getinfo($ch, CURLINFO_HTTP_CODE)` per handle e indirizza i fallimenti in una coda di retry anziché rifetch inline. Quando un flusso si estende su più richieste che devono condividere un'identità (login, carrello, paginazione con cookie), passa quell'handle a una sessione sticky aggiungendo un suffisso al nome utente: `USER-sid-a1b2c3-ttl-30m` mantiene lo stesso IP di uscita per tutta la durata invece di ruotare.

![Un worker PHP invia batch curl_multi da 8 a 16 handle attraverso un unico URL proxy, e il gateway decide l'uscita: un IP residenziale nuovo a rotazione, una sessione sticky che mantiene la stessa uscita o un'uscita geolocalizzata. La scheda: rotazione, nome utente sticky USER-session-a1b2c3 e geotargeting stanno tutti nell'URL del proxy.](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335147/blog/generated/qp-b9-008-diag2.png)
*Il worker rimane semplice, il gateway rimane intelligente: rotazione, adesività e targeting geografico vivono tutti nell'URL del proxy.*

## Header e cookie: sembrare il browser che affermi di essere

Un IP di uscita pulito con header cURL predefiniti appare ancora come un bot — PHP si annuncia a meno che tu non vesta la richiesta. Tre opzioni chiudono la maggior parte del divario: `CURLOPT_USERAGENT` con una stringa del browser attuale, `CURLOPT_HTTPHEADER` per `Accept-Language` e simili, e la coppia di cookie `CURLOPT_COOKIEJAR` / `CURLOPT_COOKIEFILE` puntati allo stesso file, che persiste i cookie di sessione tra gli handle come fa un browser. La persistenza dei cookie conta più di quanto le persone si aspettino: molti siti impostano un token al primo accesso e sfidano silenziosamente i client che non lo restituiscono mai. Mantieni coerente l'identità dichiarata — un User-Agent di Chrome senza `Accept-Language` e un set di header scarno è esso stesso un'impronta digitale, e il controllo della coerenza è esattamente come i fornitori anti-bot di medio livello catturano gli scraper PHP alla loro seconda richiesta.

## Guzzle: lo stesso proxy, ergonomia migliore

```php
<?php
use GuzzleHttp\Client;

$client = new Client([
    'proxy'           => 'http://USER:PASS@residentialpremium.quantumproxies.io:8080',
    'timeout'         => 30,
    'connect_timeout' => 10,
]);

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

// per-request override, e.g. a sticky session for a login flow
$res = $client->get('https://example.com/account', [
    'proxy' => 'http://USER-sid-a1b2c3-ttl-30m:PASS@residentialpremium.quantumproxies.io:8080',
]);
```

Guzzle guida cURL sotto il cofano, quindi tutto quanto sopra si applica ancora — semplicemente si legge meglio. Un avvertimento: il `timeout` predefinito di Guzzle è 0, il che significa attendere per sempre. Un'uscita proxy che si blocca a metà trasferimento bloccherà un worker PHP-FPM indefinitamente, quindi imposta entrambi i timeout su ogni client che costruisci. L'opzione `proxy` accetta anche un array con chiavi `http`, `https` e `no` quando hai bisogno di routing per schema o esclusioni di host interni.

## Quando PHP cURL non basta più

cURL recupera HTML; non esegue JavaScript. Se la risposta arriva come una pagina scheletro con div vuoti, i dati sono renderizzati lato client — la nostra guida su [risposte di pagine vuote](https://quantumproxies.io/blog/scraper-returns-empty-page-javascript) mostra come rilevarlo. E su obiettivi rinforzati, la stretta di mano TLS di cURL stessa ti tradisce indipendentemente da quanto sia pulito l'IP (il mistero [browser-works-curl-403](https://quantumproxies.io/blog/curl-403-but-browser-works)). PHP non può ragionevolmente eseguire un browser headless per richiesta, quindi la via di fuga pragmatica è l'[Scraper API](https://quantumproxies.io/scraper-api): una chiamata cURL a un singolo endpoint, e rendering, impronte digitali del browser, rotazione e parsing tornano come markdown, JSON o HTML puliti.

[Prova lo Scraper API da PHP](https://quantumproxies.io/scraper-api)

## Domande frequenti

### Come uso un proxy con PHP cURL?

Imposta `CURLOPT_PROXY` all'URL completo del proxy, aggiungi `CURLOPT_PROXYUSERPWD` se necessita di credenziali, e abilita sempre `CURLOPT_RETURNTRANSFER` più timeout espliciti. Questo è l'intero set richiesto — gli obiettivi HTTPS tunnelizzano automaticamente, e tutto il resto è ottimizzazione opzionale.

### Come passo un nome utente e una password proxy in PHP?

O `curl_setopt($ch, CURLOPT_PROXYUSERPWD, 'user:pass')` o incorporali nell'URL: `http://user:pass@host:port`. Se ricevi HTTP 407, il proxy ha rifiutato quelle credenziali — controlla per errori di battitura e codifica URL i caratteri speciali. I fornitori che utilizzano l'autenticazione tramite whitelist IP non necessitano di credenziali una volta che l'IP del tuo server è autorizzato.

### PHP cURL può usare un proxy SOCKS5?

Sì. Passa un URL `socks5h://` a `CURLOPT_PROXY`, o imposta `CURLOPT_PROXYTYPE` su `CURLPROXY_SOCKS5_HOSTNAME`. Preferisci le varianti hostname rispetto a `socks5` semplice in modo che il DNS venga risolto all'uscita del proxy piuttosto che dal tuo server — altrimenti ogni dominio target trapela attraverso il tuo resolver locale.

### Cos'è CURLOPT_HTTPPROXYTUNNEL e ne ho bisogno?

Forza cURL a tunnelizzare attraverso il proxy con CONNECT invece di chiedere al proxy di recuperare l'URL. Per obiettivi `https://` cURL tunnelizza automaticamente, quindi l'opzione è ridondante in quasi tutto il codice di scraping. Impostalo solo quando hai bisogno di semantiche di tunnel per un obiettivo plain-HTTP.

### Come imposto un timeout per PHP cURL attraverso un proxy?

Imposta entrambi: `CURLOPT_CONNECTTIMEOUT` copre il raggiungimento del proxy e l'apertura del tunnel, `CURLOPT_TIMEOUT` limita l'intero trasferimento. Esistono varianti in millisecondi (`_MS`) per budget più stretti. Senza di essi, un'uscita bloccata trattiene la richiesta — e sotto PHP-FPM, un intero worker — fino a quando il limite del server web non la termina.

Copia il primo snippet, tieni a mente la lista di controllo, e PHP è un linguaggio di scraping perfettamente solido: `curl_multi` per il parallelismo, un gateway rotante per l'identità, Guzzle per l'ergonomia, e lo Scraper API per le pagine che si oppongono.

[Esegui il tuo scraper PHP su IP residenziali QuantumProxies](https://quantumproxies.io/residential-proxies)
