Ротация прокси в Colly: Полное руководство для Go-разработчиков

Практическое руководство по ротации прокси в Colly — от встроенного RoundRobinProxySwitcher до кастомных SetProxyFunc с резидентными IP. Примеры кода, продакшн-паттерны и типичные ошибки.

Rotating Proxies in Colly: A Framework-Idiomatic Guide for Go Developers
В этой статье

Ротация прокси в Colly — ключевой навык для любого Go-разработчика, который строит высоконагруженный скрейпер. Colly — быстрый и элегантный фреймворк, но без правильной настройки прокси ваш коллектор получит HTTP 403 или CAPTCHA уже после первых десятков запросов. В этом руководстве мы разберём, как использовать proxy.RoundRobinProxySwitcher и c.SetProxyFunc с резидентными IP, как интегрировать ProxyHat и какие продакшн-паттерны применять для масштабирования.

Важно: Эта статья посвящена сбору публично доступных данных. Сбор данных может регулироваться CFAA (США) и GDPR (ЕС). Всегда соблюдайте robots.txt, условия обслуживания сайтов и местное законодательство. Информация здесь — не юридическая консультация.

Почему ротация прокси в Colly необходима

Colly отправляет HTTP-запросы через стандартный net/http клиент Go. По умолчанию все запросы идут с одного IP-адреса вашей машины. Современные антибот-системы — Cloudflare, Akamai Bot Manager, PerimeterX — анализируют поведение и быстро блокируют IP, с которого поступает аномальный трафик. Типичный порог — 50–200 запросов с одного IP за короткий промежуток времени.

Ротация прокси решает эту проблему, распределяя запросы по пулу IP-адресов. Веб-скрейпинг с ротацией резидентных прокси снижает риск блокировок до минимума, поскольку каждый запрос выглядит как отдельный пользователь из реального ISP.

Типы прокси: сравнение

Тип проксиДетектируемостьСкоростьСтоимостьПодходит для
DatacenterВысокаяВысокая (<50ms)Низкая (~$1/GB)Простые сайты, API
ResidentialНизкаяСредняя (100–500ms)Средняя (~$5–15/GB)SERP, e-commerce, соцсети
MobileОчень низкаяНизкая (300–1000ms)Высокая (~$20–50/GB)Жёсткие антибот-системы

Резидентные прокси — оптимальный баланс для большинства задач SERP-трекинга и парсинга e-commerce. Они используют реальные IP-адреса провайдеров, что делает их практически неотличимыми от обычных пользователей.

Модель коллектора Colly: OnRequest, OnHTML, OnError

Colly построен вокруг colly.Collector — структуры, которая управляет очередью запросов, HTTP-клиентом и callback-хуками. Понимание модели коллектора критично для правильной интеграции прокси.

Callback-хуки

Colly предоставляет несколько хуков, которые вызываются на разных стадиях жизненного цикла запроса:

  • OnRequest(func(*colly.Request)) — вызывается перед отправкой. Здесь можно установить заголовки, изменить URL, логировать.
  • OnResponse(func(*colly.Response)) — вызывается после получения ответа.
  • OnHTML(goquerySelector, func(*colly.HTMLElement)) — вызывается для каждого элемента,匹配ающего селектору. Использует goquery для DOM-обхода.
  • OnError(func(*colly.Response, error)) — вызывается при ошибке запроса.
  • OnScraped(func(*colly.Response)) — вызывается после полной обработки ответа.

Асинхронный режим включается вызовом c.Async = true. В этом режиме Colly использует горутины для параллельной обработки запросов, что критично для производительности, но требует осторожной настройки Limit() и ротации прокси.

Идиоматическая поверхность прокси: RoundRobinProxySwitcher и SetProxyFunc

Colly предоставляет два способа работы с прокси: встроенный proxy.RoundRobinProxySwitcher и кастомная функция через c.SetProxyFunc. Оба принимают func(*http.Request) (*url.URL, error) — функцию, которая возвращает URL прокси для конкретного запроса.

