Go Proxy Scraping: http.Transport, uwierzytelnianie, rotacja i współbieżność
W Go cała historia proxy opiera się na jednym polu: Transport.Proxy. Ustaw je poprawnie, a otrzymasz rotację per-request, SOCKS5 i colly za darmo. Ustaw je źle, a o 2 w nocy otrzymasz 'context canceled'.
Go sprawia, że proxy jest zwodniczo proste: cała historia po stronie klienta opiera się na jednym polu, http.Transport.Proxy. Ustaw je poprawnie, a rotacja per-request, SOCKS5 i integracja z colly wynikają z tego samego projektu. Ustaw je źle, a napotkasz klasyczne błędy proxy Go - context canceled, wyczerpanie puli połączeń i zmienną HTTP_PROXY, którą twój niestandardowy transport cicho ignoruje. Ten przewodnik przeprowadza przez całą ścieżkę z kodem gotowym do uruchomienia.
Jednolinijkowiec: http.Transport z URL proxy
Każde żądanie przez proxy w Go przepływa przez Transport. Pakiet net/http dostarcza pomocnika, http.ProxyURL, który przypisuje jedno statyczne proxy dla całego klienta. Dane uwierzytelniające trafiają bezpośrednio do URL userinfo - Go zamienia je na nagłówek Proxy-Authorization za ciebie:
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
}
Zawsze ustawiaj Client.Timeout. Go nie ma domyślnego limitu czasu na http.Client, więc jedno martwe wyjście zablokuje goroutine na zawsze - głównym powodem, dla którego skrobak Go wydaje się zawieszać, jest brakujący limit czasu, a nie wolny cel.
Rotacja proxy z funkcją Proxy
Proxy nie jest ograniczone do stałego URL - jest to funkcja func(*http.Request) (*url.URL, error), którą Go wywołuje raz na żądanie. To jest twój haczyk rotacyjny. Wybierz losowy punkt końcowy z puli, a każde żądanie wychodzi przez inny IP, bez żadnej logiki zarządzania listą w pętli pobierania:
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
}
W większości przypadków skrobania nie powinieneś w ogóle zarządzać pulą. Skieruj funkcję Proxy na pojedynczy rotacyjny bramowy punkt dostępowy, a brama dostarczy ci świeży IP przy każdym żądaniu z ponad 90 milionów adresów w ponad 200 krajach - jeden punkt końcowy, brak listy, sesje lepkie, gdy przepływ wymaga tego samego wyjścia przez kilka minut.

Uwierzytelnianie proxy w Go
Umieszczenie USER:PASS@ w URL proxy to czysta ścieżka i działa zarówno dla ProxyURL, jak i niestandardowej funkcji Proxy. Jeśli wolisz trzymać dane uwierzytelniające poza URL, ustaw nagłówek na tunelu CONNECT samodzielnie za pomocą Transport.ProxyConnectHeader. Jeśli twój dostawca używa uwierzytelniania na białej liście IP, całkowicie pomiń dane uwierzytelniające i autoryzuj IP swojego serwera w panelu - QuantumProxies obsługuje oba. Odmowa CONNECT z 407 lub proxyconnect tcp prawie zawsze oznacza brakujące lub błędne dane uwierzytelniające, a nie blokadę po stronie celu.
Zmienna środowiskowa HTTP_PROXY (i kiedy Go ją ignoruje)
Go's http.DefaultTransport używa http.ProxyFromEnvironment, który odczytuje HTTP_PROXY, HTTPS_PROXY i NO_PROXY (zgodnie z pakietem golang.org/x/net/http/httpproxy). Dwa zachowania wprowadzają ludzi w błąd. Po pierwsze, te zmienne akceptują pełny URL lub nagi host:port, gdzie zakłada się schemat http. Po drugie, żądania do localhost lub adresu pętli zwrotnej zawsze omijają proxy, zwracając pusty URL. Większym problemem jest odwrotność: w momencie, gdy tworzysz własny &http.Transport{Proxy: ...}, zastępujesz zachowanie zmiennych środowiskowych - twoja jawna funkcja wygrywa i HTTP_PROXY jest ignorowane. Jeśli chcesz obu, opakuj je samodzielnie.
SOCKS5 proxy w Go
Biblioteka standardowa nie ma klienta SOCKS5, więc pobierz golang.org/x/net/proxy i wybierz przez niego. Podaj dialer do Transport.DialContext, aby DNS rozwiązywał się w proxy, a nie na twoim komputerze (ten sam problem z wyciekiem nazw hostów, który socks5h rozwiązuje w innych stosach). Jeśli rozważasz oba schematy, nasza uwaga na temat SOCKS5 vs HTTP proxies wyjaśnia, kiedy każdy z nich wygrywa:
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}
Proxy w colly
Jeśli skrobiesz za pomocą colly, otrzymujesz rotację bez dotykania transportu. Pakiet colly/proxy dostarcza RoundRobinProxySwitcher, który cyklicznie zmienia punkty końcowe per request, a LimitRule zarządza tempem. To najszybszy sposób na uprzejmego, rotacyjnego skrobaka w 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,
})
Współbieżność zrobiona dobrze (i pułapka context-canceled)
Współbieżność Go to powód, dla którego zespoły wybierają go do skrobania, a także źródło błędu context canceled. Występuje, gdy kontekst żądania zostanie anulowany przed pełnym odczytaniem ciała - zazwyczaj jest to wygasły limit czasu per request lub cancel(), który uruchomił się zbyt wcześnie z powodu błędnego defer. Ogranicz fan-out za pomocą semafora, daj każdemu żądaniu własny kontekst limitu czasu, a następnie odczytaj i zamknij ciało przed uruchomieniem anulowania:
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()
Dwa dodatkowe uwagi produkcyjne. Ustaw MaxIdleConnsPerHost wystarczająco wysoko, aby duża pula ponownie wykorzystywała połączenia zamiast wyczerpywać deskryptory plików - zwykła przyczyna EOF po kilku setkach żądań. I rotuj wyjście w przypadku awarii zamiast ponawiania tego samego; martwy IP rezydencyjny nie odzyska się przy następnej próbie. Dla szerszego projektu na milionach stron, nasz przewodnik na temat architektury skrobania na dużą skalę obejmuje kolejki, deduplikację i warstwowanie proxy.
Ponowne użycie jednego klienta, dostrojenie transportu
Utwórz http.Client raz i udostępnij go we wszystkich goroutines - jest bezpieczny do współbieżnego użycia, a jego pula połączeń sprawia, że skrobak Go jest szybki. Nowy klient na każde żądanie wyrzuca keep-alive i wymusza nowy handshake TLS za każdym razem. Trzy pola Transport mają znaczenie pod obciążeniem: MaxIdleConns i MaxIdleConnsPerHost określają rozmiar puli, a IdleConnTimeout wycofuje nieaktywne połączenia, więc rotacyjna pula proxy nigdy nie przypina martwego wyjścia. Ustaw je jawnie - domyślne ustawienia biblioteki standardowej są dostrojone do przeglądarki, a nie do skrobaka uruchamiającego tysiące żądań na minutę.

