# Go Proxy Scraping: http.Transport, Auth, Rotatie en Concurrency

In Go draait het hele proxyverhaal om één veld: Transport.Proxy. Stel het correct in en je krijgt per-verzoek rotatie, SOCKS5 en colly gratis. Stel het verkeerd in en je krijgt 'context canceled' om 2 uur 's nachts.

Go maakt proxying bedrieglijk eenvoudig: het hele client-side verhaal draait om één veld, `http.Transport.Proxy`. Stel het correct in en per-verzoek rotatie, SOCKS5 en `colly` integratie komen allemaal voort uit hetzelfde ontwerp. Stel het verkeerd in en je ontmoet de klassieke Go proxy-fouten - `context canceled`, uitputting van de verbindingenpool, en een `HTTP_PROXY` variabele die je aangepaste transport stilletjes negeert. Deze gids loopt het hele pad door met uitvoerbare code.

## De one-liner: http.Transport met een proxy URL

Elk geproxied verzoek in Go stroomt door een `Transport`. Het `net/http` pakket levert een helper, `http.ProxyURL`, die één statische proxy voor de hele client vastlegt. Inloggegevens gaan rechtstreeks in de URL userinfo - Go zet ze voor je om in de `Proxy-Authorization` header:

```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
}
```

Stel altijd `Client.Timeout` in. Go heeft geen standaard timeout op `http.Client`, dus een enkele dode exit blokkeert de goroutine voor altijd - de nummer één reden dat een Go-scraper lijkt vast te lopen is een ontbrekende timeout, niet een trage target.

## Proxies roteren met een Proxy-functie

`Proxy` is niet beperkt tot een vaste URL - het is een functie `func(*http.Request) (*url.URL, error)` die Go één keer per verzoek aanroept. Dat is je rotatiehaak. Kies een willekeurig eindpunt uit een pool en elk verzoek vertrekt via een ander IP, zonder enige lijstbeheerlogica in je fetch-lus:

```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
}
```

