# Go Proxy Scraping: http.Transport, Autenticazione, Rotazione e Concorrenza

In Go, tutta la gestione dei proxy si concentra su un unico campo: Transport.Proxy. Impostalo correttamente e otterrai rotazione per richiesta, SOCKS5 e colly gratuitamente. Impostalo male e avrai 'context canceled' alle 2 del mattino.

Go rende il proxying ingannevolmente semplice: tutta la gestione lato client si concentra su un unico campo, `http.Transport.Proxy`. Impostalo correttamente e la rotazione per richiesta, SOCKS5 e l'integrazione con `colly` derivano tutte dallo stesso design. Impostalo male e incontrerai i classici fallimenti dei proxy Go - `context canceled`, esaurimento del pool di connessioni e una variabile `HTTP_PROXY` che il tuo trasporto personalizzato ignora silenziosamente. Questa guida percorre l'intero percorso con codice eseguibile.

## La soluzione in una riga: http.Transport con un URL proxy

Ogni richiesta proxy in Go passa attraverso un `Transport`. Il pacchetto `net/http` fornisce un helper, `http.ProxyURL`, che fissa un proxy statico per l'intero client. Le credenziali vanno direttamente nell'URL userinfo - Go le trasforma nell'intestazione `Proxy-Authorization` per te:

```go
package main

import (
  "fmt"
  "net/http"
  "net/url"
  "time"
)

func main() {
  proxyURL, _ := url.Parse("http://USER:PASS@residentialpremium.quantumproxies.io:8080")
  client := &http.Client{
    Timeout:   20 * time.Second,
    Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)},
  }
  resp, err := client.Get("https://httpbin.org/ip")
  if err != nil {
    panic(err)
  }
  defer resp.Body.Close()
  fmt.Println(resp.Status) // body shows the exit IP, not yours
}
```

Imposta sempre `Client.Timeout`. Go non ha un timeout predefinito su `http.Client`, quindi un singolo exit morto bloccherà il goroutine per sempre - il motivo principale per cui uno scraper Go sembra bloccarsi è un timeout mancante, non un target lento.

## Rotazione dei proxy con una funzione Proxy

`Proxy` non è limitato a un URL fisso - è una funzione `func(*http.Request) (*url.URL, error)` che Go chiama una volta per richiesta. Questo è il tuo hook di rotazione. Scegli un endpoint casuale da un pool e ogni richiesta esce attraverso un IP diverso, senza logica di gestione della lista nel tuo ciclo di fetch:

```go
import "math/rand"

// three sticky sessions on the same gateway = three exit IPs to pick from
pool := []string{
  "http://USER-sid-pool1-ttl-30m:PASS@residentialpremium.quantumproxies.io:8080",
  "http://USER-sid-pool2-ttl-30m:PASS@residentialpremium.quantumproxies.io:8080",
  "http://USER-sid-pool3-ttl-30m:PASS@residentialpremium.quantumproxies.io:8080",
}

transport := &http.Transport{
  Proxy: func(r *http.Request) (*url.URL, error) {
    return url.Parse(pool[rand.Intn(len(pool))])
  },
  MaxIdleConnsPerHost: 32, // reuse connections across the pool
}
```