Kiedy przestać ręcznie tworzyć transporty
Kod powyżej wystarcza na czyste cele. Gdy strona dodaje Cloudflare, TLS fingerprinting lub renderowanie JavaScript, surowy handshake TLS Go nie wygląda jak ten w Chrome, i żadne proxy nie naprawi tej niezgodności. W tym momencie Scraper API, który posiada prawdziwy odcisk palca przeglądarki, rotuje IP i renderuje JS za ciebie, to mniej kodu i wyższy wskaźnik sukcesu niż utrzymywanie stosu ręcznie. Jeśli wybierasz język dla nowego skrobaka, nasz post na temat najlepsze proxy do skrobania webowego mapuje kompromisy w różnych stosach.
Przekierowanie renderowania i rotacji do Scraper API
Często zadawane pytania
Jak ustawić proxy dla klienta HTTP Go?
Zbuduj http.Transport z polem Proxy i przekaż go do http.Client. Użyj http.ProxyURL(u) dla jednego statycznego proxy lub func(*http.Request) (*url.URL, error), aby wybrać wyjście per request. Umieść dane uwierzytelniające w URL jako http://user:pass@host:port i zawsze ustaw Client.Timeout.
Dlaczego Go ignoruje moją zmienną HTTP_PROXY?
Ponieważ zbudowałeś niestandardowy Transport z jawną funkcją Proxy, która zastępuje domyślne zachowanie ProxyFromEnvironment. Zmienne środowiskowe mają zastosowanie tylko wtedy, gdy używasz http.DefaultTransport lub sam ustawiasz Proxy: http.ProxyFromEnvironment. Zauważ również, że żądania do loopback i localhost zawsze omijają proxy.
Co powoduje 'context canceled' z proxy Go?
Kontekst żądania został anulowany przed odczytaniem ciała odpowiedzi - zazwyczaj wygasły limit czasu per request lub cancel(), który uruchomił się zbyt wcześnie przez defer. Daj każdemu żądaniu własny kontekst limitu czasu, opróżnij i zamknij ciało przed wykonaniem anulowania, i nie dziel jednego kontekstu anulowalnego między wiele goroutines.
Czy Go obsługuje SOCKS5 proxy?
Nie w bibliotece standardowej, ale golang.org/x/net/proxy dodaje dialer SOCKS5. Utwórz go za pomocą proxy.SOCKS5 i podłącz do Transport.DialContext, aby nazwy hostów rozwiązywały się w proxy, a nie wyciekały przez lokalne wyszukiwanie DNS.
To jest cały obraz po stronie klienta: jedno pole Transport do konfiguracji, funkcja do rotacji, x/net/proxy dla SOCKS5, colly dla uprzejmego skrobania i semafor plus konteksty per request dla współbieżności. Zacznij od czystej rotacyjnej puli, a ominiesz większość listy błędów, zanim się pojawią.