RoundRobinProxySwitcher — простой round-robin

Встроенный свитчер принимает список URL прокси и поочерёдно назначает их запросам. Поддерживает http, https и socks5 схемы:

package main

import (
    "fmt"
    "log"
    "net/url"

    "github.com/gocolly/colly"
    "github.com/gocolly/colly/proxy"
)

func main() {
    c := colly.NewCollector()

    proxies := []string{
        "http://user-country-US:pass@gate.proxyhat.com:8080",
        "http://user-country-DE:pass@gate.proxyhat.com:8080",
        "http://user-country-GB:pass@gate.proxyhat.com:8080",
    }

    rp, err := proxy.RoundRobinProxySwitcher(proxies...)
    if err != nil {
        log.Fatal(err)
    }
    c.SetProxyFunc(rp)

    c.OnHTML("title", func(e *colly.HTMLElement) {
        fmt.Println(e.Text)
    })

    c.Visit("https://httpbin.org/ip")
}

Это базовый подход, но он имеет ограничение: список прокси фиксирован на старте. Для динамической ротации с гео-таргетингом и sticky-сессиями нужен кастомный свитчер.

Кастомный SetProxyFunc — полный контроль

Интерфейс c.SetProxyFunc принимает любую функцию с сигнатурой func(*http.Request) (*url.URL, error). Это позволяет реализовать любую логику ротации — round-robin, random, sticky-сессии, гео-таргетинг по домену:

package main

import (
    "fmt"
    "log"
    "math/rand"
    "net/url"
    "sync/atomic"

    "github.com/gocolly/colly"
)

var counter uint64

func proxySwitcher(req *http.Request) (*url.URL, error) {
    countries := []string{"US", "DE", "GB", "FR", "JP"}
    idx := atomic.AddUint64(&counter, 1) % uint64(len(countries))
    country := countries[idx]

    // Уникальная сессия для каждого запроса
    session := fmt.Sprintf("sess-%d", atomic.LoadUint64(&counter))

    proxyURL := fmt.Sprintf(
        "http://user-country-%s-session-%s:pass@gate.proxyhat.com:8080",
        country, session,
    )
    return url.Parse(proxyURL)
}

func main() {
    c := colly.NewCollector()
    c.SetProxyFunc(proxySwitcher)

    c.OnResponse(func(r *colly.Response) {
        fmt.Printf("Status: %d, Body: %s\n", r.StatusCode, string(r.Body[:100]))
    })

    for i := 0; i < 10; i++ {
        c.Visit("https://httpbin.org/ip")
    }
    c.Wait()
}

Резидентные прокси для сложных целей: гео-таргетинг и сессии

ProxyHat кодирует параметры гео-таргетинга и сессий прямо в username. Это означает, что каждый запрос может использовать разные параметры без отдельного пула прокси-URL — достаточно динамически формировать строку username.

Форматы username

  • user-country-DE:pass — IP из Германии
  • user-country-DE-city-berlin:pass — IP из Берлина
  • user-session-abc123:pass — sticky-сессия (один IP для всех запросов с этой сессией)
  • user-country-DE-session-abc123:pass — комбинированный режим

Sticky-сессии особенно важны для многостраничного скрейпинга — например, пагинации каталога, где каждый запрос должен идти с того же IP, чтобы сервер «запомнил» вашу корзину или сессию.

Runnable-пример: коллектор с резидентной ротацией, Limit и custom switcher

Полный пример с Limit-правилами, асинхронным режимом и кастомным свитчером, читающим список стран:

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "net/url"
    "sync/atomic"
    "time"

    "github.com/gocolly/colly"
)

type ProxyConfig struct {
    Username string
    Password string
    Gateway  string
    Port     int
}

type SmartSwitcher struct {
    config   ProxyConfig
    counter  uint64
    countries []string
}

