Rotacja proxy w Colly: Kompletny przewodnik dla programistów Go

Praktyczny przewodnik po integracji rotujących proxy residential z frameworkiem Colly w Go. Przykłady kodu, wzorce produkcyjne i pułapki, które warto znać.

Rotating Proxies in Colly: A Framework-Idiomatic Guide for Go Scrapers
W tym artykule

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 Berlina
  • user-country-US-state-california:pass@gate.proxyhat.com:8080 — IP z Kalifornii
  • user-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 przez colly.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 SetProxyFunc z RoundRobinProxySwitcher lub 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() lub r.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.

Często zadawane pytania

Czym jest rotacja proxy w Colly?

Rotacja proxy w Colly to mechanizm zmiany adresu IP między żądaniami HTTP, implementowany przez SetProxyFunc z RoundRobinProxySwitcher lub własną funkcją proxy. Colly obsługuje schematy http, https i socks5, co pozwala na integrację z bramkami proxy residential takimi jak ProxyHat.

Dlaczego rotacja proxy w Colly ma znaczenie dla użytkowników proxy?

Bez rotacji proxy wszystkie żądania pochodzą z jednego IP, co szybko prowadzi do blokad 403, 429 i banów. Rotacja rozprasza ruch na wiele adresów IP, zmniejszając ryzyko wykrycia i utrzymując przepustowość scrapera. Proxy residential dodatkowo maskują ruch jako organiczny.

Który typ proxy działa najlepiej w rotacji proxy w Colly?

Proxy residential działają najlepiej dla trudnych celów jak SERP Google czy platformy e-commerce, ponieważ używają adresów IP od prawdziwych ISP. Proxy datacenter są szybsze i tańsze, ale łatwo wykrywalne. Proxy mobile są najtrudniejsze do zablokowania, ale najdroższe. Wybierz typ zależnie od celu scrapingu.

Jak unikać blokad implementując rotację proxy w Colly?

Używaj Limit() z Parallelism i RandomDelay, implementuj sticky sessions dla paginacji, rotuj kraje w nazwie użytkownika proxy, dodaj retry z backoffem na błędy 403/429, dostosuj TLS fingerprint i User-Agent. Szanuj robots.txt i utrzymuj rozsądne tempo żądań.

Sprawdź konfigurację proxy w kilka sekund

Darmowy tester proxy — potwierdź, że Twoje IP są szybkie, anonimowe i nieblokowane.

Sprawdź proxy za darmo
← Powrót do Bloga