Ротация прокси в Colly: руководство для Go-разработчиков
Colly — самый популярный скрейпинг-фреймворк для Go, но рано или поздно каждый разработчик сталкивается с блокировками по IP. Ротация прокси в Colly — это не просто переключение адресов между запросами, а архитектурный паттерн, затрагивающий middleware, управление сессиями и стратегию обхода. В этом руководстве мы разберём, как идиоматично встроить residential-прокси в ваш коллектор, почему дата-центровые IP не работают на сложных мишенях и как масштабировать скрейпинг до 1500 запросов в минуту.
Правовая оговорка: Сбор публичных данных не освобождает от ответственности. В США Computer Fraud and Abuse Act (CFAA) может квалифицировать обход технических средств защиты как правонарушение, а в ЕС GDPR регулирует обработку персональных данных, включая IP-адреса. Уважайте robots.txt, соблюдайте ToS сайтов и не скрейпьте данные за аутентификацией без разрешения.
Архитектура Collector в Colly
Colly построен вокруг Collector — структуры, которая управляет всем жизненным циклом скрейпинга: от формирования HTTP-запроса до парсинга HTML. Понимание модели Collector критически важно для правильной интеграции прокси.
OnRequest, OnHTML, OnError — три кита Colly
Коллектор использует callback-архитектуру. Три основных хука:
- OnRequest — вызывается перед отправкой запроса. Здесь можно модифицировать заголовки, добавить cookies или логировать URL.
- OnHTML — вызывается после парсинга ответа. Использует goquery для DOM-обхода. Например,
c.OnHTML("a[href]", func(e *colly.HTMLElement) { ... })найдёт все ссылки на странице. - OnError — вызывается при ошибке запроса. Идеальное место для ретраев с новым прокси.
Эти хуки — точки расширения, куда встраивается вся логика прокси-ротации. Colly не навязывает конкретную стратегию; вместо этого он предоставляет интерфейсы, через которые вы подключаете свой proxy-слой.
Async-режим и goquery
По умолчанию Colly работает синхронно — каждый Visit() блокирует до завершения запроса. Для высокопроизводительного скрейпинга включается async-режим через colly.Async(true), после чего запросы отправляются в горутинах. Управление параллелизмом осуществляется через c.Limit(), который задаёт Parallelism (число одновременных запросов) и RandomDelay (случайная задержка между запросами).
Парсинг HTML выполняется через goquery — порт jQuery для Go. Colly оборачивает goquery в colly.HTMLElement, добавляя удобные методы вроде e.ChildText("selector") и e.ForEach("selector", func(i int, el *colly.HTMLElement) { ... }).
Идиоматичный proxy-слой: RoundRobinProxySwitcher и SetProxyFunc
Colly предоставляет два уровня работы с прокси. Первый — встроенный colly.ProxySwitcher(), который создаёт round-robin переключатель из списка URL. Второй — c.SetProxyFunc(func(*http.Request) (*url.URL, error)), позволяющий полностью контролировать выбор прокси для каждого запроса.
RoundRobinProxySwitcher: простая ротация
Самый быстрый способ начать — передать список прокси-URL в colly.ProxySwitcher():
package main
import (
"log"
"github.com/gocolly/colly/v2"
)
func main() {
c := colly.NewCollector()
// Residential proxies with different geo-targets
proxyURLs := []string{
"http://user-country-US:YOUR_PASSWORD@gate.proxyhat.com:8080",
"http://user-country-DE-city-berlin:YOUR_PASSWORD@gate.proxyhat.com:8080",
"http://user-country-GB:YOUR_PASSWORD@gate.proxyhat.com:8080",
"http://user-country-FR:YOUR_PASSWORD@gate.proxyhat.com:8080",
}
rp, err := colly.ProxySwitcher(proxyURLs...)
if err != nil {
log.Fatal(err)
}
c.SetProxyFunc(rp)
c.OnHTML("title", func(e *colly.HTMLElement) {
log.Println("Title:", e.Text)
})
c.Visit("https://example.com")
}
Этот подход работает, но у него есть ограничение: список прокси фиксирован. Если вам нужно динамически менять страну или сессию для каждого запроса, нужен кастомный switcher.
SetProxyFunc: полный контроль над ротацией
Для production-скрейпинга лучше использовать SetProxyFunc с собственной функцией. Это позволяет генерировать уникальный proxy-URL для каждого запроса, включая ротацию стран, городов и сессий:
package main
import (
"fmt"
"net/http"
"net/url"
"sync/atomic"
"github.com/gocolly/colly/v2"
)
var counter uint64
func residentialSwitcher(r *http.Request) (*url.URL, error) {
countries := []string{"US", "DE", "GB", "FR", "JP"}
idx := atomic.AddUint64(&counter, 1)
country := countries[idx%uint64(len(countries))]
session := fmt.Sprintf("sess-%d", idx)
return url.Parse(fmt.Sprintf(
"http://user-country-%s-session-%s:YOUR_PASSWORD@gate.proxyhat.com:8080",
country, session,
))
}
func main() {
c := colly.NewCollector()
c.SetProxyFunc(residentialSwitcher)
c.OnHTML("title", func(e *colly.HTMLElement) {
fmt.Println("Title:", e.Text)
})
c.Visit("https://example.com")
}
Здесь каждый запрос получает уникальную сессию (sess-1, sess-2, ...) и ротацию по странам. Это идиоматичный способ реализации colly proxy rotation в Go — proxy-функция вызывается для каждого http.Request, что даёт гранулярный контроль без хаков.
Почему residential-прокси необходимы для сложных мишеней
Дата-центровые прокси дешёвые и быстрые, но их легко обнаружить. Современные антибот-системы (Cloudflare, Datadome, PerimeterX) проверяют ASN и помечают запросы от дата-центров как подозрительные. Residential-прокси используют IP-адреса реальных интернет-провайдеров, что делает их неотличимыми от обычного трафика.
| Характеристика | Datacenter | Residential | Mobile |
|---|---|---|---|
| Скорость | Высокая (<50ms) | Средняя (100–500ms) | Низкая (200–1000ms) |
| Обнаружение | Легко | Сложно | Очень сложно |
| Цена | Низкая | Средняя | Высокая |
| Успешность на сложных мишенях | ~30% | ~85–95% | ~95–99% |
| Подходит для | Простые сайты, API | SERP, e-commerce, соцсети | Жёсткие антибот-системы |
Для большинства production-задач residential-прокси — оптимальный баланс между стоимостью и успешностью. Ознакомьтесь с тарифами ProxyHat и списком доступных локаций, чтобы подобрать подходящий пул IP-адресов.
Гео-таргетинг и сессии через username
ProxyHat передаёт параметры ротации через username в формате user-country-{CC}-city-{city}-session-{id}. Это значит, что вам не нужен отдельный API для получения списка IP — вы формируете URL прямо в коде:
user-country-DE-city-berlin— IP из Берлина, Германияuser-country-US-session-abc123— sticky-сессия: все запросы с этим ID пойдут через один IPuser-country-JP-session-tokyo-42— sticky-сессия из Японии
Sticky-сессии критически важны для сайтов, где требуется поддержание состояния (корзина, логин, пагинация). Без sticky-сессии каждый запрос пойдёт с нового IP, и сервер сбросит сессию.
Для SOCKS5-подключения используйте порт 1080:
// SOCKS5 proxy URL format
// socks5://user-country-US-session-abc123:YOUR_PASSWORD@gate.proxyhat.com:1080
Рабочий пример: residential-скрейпер с Limit и ретраями
Соберём всё вместе: коллектор с async-режимом, residential-ротацией, лимитами параллелизма и ретраями при 403/429:
package main
import (
"fmt"
"log"
"net/http"
"net/url"
"sync/atomic"
"time"
"github.com/gocolly/colly/v2"
)
var reqCounter uint64
func residentialProxy(r *http.Request) (*url.URL, error) {
countries := []string{"US", "DE", "GB", "FR", "JP", "CA", "AU"}
idx := atomic.AddUint64(&reqCounter, 1)
country := countries[idx%uint64(len(countries))]
session := fmt.Sprintf("sess-%d", idx)
return url.Parse(fmt.Sprintf(
"http://user-country-%s-session-%s:YOUR_PASSWORD@gate.proxyhat.com:8080",
country, session,
))
}
func main() {
c := colly.NewCollector(colly.Async(true))
c.SetProxyFunc(residentialProxy)
// Max 5 parallel requests, 200ms random delay
c.Limit(&colly.LimitRule{
DomainGlob: "*",
Parallelism: 5,
RandomDelay: 200 * time.Millisecond,
})
c.OnRequest(func(r *colly.Request) {
r.Headers.Set("User-Agent", "Mozilla/5.0 (compatible; MyBot/1.0)")
})
c.OnError(func(r *colly.Response, err error) {
log.Printf("Error %d on %s: %v", r.StatusCode, r.Request.URL, err)
if r.StatusCode == 403 || r.StatusCode == 429 {
// Retry with a new proxy (new session)
r.Request.Retry()
}
})
c.OnHTML("a[href]", func(e *colly.HTMLElement) {
link := e.Attr("href")
e.Request.Visit(link)
})
c.OnHTML("title", func(e *colly.HTMLElement) {
fmt.Println("Title:", e.Text)
})
c.Visit("https://example.com")
c.Wait()
}
Ключевые моменты этого примера:
- Async-режим — запросы отправляются параллельно,
c.Wait()блокирует до завершения всех. - Limit — не более 5 одновременных запросов и 200ms случайной задержки. Это снижает нагрузку и имитирует человеческое поведение.
- Retry — при 403 или 429 Colly автоматически повторяет запрос. Поскольку
residentialProxyгенерирует новый session-ID для каждого вызова, ретрай пойдёт через новый IP. - Atomic counter — потокобезопасный счётчик для генерации уникальных session-ID.
Production-паттерны для высоконагруженного скрейпинга
Ретраи через c.Clone()
Для более сложной логики ретраев можно клонировать коллектор. Клон наследует все настройки, но позволяет переопределить proxy-функцию и заголовки:
func retryWithNewProxy(c *colly.Collector, targetURL string, maxRetries int) {
for i := 0; i < maxRetries; i++ {
clone := c.Clone()
// Each clone gets a fresh session via the same proxy func
if err := clone.Visit(targetURL); err == nil {
return
}
time.Sleep(time.Duration(i+1) * time.Second)
}
}
Стратегия экспоненциальной задержки (exponential backoff) между ретраями снижает риск повторных блокировок. Задержка 1с, 2с, 3с — простой и эффективный паттерн.
RandomDelay и Parallelism: настройка под мишень
Выбор параметров зависит от мишени. Для простой страницы достаточно 10 параллельных потоков и 100ms задержки. Для защищённых сайтов (SERP, e-commerce) лучше 5 потоков и 500–1000ms задержки. ProxyHat поддерживает до 100 одновременных сессий на аккаунт, что позволяет масштабировать скрейпинг горизонтально.
Для SERP-трекинга рекомендуется использовать sticky-сессии с длительностью 10–30 минут, чтобы результаты были консистентны в рамках одного региона.
Кастомный Transport и TLS-конфигурация
Иногда нужно переопределить HTTP-транспорт — например, для настройки таймаутов или пула соединений:
c.WithTransport(&http.Transport{
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 10 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
MaxIdleConns: 100,
})
Настройка MaxIdleConns на 100 соединений улучшает переисользование TCP-коннектов при ротации прокси, снижая overhead на установку новых соединений.
Распределённый скрейпинг через Redis storage
Для горизонтального масштабирования Colly поддерживает pluggable storage backends из коробки. Redis-бэкенд обеспечивает дедупликацию URL между несколькими экземплярами коллектора:
package main
import (
"fmt"
"net/http"
"net/url"
"sync/atomic"
"github.com/gocolly/colly/v2"
"github.com/gocolly/colly/v2/storage"
)
var reqCounter uint64
func residentialProxy(r *http.Request) (*url.URL, error) {
idx := atomic.AddUint64(&reqCounter, 1)
return url.Parse(fmt.Sprintf(
"http://user-country-US-session-sess-%d:YOUR_PASSWORD@gate.proxyhat.com:8080",
idx,
))
}
func main() {
c := colly.NewCollector(colly.Async(true))
c.SetProxyFunc(residentialProxy)
redisStorage := &storage.RedisStorage{
Address: "redis://localhost:6379",
DB: 0,
Prefix: "scraper",
}
if err := c.SetStorage(redisStorage); err != nil {
panic(err)
}
defer redisStorage.Close()
c.Limit(&colly.LimitRule{
DomainGlob: "*",
Parallelism: 10,
})
c.OnHTML("title", func(e *colly.HTMLElement) {
fmt.Println("Title:", e.Text)
})
c.Visit("https://example.com")
c.Wait()
}
С Redis-бэкендом можно запустить несколько контейнеров с коллектором, и каждый будет видеть только непосещённые URL. Это ключевой паттерн для скрейпинга в 1500+ запросов в минуту: 5 контейнеров × 10 параллельных потоков × ~3 запроса/сек на поток.
Когда НЕ использовать Colly
Colly — отличный выбор для статических HTML-страниц и API, но он не выполняет JavaScript. Если мишень — SPA на React/Vue/Angular, где контент рендерится клиентом, Colly увидит только пустой <div id="root">. В таких случаях нужен браузерный скрейпинг: chromedp для Go или Playwright/Puppeteer для Node.js.
Гибридный подход: используйте Colly для обхода ссылок и сбора URL, а браузер — только для страниц, требующих JS-рендеринга. Это снижает стоимость и увеличивает скорость на 80–90% по сравнению с полным браузерным скрейпингом.
Настройка ProxyHat в Colly
Подключение ProxyHat к Colly занимает несколько минут. Получите credentials из дашборда и подставьте в URL-формат:
- HTTP:
http://user-country-{CC}-session-{ID}:PASSWORD@gate.proxyhat.com:8080 - SOCKS5:
socks5://user-country-{CC}-session-{ID}:PASSWORD@gate.proxyhat.com:1080
Документация по параметрам гео-таргетинга доступна на docs.proxyhat.com. Подробнее о применении прокси для веб-скрейпинга — в нашем разделе use-cases.
Key Takeaways
- Используйте SetProxyFunc вместо ProxySwitcher для динамической ротации по странам и сессиям.
- Residential-прокси обязательны для сайтов с антибот-защитой. Дата-центровые IP дают лишь ~30% успешности.
- Sticky-сессии через -session-ID — для сайтов с поддержанием состояния. Уникальный ID для каждого запроса — для максимальной ротации.
- Limit + RandomDelay — основные инструменты контроля нагрузки. 5 параллельных потоков и 200ms задержки — хороший старт.
- Redis storage — для распределённого скрейпинга с дедупликацией между контейнерами.
- Colly не выполняет JS — для SPA нужен браузерный скрейпинг через chromedp или Playwright.
- Соблюдайте этику: уважайте robots.txt (RFC 9309), ToS и законы о защите данных.
FAQ
Что такое ротация прокси в Colly?
Ротация прокси в Colly — это механизм переключения IP-адресов между HTTP-запросами для обхода блокировок по IP. Colly предоставляет два интерфейса: colly.ProxySwitcher() для простого round-robin из фиксированного списка и c.SetProxyFunc() для кастомной логики выбора прокси. Residential-прокси обеспечивают 85–95% успешности на сложных мишенях против ~30% у дата-центровых.
Почему ротация прокси в Colly важна для пользователей прокси?
Без ротации все запросы идут с одного IP, что быстро приводит к блокировке. Ротация распределяет запросы по пулу IP, имитируя органический трафик. В сочетании с residential-прокси и гео-таргетингом это позволяет скрейпить сайты с жёсткой антибот-защитой (Cloudflare, Datadome) и поддерживать throughput на уровне 1500+ запросов в минуту.
Какой тип прокси лучше всего подходит для ротации в Colly?
Residential-прокси — оптимальный выбор для большинства задач. Они используют IP реальных ISP, что делает их неотличимыми от обычных пользователей. Datacenter-прокси подходят только для простых сайтов без защиты. Mobile-прокси дают максимальную успешность (95–99%), но стоят дороже. ProxyHat предоставляет все три типа с гео-таргетингом по странам и городам.
Как избежать блокировок при реализации ротации прокси в Colly?
Используйте residential-прокси с ротацией стран и уникальными session-ID для каждого запроса. Настройте Limit с Parallelism 5 и RandomDelay 200–500ms. Добавьте ретраи при 403/429 через OnError с экспоненциальной задержкой. Установите реалистичный User-Agent и соблюдайте robots.txt. Для сложных мишеней используйте sticky-сессии длительностью 10–30 минут.






