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:

package main

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

func main() {
  proxyURL, _ := url.Parse("http://USER:PASS@gate.quantumproxies.io:8000")
  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:

import "math/rand"

pool := []string{
  "http://USER:PASS@ip1.quantumproxies.io:8000",
  "http://USER:PASS@ip2.quantumproxies.io:8000",
  "http://USER:PASS@ip3.quantumproxies.io:8000",
}

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 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.

Diagram dat een Go http.Client toont die een verzoek delegeert aan http.Transport, die de Proxy-functie aanroept om via een roterende residentiële gateway naar de doelsite te routeren
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 wanneer elk wint:

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

auth := &proxy.Auth{User: "USER", Password: "PASS"}
dialer, err := proxy.SOCKS5("tcp", "gate.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:

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:PASS@ip1.quantumproxies.io:8000",
  "http://USER:PASS@ip2.quantumproxies.io:8000",
)
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:

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 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.

Twee-kolommentabel die Go proxy-fouten in kaart brengt - proxyconnect geweigerd, context canceled, x509, EOF, genegeerde HTTP_PROXY - naar hun oorzaken en oplossingen
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 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 post de afwegingen over stacks in kaart.

Verplaats rendering en rotatie naar de 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