Scraping w Go z Colly to potężne narzędzie, ale bez rotacji proxy szybko napotkasz blokady, 403 i bany IP. Rotacja proxy w Colly to nie tylko dodanie adresu do konfiguracji — to architektura, która wpływa na współbieżność, opóźnienia i niezawodność całego scrapera. W tym przewodniku pokazujemy, jak zintegrować proxy residential z ProxyHat, jak używać proxy.RoundRobinProxySwitcher i własnych funkcji proxy, oraz jak skalować zbieranie danych w produkcji.
Ważna uwaga prawna: Scraping danych publicznych jest legalny w wielu jurysdykcjach, ale może podlegać ograniczeniom na podstawie ustawy CFAA w USA oraz RODO (GDPR) w UE. Szanuj robots.txt, warunki korzystania z serwisu i nie zbieraj danych osobowych bez podstawy prawnej.
Model Collector w Colly: callbacki, tryb asynchroniczny i goquery
Colly buduje scrapery wokół obiektu Collector, który zarządza cyklem życia żądań HTTP. Główne punkty zaczepienia to callbacki OnRequest, OnHTML, OnError oraz konfiguracja asynchroniczna przez Async i Limit. Zrozumienie tego modelu jest kluczowe, zanim dodasz rotację proxy.
Callbacki i DOM traversal
OnRequest wywoływany jest przed wysłaniem żądania — to idealne miejsce na modyfikację nagłówków lub ustawienie proxy dla pojedynczego żądania. OnHTML korzysta z goquery — portem jQuery dla Go — pozwalając na selektory CSS i manipulację DOM. OnError przechwytuje błędy sieciowe i kody HTTP, co jest niezbędne do logiki ponownych prób.
package main
import (
"fmt"
"github.com/gocolly/colly/v2"
)
func main() {
c := colly.NewCollector()
c.OnRequest(func(r *colly.Request) {
fmt.Println("Wysyłam do:", r.URL)
r.Headers.Set("User-Agent", "MyScraper/1.0")
})
c.OnHTML("h1", func(e *colly.HTMLElement) {
fmt.Println("Tytuł:", e.Text)
})
c.OnError(func(r *colly.Response, err error) {
fmt.Printf("Błąd %d: %v\n", r.StatusCode, err)
})
c.Visit("https://example.com")
}
Tryb asynchroniczny i Limit
Domyślnie Colly działa synchronicznie. Włączenie c.Async = true uruchamia żądania w goroutinach, a c.Limit() kontroluje współbieżność i opóźnienia. To kluczowe dla uniknięcia przeciążenia serwera i własnego proxy.
c := colly.NewCollector(
colly.Async(true),
)
c.Limit(&colly.LimitRule{
DomainGlob: "*",
Parallelism: 20,
Delay: 500 * time.Millisecond,
RandomDelay: 200 * time.Millisecond,
})
Przy 20 równoległych żądaniach i losowym opóźnieniu 200-700 ms, scraper generuje około 40 żądań na sekundę — wystarczająco, by wielu dostawców proxy residential obsłużyło ruch bez flag blokady.
Idiomatyczna powierzchnia proxy: RoundRobinProxySwitcher i SetProxyFunc
Colly proxy switcher to oficjalny mechanizm rotacji. Pakiet github.com/gocolly/colly/v2/proxy dostarcza RoundRobinProxySwitcher, który przyjmuje listę URL-i proxy i zwraca funkcję proxy.Func kompatybilną z c.SetProxyFunc().
Podstawowa rotacja RoundRobin
import (
"github.com/gocolly/colly/v2"
"github.com/gocolly/colly/v2/proxy"
)
func main() {
proxies := []string{
"http://user-country-DE:pass@gate.proxyhat.com:8080",
"http://user-country-US:pass@gate.proxyhat.com:8080",
"http://user-country-GB:pass@gate.proxyhat.com:8080",
"socks5://user-country-FR:pass@gate.proxyhat.com:1080",
}
rp, err := proxy.RoundRobinProxySwitcher(proxies...)
if err != nil {
panic(err)
}
c := colly.NewCollector()
c.SetProxyFunc(rp)
c.OnHTML("title", func(e *colly.HTMLElement) {
fmt.Println(e.Text)
})
c.Visit("https://example.com")
}
RoundRobinProxySwitcher obsługuje schematy http, https oraz socks5, więc możesz mieszać proxy HTTP i SOCKS5 w jednej liście. ProxyHat wspiera oba protokoły — HTTP na porcie 8080, SOCKS5 na 1080.
Własna funkcja proxy: func(*http.Request) (*url.URL, error)
RoundRobin jest prosty, ale nie wystarcza, gdy potrzebujesz logiki per-żądanie — np. przyklejonej sesji dla paginacji lub geo-targetowania zależnego od domeny. Wtedy implementujesz własną funkcję zgodną z proxy.Func:
type ProxyFunc func(*http.Request) (*url.URL, error)
customSwitcher := func(req *http.Request) (*url.URL, error) {
// Przyklejona sesja dla tej domeny
sessionID := hashDomain(req.URL.Host)
proxyURL := fmt.Sprintf(
"http://user-session-%s-country-DE:pass@gate.proxyhat.com:8080",
sessionID,
)
return url.Parse(proxyURL)
}
c.SetProxyFunc(customSwitcher)
To podejście jest golang colly proxy w najczystszej formie — pełna kontrola nad wyborem IP dla każdego żądania, z dostępem do URL, nagłówków i kontekstu.
Dlaczego proxy residential są wymagane dla trudnych celów
Proxy datacenter są tańsze i szybsze, ale ich zakresy IP są łatwo identyfikowalne przez usługi anti-bot takie jak Cloudflare, Datadome czy PerimeterX. Proxy residential używają adresów przydzielanych przez ISP zwykłym użytkownikom domowym, co sprawia, że ruch wygląda organicznie.
Dla celów takich jak SERP Google, platformy e-commerce czy portale z biletami, proxy residential to wymóg, nie opcja. ProxyHat oferuje lokalizacje w ponad 190 krajach z geo-targetowaniem na poziomie kraju i miasta.
Geo-targetowanie i sesje w nazwie użytkownika
ProxyHat koduje parametry sesji i geolokalizacji w nazwie użytkownika, co pozwala na rotację bez zarządzania pulą adresów IP:
user-country-DE-city-berlin:pass@gate.proxyhat.com:8080— IP z Berlinauser-country-US-state-california:pass@gate.proxyhat.com:8080— IP z Kaliforniiuser-session-abc123:pass@gate.proxyhat.com:8080— sticky session (to samo IP dla wielu żądań)user-session-abc123-country-DE-city-berlin:pass@gate.proxyhat.com:8080— sticky + geo
Sticky sessions utrzymują to samo IP przez okres ważności sesji (zwykle do 30 minut), co jest niezbędne dla logowania, koszyków zakupowych i paginacji, gdzie zmiana IP w trakcie sesji wyzwala anti-bot.
Przykład: kompletny scraper z rotacją residential
Poniżej znajduje się kompletny, uruchamialny program Go, który integruje proxy residential ProxyHat z Colly, implementuje własny switcher z geo-rotacją i zarządza współbieżnością.
package main
import (
"crypto/sha1"
"encoding/hex"
"fmt"
"math/rand"
"net/url"
"sync/atomic"
"time"
"github.com/gocolly/colly/v2"
"github.com/gocolly/colly/v2/proxy"
)
var countries = []string{"DE", "US", "GB", "FR", "NL", "PL"}
var counter uint64
func hashDomain(domain string) string {
h := sha1.Sum([]byte(domain))
return hex.EncodeToString(h[:8])
}
// residentialSwitcher rotuje kraje per-żądanie, ale przykleja sesję
// dla tej samej domeny (sticky session).
func residentialSwitcher(req *http.Request) (*url.URL, error) {
idx := atomic.AddUint64(&counter, 1) % uint64(len(countries))
country := countries[idx]
// Dla paginacji tej samej domeny używamy stałej sesji
session := hashDomain(req.URL.Host)
proxyStr := fmt.Sprintf(
"http://user-session-%s-country-%s:pass@gate.proxyhat.com:8080",
session, country,
)
return url.Parse(proxyStr)
}
func main() {
c := colly.NewCollector(
colly.Async(true),
colly.UserAgent("Mozilla/5.0 (compatible; ResearchBot/1.0)"),
)
// Limit współbieżności: 15 równoległych żądań, opóźnienie 300-600ms
c.Limit(&colly.LimitRule{
DomainGlob: "*",
Parallelism: 15,
Delay: 300 * time.Millisecond,
RandomDelay: 300 * time.Millisecond,
})
// Ustawienie własnej funkcji proxy
c.SetProxyFunc(residentialSwitcher)
// Alternatywnie: RoundRobinProxySwitcher z predefiniowaną listą
proxyList := []string{
"http://user-country-DE-city-berlin:pass@gate.proxyhat.com:8080",
"http://user-country-US:pass@gate.proxyhat.com:8080",
"http://user-country-PL-city-warsaw:pass@gate.proxyhat.com:8080",
}
rp, _ := proxy.RoundRobinProxySwitcher(proxyList...)
// c.SetProxyFunc(rp) // odkomentuj, aby użyć RoundRobin
visited := make(map[string]bool)
var mu sync.Mutex
c.OnHTML("a[href]", func(e *colly.HTMLElement) {
link := e.Attr("href")
e.Request.Visit(link)
})
c.OnHTML("title", func(e *colly.HTMLElement) {
mu.Lock()
fmt.Printf("[%s] %s\n", e.Request.URL, e.Text)
mu.Unlock()
})
c.OnError(func(r *colly.Response, err error) {
fmt.Printf("Błąd %d na %s: %v\n", r.StatusCode, r.Request.URL, err)
})
c.Visit("https://example.com")
c.Wait() // czeka na zakończenie wszystkich żądań
}
Przy 15 równoległych połączeniach i opóźnieniu 300-600 ms, ten scraper utrzymuje przepustowość około 25-50 żądań na sekundę, co mieści się w komforcie puli proxy residential ProxyHat bez wyzwalania limitów.
Wzorce produkcyjne: ponowne próby, TLS i składowanie rozproszone
Ponowne próby z c.Clone()
Colly nie ma wbudowanego mechanizmu retry, ale c.Clone() pozwala utworzyć podrzędny Collector z tą samą konfiguracją. Typowym wzorcem jest ponowienie żądania z innym proxy po błędzie 403 lub 429.
maxRetries := 3
c.OnError(func(r *colly.Response, err error) {
if r.StatusCode == 403 || r.StatusCode == 429 {
retries := 0
if v, ok := r.Ctx.GetAny("retries").(int); ok {
retries = v
}
if retries < maxRetries {
r.Ctx.Put("retries", retries+1)
time.Sleep(time.Duration(retries+1) * 2 * time.Second)
r.Request.Retry()
}
}
})
Niestandardowy Transport z konfiguracją TLS
Niektóre serwery anti-bot sprawdzają odcisk palca TLS (JA3). Dostosowanie http.Transport pozwala na rotację cipher suites i timeoutów:
transport := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: false,
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_AES_128_GCM_SHA256,
tls.TLS_AES_256_GCM_SHA384,
tls.TLS_CHACHA20_POLY1305_SHA256,
},
},
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
}
c.SetTransport(transport)
Skraper rozproszony z redis storage
Colly wspiera storage backends — domyślnie pamięć, ale także Redis. Redis storage synchronizuje odwiedzone URL-e między wieloma instancjami scrapera, co jest kluczowe przy uruchamianiu w kontenerach lub na wielu maszynach.
import "github.com/gocolly/colly/v2/storage"
redisStorage := &storage.RedisStorage{
Address: "redis://localhost:6379",
Prefix: "scraper_",
Client: nil,
}
c.SetStorage(redisStorage)
Przy 3-5 instancjach i 50 żądań/s na instancję, osiągasz 150-250 żądań/s — wystarczająco do zebrania tysięcy stron w kilkanaście minut. Sprawdź ceny ProxyHat, aby dobrać plan przepustowości.
Konteneryzacja i headless fleet
Najlepszą praktyką jest uruchamianie scrapera w kontenerze Docker z limitami zasobów i rotacją proxy per-kontener. Przykład Dockerfile:
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY . .
RUN go mod download && go build -o scraper .
FROM alpine:3.19
COPY --from=builder /app/scraper /scraper
CMD ["/scraper"]
Skalowanie przez docker compose up --scale scraper=5 uruchamia 5 instancji, każda z niezależnym counterem rotacji proxy, co zwiększa różnorodność IP.
Kiedy NIE używać Colly: SPA i JS-heavy strony
Colly wykonuje tylko żądania HTTP i parsuje HTML — nie wykonuje JavaScript. Strony oparte na React, Vue czy Angular, które renderują treść po stronie klienta, zwrócą pusty lub częściowy DOM. W takich przypadkach potrzebujesz przeglądarki headless (Playwright, Puppeteer, chromedp).
Colly jest idealne dla:
- Stron statycznych i serwer-side rendered (SSR)
- API JSON endpointów (REST, GraphQL)
- Stron z prostym JavaScript, gdzie dane są w inline JSON lub
<script>tagach
Dla stron SPA rozważ chromedp z proxy ProxyHat skonfigurowanym na poziomie Chrome.
Etyka i odpowiedzialny scraping
Scraping danych publicznych jest legalny, ale musisz:
- Szanować
robots.txt— Colly ma wbudowane wsparcie przezcolly.IgnoreRobotsTxt()(domyślnie szanuje) - Utrzymywać rozsądne tempo — nie przekraczaj 1-2 żądań/s na domenę bez proxy
- Nie zbierać danych osobowych bez podstawy prawnej (RODO)
- Sprawdzać warunki korzystania z serwisu (ToS)
- Nie obciążać serwera celowego (DoS-like behavior)
Szczegółowe wskazówki znajdziesz w dokumentacji ProxyHat oraz na stronie przypadków użycia web scrapingu.
Kluczowe wnioski
- Rotacja proxy w Colly odbywa się przez
SetProxyFunczRoundRobinProxySwitcherlub własną funkcjąfunc(*http.Request) (*url.URL, error).- Proxy residential są wymagane dla trudnych celów — koduj geo i sesje w nazwie użytkownika ProxyHat.
- Sticky sessions (
-session-abc123) są niezbędne dla paginacji i logowania.- Używaj
Limit()z Parallelism i RandomDelay, aby unikać banów i przeciążeń.- Implementuj retry przez
c.Clone()lubr.Request.Retry()z backoffem.- Redis storage synchronizuje odwiedzone URL-e w scraperach rozproszonych.
- Colly nie wykonuje JavaScript — dla SPA używaj przeglądarki headless.
Jeśli budujesz scraper SERP lub monitor cen, sprawdź nasze przypadki użycia śledzenia SERP i web scrapingu, aby zobaczyć, jak ProxyHat wpisuje się w Twój stos.