func (s *SmartSwitcher) GetProxy(req *http.Request) (*url.URL, error) {
    idx := atomic.AddUint64(&s.counter, 1) % uint64(len(s.countries))
    country := s.countries[idx]
    session := fmt.Sprintf("sess-%d-%d", time.Now().Unix(), atomic.LoadUint64(&s.counter))

    proxyStr := fmt.Sprintf("http://%s-country-%s-session-%s:%s@%s:%d",
        s.config.Username, country, session,
        s.config.Password, s.config.Gateway, s.config.Port,
    )
    return url.Parse(proxyStr)
}

func main() {
    switcher := &SmartSwitcher{
        config: ProxyConfig{
            Username: "youruser",
            Password: "yourpass",
            Gateway:  "gate.proxyhat.com",
            Port:     8080,
        },
        countries: []string{"US", "DE", "GB", "FR", "NL", "JP"},
    }

    c := colly.NewCollector(
        colly.Async(true),
    )
    c.SetProxyFunc(switcher.GetProxy)

    // Лимиты: домен, параллелизм, задержка
    c.Limit(&colly.LimitRule{
        DomainGlob:  "*httpbin.org",
        Parallelism: 5,
        Delay:       2 * time.Second,
        RandomDelay: 1 * time.Second,
    })

    results := make([]string, 0)
    var mu sync.Mutex

    c.OnHTML("body", func(e *colly.HTMLElement) {
        mu.Lock()
        defer mu.Unlock()
        results = append(results, e.Text)
    })

    c.OnError(func(r *colly.Response, err error) {
        log.Printf("Error on %s: %v", r.Request.URL, err)
    })

    for i := 0; i < 20; i++ {
        c.Visit(fmt.Sprintf("https://httpbin.org/ip?req=%d", i))
    }
    c.Wait()

    for i, r := range results {
        fmt.Printf("Result %d: %s\n", i, r)
    }
}

Ключевые моменты этого примера:

  • Parallelism: 5 — не более 5 одновременных запросов на домен.
  • Delay: 2 * time.Second с RandomDelay: 1 * time.Second — каждый запрос задерживается на 2–3 секунды.
  • Каждый запрос получает уникальную сессию и страну — максимальная ротация.
  • c.Wait() — ожидание завершения всех асинхронных запросов.

Продакшн-паттерны: ретраи, TLS, распределённое скрейпинг

Ретраи через c.Clone()

Colly не имеет встроенного механизма ретраев, но идиоматичный паттерн — клонирование коллектора в OnError. Клон наследует настройки прокси, но получает свежий HTTP-клиент:

var retryCount = make(map[string]int)
var maxRetries = 3

c.OnError(func(r *colly.Response, err error) {
    urlStr := r.Request.URL.String()
    if retryCount[urlStr] < maxRetries {
        retryCount[urlStr]++
        log.Printf("Retry %d for %s", retryCount[urlStr], urlStr)
        // Клон наследует SetProxyFunc
        r.Request.Ctx.Put("retry", retryCount[urlStr])
        c.Clone().Visit(urlStr)
    } else {
        log.Printf("Max retries reached for %s", urlStr)
    }
})

Кастомный Transport с TLS-конфигурацией

Некоторые сайты требуют специфичных TLS-настроек. Colly позволяет заменить http.Transport через c.WithTransport():

import (
    "crypto/tls"
    "net/http"
)

c.WithTransport(&http.Transport{
    TLSClientConfig: &tls.Config{
        InsecureSkipVerify: false,
        MinVersion:         tls.VersionTLS12,
        MaxVersion:         tls.VersionTLS13,
    },
    MaxIdleConns:        100,
    MaxIdleConnsPerHost: 10,
    IdleConnTimeout:    30 * time.Second,
})

Распределённый скрейпинг через Redis-очередь