Voor de meeste scraping moet je helemaal geen pool beheren. Richt de `Proxy` functie op een enkele [roterende residentiële gateway](https://quantumproxies.io/rotating-proxies) en de gateway geeft je een vers IP bij elk verzoek over 90M+ adressen in 200+ landen - één eindpunt, geen lijst, sticky sessies wanneer een flow dezelfde exit een paar minuten nodig heeft.

![Eén Go http.Client met één Transport waarvan de Proxy-functie bij elk verzoek draait, zodat verzoeken 1, 2 en 3 via verschillende exit-IP's naar hetzelfde doel vertrekken. De kaart: de functie wordt één keer per verzoek aangeroepen, kiest uit een pool of wijst naar één roterende gateway, en MaxIdleConnsPerHost: 32 zorgt dat verbindingen worden hergebruikt.](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335148/blog/generated/qp-b9-009-diag1.png)
*Eén Transport, één Proxy-functie: de functie draait per verzoek, dus een enkele client roteert over de hele pool.*

## Proxy-authenticatie in Go

`USER:PASS@` in de proxy-URL plaatsen is de schone weg en werkt voor zowel `ProxyURL` als een aangepaste `Proxy` functie. Als je de voorkeur geeft aan het buiten de URL houden van inloggegevens, stel de header dan zelf in op de CONNECT-tunnel via `Transport.ProxyConnectHeader`. Als je provider IP-whitelist authenticatie gebruikt, laat de inloggegevens dan volledig weg en autoriseer het IP van je server in het dashboard - QuantumProxies ondersteunt beide. Een geweigerde CONNECT met `407` of `proxyconnect tcp` betekent bijna altijd ontbrekende of verkeerde inloggegevens, niet een blokkade aan de targetzijde.

## De HTTP_PROXY omgevingsvariabele (en wanneer Go deze negeert)

Go's `http.DefaultTransport` gebruikt `http.ProxyFromEnvironment`, dat `HTTP_PROXY`, `HTTPS_PROXY` en `NO_PROXY` leest (volgens het `golang.org/x/net/http/httpproxy` pakket). Twee gedragingen brengen mensen in de war. Ten eerste accepteren die variabelen ofwel een volledige URL of een kale `host:port`, waarbij het `http` schema wordt aangenomen. Ten tweede worden verzoeken aan `localhost` of een loopback-adres altijd de proxy omzeild, wat een nul-URL oplevert. De grotere valkuil is het omgekeerde: zodra je je eigen `&http.Transport{Proxy: ...}` bouwt, heb je het gedrag van de omgevingsvariabele *vervangen* - je expliciete functie wint en `HTTP_PROXY` wordt genegeerd. Als je beide wilt, wikkel ze dan zelf.

## SOCKS5 proxies in Go

De standaardbibliotheek heeft geen SOCKS5-client, dus haal `golang.org/x/net/proxy` binnen en bel erdoorheen. Voer de dialer in `Transport.DialContext` zodat DNS wordt opgelost bij de proxy, niet op je machine (hetzelfde hostname-lekprobleem dat `socks5h` oplost in andere stacks). Als je de twee schema's overweegt, behandelt onze notitie over [SOCKS5 versus HTTP proxies](https://quantumproxies.io/blog/socks5-vs-http-proxy-difference) wanneer elk wint:

```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}
```

## Proxies in colly

Als je met `colly` scrapt, krijg je rotatie zonder de transport aan te raken. Het `colly/proxy` pakket levert een `RoundRobinProxySwitcher` die eindpunten per verzoek roteert, en `LimitRule` regelt het tempo. Dit is de snelste manier naar een beleefde, roterende crawler 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,
})
```

## Concurrency goed gedaan (en de context-canceled valkuil)

Go's concurrency is waarom teams het kiezen voor scraping, en ook waar de `context canceled` fout vandaan komt. Het treedt op wanneer de context van een verzoek wordt geannuleerd voordat het lichaam volledig is gelezen - meestal een per-verzoek timeout die is verlopen, of een `cancel()` die te vroeg werd uitgevoerd vanwege een verkeerd geplaatste `defer`. Beperk je fan-out met een semafoor, geef elk verzoek zijn eigen timeout-context, en lees en sluit het lichaam voordat de annulering wordt uitgevoerd:

```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()
```

Twee extra productienotities. Stel `MaxIdleConnsPerHost` hoog genoeg in zodat een grote pool verbindingen hergebruikt in plaats van bestandshandvatten uit te putten - de gebruikelijke oorzaak van `EOF` na een paar honderd verzoeken. En roteer de exit bij een mislukking in plaats van dezelfde opnieuw te proberen; een dood residentieel IP herstelt zich niet bij de volgende poging. Voor het bredere ontwerp bij miljoenen pagina's, behandelt onze gids over [grootschalige scraping-architectuur](https://quantumproxies.io/blog/large-scale-web-scraping-architecture) wachtrijen, deduplicatie en proxy-tiering.

## Herbruik één client, stem het transport af

Maak de `http.Client` één keer en deel het over elke goroutine - het is veilig voor gelijktijdig gebruik, en zijn verbindingenpool is wat een Go-scraper snel maakt. Een nieuwe client per verzoek gooit keep-alive weg en dwingt elke keer een nieuwe TLS-handshake af. Drie `Transport` velden zijn belangrijk onder belasting: `MaxIdleConns` en `MaxIdleConnsPerHost` bepalen de poolgrootte, en `IdleConnTimeout` trekt verouderde verbindingen terug zodat een roterende proxypool nooit een dode exit vastpint. Stel ze expliciet in - de standaardinstellingen van de standaardbibliotheek zijn afgestemd op een browser, niet op een crawler die duizenden verzoeken per minuut uitvoert.

![Een tabel met Go-proxyfouten met de oorzaakkolom uitgelicht. De kaart koppelt elke fout aan de oorzaak: proxyconnect of 407 betekent verkeerde inloggegevens, context canceled betekent dat cancel() liep voordat de body was gelezen, EOF betekent dat MaxIdleConnsPerHost te laag is, een genegeerde HTTP_PROXY betekent een eigen Transport; blijft het hangen, dan is er geen timeout.](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335148/blog/generated/qp-b9-009-diag2.png)
*Bijna elke Go proxy-fout komt overeen met een van deze vijf - controleer de oorzaakkolom voordat je dezelfde exit opnieuw probeert.*

## Wanneer stoppen met het handmatig rollen van transports

De bovenstaande code is voldoende voor schone doelen. Zodra een site Cloudflare, TLS-fingerprinting of JavaScript-rendering toevoegt, lijkt een ruwe Go TLS-handshake niets op die van Chrome, en geen enkele proxy lost die mismatch op. Op dat punt is een [Scraper API](https://quantumproxies.io/scraper-api) die een echte browserfingerprint draagt, IP's roteert en JS voor je rendert minder code en een hogere slagingskans dan de stack handmatig onderhouden. Als je een taal kiest voor een nieuwe scraper, brengt onze [beste proxies voor web scraping](https://quantumproxies.io/blog/best-proxies-for-web-scraping) post de afwegingen over stacks in kaart.

[Verplaats rendering en rotatie naar de Scraper API](https://quantumproxies.io/scraper-api)

## Veelgestelde vragen

### Hoe stel ik een proxy in voor de Go HTTP-client?

Bouw een `http.Transport` met een `Proxy` veld en geef het door aan `http.Client`. Gebruik `http.ProxyURL(u)` voor een enkele statische proxy, of een `func(*http.Request) (*url.URL, error)` om de exit per verzoek te kiezen. Plaats inloggegevens in de URL als `http://user:pass@host:port` en stel altijd `Client.Timeout` in.

### Waarom negeert Go mijn HTTP_PROXY variabele?

Omdat je een aangepaste `Transport` hebt gebouwd met een expliciete `Proxy` functie, die het standaard `ProxyFromEnvironment` gedrag vervangt. Omgevingsvariabelen zijn alleen van toepassing wanneer je `http.DefaultTransport` gebruikt of zelf `Proxy: http.ProxyFromEnvironment` instelt. Let ook op dat loopback- en localhost-verzoeken altijd de proxy omzeilen.

### Wat veroorzaakt 'context canceled' met een Go proxy?

De context van het verzoek werd geannuleerd voordat het antwoordlichaam was gelezen - meestal een verlopen per-verzoek timeout of een `cancel()` die te vroeg werd uitgevoerd via `defer`. Geef elk verzoek zijn eigen timeout-context, leeg en sluit het lichaam voordat de uitgestelde annulering wordt uitgevoerd, en deel geen annuleerbare context over veel goroutines.

### Ondersteunt Go SOCKS5 proxies?

Niet in de standaardbibliotheek, maar `golang.org/x/net/proxy` voegt een SOCKS5-dialer toe. Maak het met `proxy.SOCKS5` en sluit het aan op `Transport.DialContext` zodat hostnamen worden opgelost bij de proxy in plaats van te lekken via een lokale DNS-opzoeking.

Dat is het hele client-side plaatje: één `Transport` veld voor setup, een functie voor rotatie, `x/net/proxy` voor SOCKS5, `colly` voor beleefd crawlen, en een semafoor plus per-verzoek contexten voor concurrency. Begin met een schone roterende pool en je vermijdt de meeste foutenlijst voordat deze optreedt.

[Begin met QuantumProxies residentiële proxies](https://quantumproxies.io/residential-proxies)