Per la maggior parte degli scraping non dovresti gestire affatto un pool. Punta la funzione `Proxy` a un singolo [gateway residenziale rotante](https://quantumproxies.io/rotating-proxies) e il gateway ti fornisce un nuovo IP ad ogni richiesta su oltre 90M di indirizzi in oltre 200 paesi - un endpoint, nessuna lista, sessioni sticky quando un flusso necessita dello stesso exit per alcuni minuti.

![Un unico http.Client di Go con un unico Transport la cui funzione Proxy viene eseguita per ogni richiesta, così le richieste 1, 2 e 3 escono da IP di uscita diversi verso lo stesso target. La scheda: la funzione viene chiamata una volta per richiesta, sceglie da un pool o punta a un unico gateway rotante, e MaxIdleConnsPerHost: 32 mantiene le connessioni riutilizzate.](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335148/blog/generated/qp-b9-009-diag1.png)
*Un Transport, una funzione Proxy: la funzione viene eseguita per richiesta, quindi un singolo client ruota su tutto il pool.*

## Autenticazione proxy in Go

Inserire `USER:PASS@` nell'URL del proxy è il percorso pulito e funziona sia per `ProxyURL` che per una funzione `Proxy` personalizzata. Se preferisci mantenere le credenziali fuori dall'URL, imposta l'intestazione sul tunnel CONNECT da solo tramite `Transport.ProxyConnectHeader`. Se il tuo provider utilizza l'autenticazione tramite whitelist IP, elimina completamente le credenziali e autorizza l'IP del tuo server nel dashboard - QuantumProxies supporta entrambi. Un CONNECT rifiutato con `407` o `proxyconnect tcp` significa quasi sempre credenziali mancanti o errate, non un blocco lato target.

## La variabile d'ambiente HTTP_PROXY (e quando Go la ignora)

Il `http.DefaultTransport` di Go utilizza `http.ProxyFromEnvironment`, che legge `HTTP_PROXY`, `HTTPS_PROXY` e `NO_PROXY` (secondo il pacchetto `golang.org/x/net/http/httpproxy`). Due comportamenti ingannano le persone. Primo, quelle variabili accettano sia un URL completo che un semplice `host:port`, dove si presume lo schema `http`. Secondo, le richieste a `localhost` o a un indirizzo di loopback bypassano sempre il proxy, restituendo un URL nullo. La più grande insidia è il contrario: nel momento in cui costruisci il tuo `&http.Transport{Proxy: ...}`, hai *sostituito* il comportamento delle variabili d'ambiente - la tua funzione esplicita vince e `HTTP_PROXY` viene ignorato. Se vuoi entrambi, avvolgili tu stesso.

## Proxy SOCKS5 in Go

La libreria standard non ha un client SOCKS5, quindi importa `golang.org/x/net/proxy` e componi tramite esso. Alimenta il dialer in `Transport.DialContext` in modo che i DNS vengano risolti al proxy, non sulla tua macchina (lo stesso problema di perdita di hostname che `socks5h` risolve in altri stack). Se stai valutando i due schemi, la nostra nota su [SOCKS5 vs HTTP proxies](https://quantumproxies.io/blog/socks5-vs-http-proxy-difference) copre quando ciascuno vince:

```bash
go get golang.org/x/net/proxy
```

```go
import (
  "context"
  "net"
  "net/http"
  "golang.org/x/net/proxy"
)

auth := &proxy.Auth{User: "USER", Password: "PASS"}
dialer, err := proxy.SOCKS5("tcp", "residentialpremium.quantumproxies.io:1080", auth, proxy.Direct)
if err != nil {
  panic(err)
}

transport := &http.Transport{
  DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
    return dialer.Dial(network, addr)
  },
}
client := &http.Client{Transport: transport}
```

## Proxy in colly

Se fai scraping con `colly`, ottieni la rotazione senza toccare il transport. Il pacchetto `colly/proxy` fornisce un `RoundRobinProxySwitcher` che cicla gli endpoint per richiesta, e `LimitRule` gestisce il ritmo. Questo è il modo più veloce per un crawler educato e rotante in Go:

```go
import (
  "time"
  "github.com/gocolly/colly/v2"
  "github.com/gocolly/colly/v2/proxy"
)

c := colly.NewCollector(colly.Async(true))

rp, err := proxy.RoundRobinProxySwitcher(
  "http://USER-sid-crawl1-ttl-30m:PASS@residentialpremium.quantumproxies.io:8080",
  "http://USER-sid-crawl2-ttl-30m:PASS@residentialpremium.quantumproxies.io:8080",
)
if err != nil {
  panic(err)
}
c.SetProxyFunc(rp)

c.Limit(&colly.LimitRule{
  DomainGlob:  "*",
  Parallelism: 8,
  RandomDelay: 2 * time.Second,
})
```

## Concorrenza fatta bene (e la trappola del context-canceled)

La concorrenza di Go è il motivo per cui i team lo scelgono per lo scraping, e anche dove si verifica l'errore `context canceled`. Si attiva quando il contesto di una richiesta viene annullato prima che il corpo sia completamente letto - di solito un timeout per richiesta scaduto, o un `cancel()` eseguito troppo presto a causa di un `defer` posizionato male. Limita il tuo fan-out con un semaforo, dai a ogni richiesta il proprio contesto di timeout e leggi poi chiudi il corpo prima che il cancel venga eseguito:

```go
sem := make(chan struct{}, 20) // cap concurrency
var wg sync.WaitGroup

for _, u := range urls {
  wg.Add(1)
  sem <- struct{}{}
  go func(u string) {
    defer wg.Done()
    defer func() { <-sem }()

    ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
    defer cancel()

    req, _ := http.NewRequestWithContext(ctx, "GET", u, nil)
    resp, err := client.Do(req)
    if err != nil {
      return // rotate/log; do not retry the same burned exit
    }
    io.Copy(io.Discard, resp.Body) // drain BEFORE cancel fires
    resp.Body.Close()
  }(u)
}
wg.Wait()
```

Altre due note di produzione. Imposta `MaxIdleConnsPerHost` abbastanza alto affinché un grande pool riutilizzi le connessioni invece di esaurire i descrittori di file - la causa usuale di `EOF` dopo alcune centinaia di richieste. E ruota l'exit in caso di fallimento piuttosto che riprovare lo stesso; un IP residenziale morto non si riprende al tentativo successivo. Per il design più ampio su milioni di pagine, la nostra guida su [large-scale scraping architecture](https://quantumproxies.io/blog/large-scale-web-scraping-architecture) copre code, dedup e tiering dei proxy.

## Riutilizza un client, ottimizza il transport

Crea il `http.Client` una volta e condividilo tra ogni goroutine - è sicuro per l'uso concorrente, e il suo pool di connessioni è ciò che rende veloce uno scraper Go. Un client nuovo per richiesta scarta il keep-alive e forza una nuova stretta di mano TLS ogni volta. Tre campi `Transport` sono importanti sotto carico: `MaxIdleConns` e `MaxIdleConnsPerHost` dimensionano il pool, e `IdleConnTimeout` ritira le connessioni obsolete in modo che un pool di proxy rotante non fissi mai un exit morto. Impostali esplicitamente - i default della libreria standard sono ottimizzati per un browser, non per un crawler che esegue migliaia di richieste al minuto.

![Una tabella degli errori proxy in Go con la colonna delle cause evidenziata. La scheda associa ogni errore alla sua causa: proxyconnect o 407 indica credenziali errate, context canceled indica che cancel() è stato eseguito prima della lettura del body, EOF indica un MaxIdleConnsPerHost troppo basso, un HTTP_PROXY ignorato indica un Transport personalizzato; un blocco indica l'assenza di timeout.](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335148/blog/generated/qp-b9-009-diag2.png)
*Quasi ogni fallimento del proxy Go si mappa su uno di questi cinque - controlla la colonna delle cause prima di riprovare lo stesso exit.*

## Quando smettere di creare transport a mano

Il codice sopra è sufficiente per target puliti. Una volta che un sito aggiunge Cloudflare, fingerprinting TLS o rendering JavaScript, una stretta di mano TLS grezza di Go non assomiglia affatto a quella di Chrome, e nessun proxy risolve quella discrepanza. A quel punto, un [Scraper API](https://quantumproxies.io/scraper-api) che trasporta un vero fingerprint del browser, ruota gli IP e rende JS per te è meno codice e un tasso di successo più alto rispetto a mantenere lo stack a mano. Se stai scegliendo un linguaggio per un nuovo scraper, il nostro post su [best proxies for web scraping](https://quantumproxies.io/blog/best-proxies-for-web-scraping) mappa i compromessi tra gli stack.

[Scarica il rendering e la rotazione allo Scraper API](https://quantumproxies.io/scraper-api)

## Domande frequenti

### Come imposto un proxy per il client HTTP di Go?

Costruisci un `http.Transport` con un campo `Proxy` e passalo a `http.Client`. Usa `http.ProxyURL(u)` per un singolo proxy statico, o una `func(*http.Request) (*url.URL, error)` per scegliere l'exit per richiesta. Inserisci le credenziali nell'URL come `http://user:pass@host:port` e imposta sempre `Client.Timeout`.

### Perché Go ignora la mia variabile HTTP_PROXY?

Perché hai costruito un `Transport` personalizzato con una funzione `Proxy` esplicita, che sostituisce il comportamento predefinito `ProxyFromEnvironment`. Le variabili d'ambiente si applicano solo quando usi `http.DefaultTransport` o imposti `Proxy: http.ProxyFromEnvironment` da solo. Nota anche che le richieste a loopback e localhost bypassano sempre il proxy.

### Cosa causa 'context canceled' con un proxy Go?

Il contesto della richiesta è stato annullato prima che il corpo della risposta fosse letto - tipicamente un timeout per richiesta scaduto o un `cancel()` eseguito troppo presto tramite `defer`. Dai a ogni richiesta il proprio contesto di timeout, svuota e chiudi il corpo prima che il cancel differito venga eseguito, e non condividere un contesto annullabile tra molti goroutine.

### Go supporta i proxy SOCKS5?

Non nella libreria standard, ma `golang.org/x/net/proxy` aggiunge un dialer SOCKS5. Crealo con `proxy.SOCKS5` e collegalo a `Transport.DialContext` in modo che i nomi host vengano risolti al proxy piuttosto che trapelare attraverso una ricerca DNS locale.

Questa è l'intera panoramica lato client: un campo `Transport` per la configurazione, una funzione per la rotazione, `x/net/proxy` per SOCKS5, `colly` per un crawling educato, e un semaforo più contesti per richiesta per la concorrenza. Inizia con un pool rotante pulito e salti la maggior parte della lista degli errori prima che si verifichi.

[Inizia con i proxy residenziali di QuantumProxies](https://quantumproxies.io/residential-proxies)