Colly поддерживает несколько storage-бэкендов для распределённой работы. Redis-бэкенд позволяет нескольким инстансам Colly разделять очередь URL и cookies:

import (
    "github.com/gocolly/redisstorage"
    "github.com/gocolly/colly"
)

storage := &redisstorage.Storage{
    Address:  "redis://localhost:6379",
    Prefix:   "colly_scraper",
    Client:   nil, // auto-create
}

if err := storage.Init(); err != nil {
    log.Fatal(err)
}

c := colly.NewCollector()
c.SetStorage(storage)

// Несколько инстансов Colly на разных машинах
// будут разделять одну очередь URL

Это позволяет масштабировать скрейпер горизонтально — несколько контейнеров или VM, каждый со своим пулом прокси, но с единой координацией через Redis. Для контейнеризации удобен Docker с переменными окружения для credentials:

# Dockerfile
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY . .
RUN go mod download && go build -o scraper ./cmd/scraper

FROM alpine:3.19
COPY --from=builder /app/scraper /usr/local/bin/scraper
ENV PROXYHAT_USER=youruser
ENV PROXYHAT_PASS=yourpass
CMD ["scraper"]

Контейнерный флот

Для высоконагруженного скрейпинга эффективен подход «headless-флот» — несколько Docker-контейнеров, каждый со своим инстансом Colly и своим набором стран. Kubernetes или Docker Compose управляют оркестрацией. Каждый контейнер можно настроить на свой регион через переменные окружения, а Redis-бэкенд координирует распределение URL.

Когда НЕ использовать Colly

Colly — HTTP-клиент, не браузер. Если целевой сайт рендерит контент через JavaScript (React, Vue, Angular SPA), Colly не выполнит JS и не получит динамический контент. В таких случаях нужен chromedp или Playwright для Go.

Признаки, что Colly не подходит:

  • Контент загружается через XHR/fetch после загрузки страницы.
  • Сайт требует выполнения JavaScript для отображения данных.
  • Антибот-система требует отрисовки canvas или WebGL-фингерпринта.
  • Нужна эмуляция скроллинга или кликов.

В этих сценариях комбинируйте Colly для статичных страниц с headless-браузером для JS-зависимых. Прокси-ротация в chromedp настраивается через --proxy-server флаг Chrome.

Этика и правовые аспекты

Сбор публичных данных — законная деятельность, но с ограничениями. Ключевые принципы:

  • Соблюдайте robots.txt. Colly может автоматически читать robots.txt через colly.NewCollector(colly.RobotsTxt()), но это не заменяет ручную проверку.
  • Не превышайте разумные лимиты. 1–5 запросов в секунду на домен — безопасный диапазон. 100+ запросов в секунду — это DoS, не скрейпинг.
  • GDPR. Сбор персональных данных граждан ЕС требует правового основания. Публично доступные деловые данные обычно безопасны, но консультируйтесь с юристом.
  • CFAA (США). Обход технических барьеров защиты может нарушать Computer Fraud and Abuse Act. Не обходьте paywall или аутентификацию без разрешения.

ProxyHat предоставляет более 190 локаций для гео-таргетинга и поддерживает HTTP, SOCKS5 и sticky-сессии. Ознакомьтесь с тарифами для выбора подходящего типа прокси.

Ключевые выводы

  • RoundRobinProxySwitcher — простой старт для базовой ротации; SetProxyFunc с кастомной функцией — для продакшна с гео-таргетингом и сессиями.
  • Резидентные прокси с динамическим формированием username (-country-DE-session-abc123) — идиоматичный способ ротации в ProxyHat без необходимости управлять пулом URL.
  • Limit() с Parallelism, Delay и RandomDelay — обязательная настройка для любого продакшн-скрейпера.
  • Ретраи через c.Clone() в OnError — паттерн Colly для обработки временных сбоев.
  • Redis-бэкенд позволяет горизонтально масштабировать несколько инстансов Colly с разделяемой очередью.
  • Colly не подходит для JS-зависимых SPA — используйте chromedp или Playwright.

