Ротация прокси в 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-скрейпер.