Часто задаваемые вопросы

Что такое ротация прокси в Colly?

Ротация прокси в Colly — это механизм автоматической смены IP-адресов между HTTP-запросами коллектора. Colly предоставляет встроенный proxy.RoundRobinProxySwitcher и интерфейс SetProxyFunc, позволяющий передать кастомную функцию func(*http.Request) (*url.URL, error), которая определяет, через какой прокси отправить каждый запрос. Это позволяет распределить трафик по пулу IP-адресов и избежать блокировок.

Зачем нужна ротация прокси в Colly для скрейпинга?

Ротация прокси предотвращает блокировку IP-адресов целевыми сайтами. Без ротации все запросы идут с одного IP, и антибот-системы (Cloudflare, Akamai) быстро ограничивают доступ — типичный порог 50–200 запросов с одного IP. Ротация резидентных прокси позволяет распределить запросы по множеству реальных IP-адресов, снижая риск блокировок и CAPTCHA до минимума.

Какой тип прокси лучше всего подходит для ротации в Colly?

Резидентные прокси — оптимальный выбор для сложных целей, так как они используют реальные IP-адреса провайдеров и реже блокируются антибот-системами. Дата-центр прокси подходят для простых задач (открытые API, статичные страницы), но легко детектируются. Мобильные прокси обеспечивают максимальную надёжность для жёстких антибот-систем, но стоят дороже — $20–50/GB против $5–15/GB для резидентных.

Как избежать блокировок при реализации ротации прокси в Colly?

Используйте резидентные прокси с гео-таргетингом, устанавливайте разумные задержки через Limit() (1–5 запросов в секунду на домен), рандомизируйте User-Agent, используйте sticky-сессии для многостраничных потоков и реализуйте ретраи через c.Clone() в OnError. Уважайте robots.txt и не превышайте разумный лимит запросов. Комбинируйте Delay с RandomDelay для естественного паттерна трафика.

Готовы начать? Изучите тарифы ProxyHat и документацию для быстрой интеграции резидентных прокси в ваш Colly-скрейпер.

Часто задаваемые вопросы

Что такое ротация прокси в Colly?

Ротация прокси в Colly — это механизм автоматической смены IP-адресов между HTTP-запросами коллектора. Colly предоставляет встроенный proxy.RoundRobinProxySwitcher и интерфейс SetProxyFunc, позволяющий передать кастомную функцию func(*http.Request) (*url.URL, error), которая определяет, через какой прокси отправить каждый запрос.

Зачем нужна ротация прокси в Colly для скрейпинга?

Ротация прокси предотвращает блокировку IP-адресов целевыми сайтами. Без ротации все запросы идут с одного IP, и антибот-системы быстро ограничивают доступ. Ротация резидентных прокси позволяет распределить запросы по множеству реальных IP-адресов, снижая риск блокировок и CAPTCHA.

Какой тип прокси лучше всего подходит для ротации в Colly?

Резидентные прокси — оптимальный выбор для сложных целей, так как они используют реальные IP-адреса провайдеров и реже блокируются антибот-системами. Дата-центр прокси подходят для простых задач, но легко детектируются. Мобильные прокси обеспечивают максимальную надёжность, но стоят дороже.

Как избежать блокировок при реализации ротации прокси в Colly?

Используйте резидентные прокси с гео-таргетингом, устанавливайте разумные задержки через Limit(), рандомизируйте User-Agent, используйте sticky-сессии для многостраничных потоков и реализуйте ретраи через c.Clone(). Уважайте robots.txt и не превышайте разумный лимит запросов — 1–5 запросов в секунду на домен.

Готовы начать?

Резидентные, ISP и мобильные прокси в 148+ странах. Создайте бесплатный аккаунт.

Создать бесплатный аккаунт
← Вернуться в Блог