La rotación de proxies en Colly es la técnica fundamental para mantener un scraper en Go operativo cuando los sitios objetivo implementan rate limiting o bloqueos por IP. Colly, el framework de scraping más popular del ecosistema Go, ofrece puntos de extensión idiomáticos para integrar proxies residenciales sin acoplar tu lógica de negocio al transporte HTTP.
Aviso legal: Este artículo cubre técnicas de scraping de datos públicos. En EE.UU., la Computer Fraud and Abuse Act (CFAA) prohíbe el acceso no autorizado a sistemas protegidos. En la UE, el GDPR regula el tratamiento de datos personales. Respeta el
robots.txt, los términos de servicio y las leyes aplicables antes de scrape cualquier sitio.
Rotación de proxies en Colly: el modelo Collector
Colly se construye alrededor del tipo colly.Collector, que orquesta el ciclo de vida completo de una petición HTTP: preparación, envío, parseo de respuesta y manejo de errores. El Collector expone hooks mediante callbacks idiomáticos que se encadenan en orden:
OnRequest(func(r *colly.Request))— se ejecuta antes de cada petición; ideal para inyectar headers, modificar la URL o loggear.OnHTML(selector string, func(e *colly.HTMLElement))— parsea el DOM con goquery; permite extraer texto, atributos y recorrer elementos.OnError(func(r *colly.Response, err error))— captura errores de red, timeouts y respuestas con códigos no esperados.OnResponse(func(r *colly.Response))— se invoca con la respuesta cruda, antes del parseo HTML.OnScraped(func(r *colly.Response))— se ejecuta después de procesar todos los callbacks OnHTML.
El Collector opera de forma asíncrona por defecto: cada llamada a c.Visit(url) encola una petición que se procesa concurrentemente según las reglas definidas por c.Limit(). Esto significa que la rotación de proxies debe ser segura para concurrencia —un detalle que veremos más adelante.
El DOM traversal se delega a goquery, que expone una API similar a jQuery: e.Find("div.product"), e.Attr("href"), e.Text. Esto hace que la extracción sea declarativa y separe la lógica de parseo del transporte HTTP.
Ejemplo: Collector básico con callbacks
package main
import (
"fmt"
"log"
"github.com/gocolly/colly/v2"
)
func main() {
c := colly.NewCollector(
colly.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64)"),
)
c.OnRequest(func(r *colly.Request) {
fmt.Println("Visitando:", r.URL.String())
})
c.OnHTML("h1", func(e *colly.HTMLElement) {
fmt.Println("Titulo:", e.Text)
})
c.OnError(func(r *colly.Response, err error) {
log.Printf("Error en %s: %v", r.Request.URL, err)
})
c.Visit("https://example.com")
}
Este patrón es la base. La rotación de proxies no cambia la estructura de tus callbacks —se inserta como una capa de transporte transparente mediante c.SetProxyFunc().
La superficie idiomática de proxies en Colly
Colly ofrece dos mecanismos para rotar proxies, ambos idiomáticos y basados en el paquete proxy:
1. proxy.RoundRobinProxySwitcher
El switcher round-robin distribuye las peticiones secuencialmente entre una lista de URLs de proxy. Es la opción más simple cuando tienes un pool estático de endpoints:
import (
"github.com/gocolly/colly/v2"
"github.com/gocolly/colly/v2/proxy"
)
func main() {
c := colly.NewCollector()
proxyURLs := []string{
"http://user-country-US:PASSWORD@gate.proxyhat.com:8080",
"http://user-country-DE:PASSWORD@gate.proxyhat.com:8080",
"http://user-country-GB:PASSWORD@gate.proxyhat.com:8080",
"http://user-country-FR:PASSWORD@gate.proxyhat.com:8080",
}
rp, err := proxy.RoundRobinProxySwitcher(proxyURLs...)
if err != nil {
log.Fatal(err)
}
c.SetProxyFunc(rp)
c.OnHTML("body", func(e *colly.HTMLElement) {
fmt.Println(e.Text)
})
c.Visit("https://httpbin.org/ip")
}
El colly proxy switcher round-robin es thread-safe: usa un sync.Mutex interno para alternar el índice del proxy. Sin embargo, no soporta lógica condicional (por ejemplo, asignar un proxy específico según el dominio de destino).
2. Custom func(*http.Request) (*url.URL, error)
Para control total, Colly acepta cualquier función con la firma func(*http.Request) (*url.URL, error) vía c.SetProxyFunc(). Esto permite rotación dinámica basada en el dominio destino, estado de la sesión, geo-targeting o cualquier criterio:
import (
"fmt"
"math/rand"
"net/http"
"net/url"
"time"
"github.com/gocolly/colly/v2"
)
func main() {
c := colly.NewCollector()
countries := []string{"US", "DE", "GB", "FR", "ES", "IT", "NL"}
cities := map[string]string{
"US": "new-york", "DE": "berlin", "GB": "london",
"FR": "paris", "ES": "madrid", "IT": "rome", "NL": "amsterdam",
}
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
c.SetProxyFunc(func(req *http.Request) (*url.URL, error) {
country := countries[rnd.Intn(len(countries))]
city := cities[country]
session := fmt.Sprintf("sess-%d", rnd.Intn(100000))
proxyStr := fmt.Sprintf(
"http://user-country-%s-city-%s-session-%s:PASSWORD@gate.proxyhat.com:8080",
country, city, session,
)
return url.Parse(proxyStr)
})
c.OnHTML("body", func(e *colly.HTMLElement) {
fmt.Println("IP visible:", e.Text)
})
for i := 0; i < 5; i++ {
c.Visit("https://httpbin.org/ip")
}
}
Este patrón es el recomendado para golang colly proxy en producción: cada petición obtiene una IP residencial diferente con geo-targeting a nivel de ciudad y una sesión única que evita correlación entre peticiones.
Por qué proxies residenciales para Colly web scraping
Los sitios modernos detectan y bloquean IPs de datacenter con alta precisión. Servicios como Cloudflare, Datadome y PerimeterX mantienen bases de datos de rangos ASN de hosting providers. Un proxy residencial, en cambio, usa IPs asignadas por ISPs legítimos a usuarios reales, lo que reduce drásticamente la probabilidad de bloqueo.
| Característica | Residencial | Datacenter | Móvil |
|---|---|---|---|
| Detección por anti-bot | Baja | Alta | Muy baja |
| Latencia típica | 200-800ms | 50-100ms | 500-2000ms |
| Coste por GB | Medio | Bajo | Alto |
| Ideal para | SERP, e-commerce, social | APIs públicas, datos abiertos | Apps móviles, login social |
Para colly web scraping en sitios protegidos, los proxies residenciales son la opción por defecto. La rotación se controla desde el nombre de usuario en ProxyHat:
user-country-DE:pass— IP aleatoria en Alemania.user-country-DE-city-berlin:pass— IP en Berlín específicamente.user-session-abc123:pass— sesión sticky: la misma IP se mantiene entre peticiones con el mismo identificador de sesión.
La combinación de rotación por país y sesiones sticky es clave: usa sesiones cuando necesites mantener estado (login, carrito de compra) y rotación pura cuando solo necesitas evadir rate limits. Consulta las documentaciones de ProxyHat para ver todos los parámetros disponibles.
Ejemplo completo: scraper con rotación residencial y Limit()
El siguiente ejemplo integra todos los componentes: un Collector con rotación de proxies residenciales por país, reglas de rate limiting, y manejo de errores con reintentos:
package main
import (
"crypto/tls"
"fmt"
"log"
"math/rand"
"net/http"
"net/url"
"sync"
"time"
"github.com/gocolly/colly/v2"
"github.com/gocolly/colly/v2/extensions"
)
func main() {
c := colly.NewCollector(
colly.MaxDepth(2),
colly.AllowURLRevisit(false),
)
// Transport TLS personalizado para evadir fingerprinting
c.WithTransport(&http.Transport{
TLSClientConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
},
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
})
// Rate limiting: 20 conexiones concurrentes, 2s de delay
c.Limit(&colly.LimitRule{
DomainGlob: "*",
Parallelism: 20,
Delay: 2 * time.Second,
RandomDelay: 1 * time.Second,
})
// Rotacion de proxies residenciales con geo-targeting
countries := []string{"US", "DE", "GB", "FR", "ES", "IT", "NL", "CA", "AU", "JP"}
var mu sync.Mutex
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
c.SetProxyFunc(func(req *http.Request) (*url.URL, error) {
mu.Lock()
country := countries[rnd.Intn(len(countries))]
session := fmt.Sprintf("sess-%d", rnd.Intn(1000000))
mu.Unlock()
proxyStr := fmt.Sprintf(
"http://user-country-%s-session-%s:PASSWORD@gate.proxyhat.com:8080",
country, session,
)
return url.Parse(proxyStr)
})
// Headers realistas
extensions.RandomUserAgent(c)
c.OnRequest(func(r *colly.Request) {
r.Headers.Set("Accept-Language", "en-US,en;q=0.9")
r.Headers.Set("Accept", "text/html,application/xhtml+xml")
})
// Reintentos con Clone
c.OnError(func(r *colly.Response, err error) {
if r.StatusCode >= 500 || r.StatusCode == 429 {
log.Printf("Reintentando %s (status %d)", r.Request.URL, r.StatusCode)
r.Request.Retry()
}
})
c.OnHTML("a[href]", func(e *colly.HTMLElement) {
link := e.Attr("href")
fmt.Println("Encontrado:", link)
e.Request.Visit(link)
})
c.Visit("https://example.com")
c.Wait()
}
Notar el uso de sync.Mutex para proteger el generador de números aleatorios —necesario porque Colly invoca SetProxyFunc desde múltiples goroutines. Omitir esto causa data races que son difíciles de debuggear.
Patrones de producción para scraping distribuido
Reintentos con c.Clone()
Para reintentos más sofisticados, clona el Collector con c.Clone(). El clon hereda la configuración del padre pero puede tener su propio proxy, timeout y reglas. Esto permite estrategias como: primer intento con proxy residencial, reintento con proxy de otro país:
c.OnError(func(r *colly.Response, err error) {
if r.StatusCode == 403 {
clone := c.Clone()
clone.SetProxyFunc(func(req *http.Request) (*url.URL, error) {
return url.Parse("http://user-country-US-session-retry:PASSWORD@gate.proxyhat.com:8080")
})
clone.Visit(r.Request.URL.String())
}
})
RandomDelay y Parallelism
El RandomDelay añade jitter al Delay base, lo que hace que tu tráfico sea menos predecible. Una configuración de Delay: 2s + RandomDelay: 1s produce esperas uniformemente distribuidas entre 2 y 3 segundos. Combinado con Parallelism: 20, obtienes 20 conexiones concurrentes con delays aleatorios —suficiente para la mayoría de sitios sin disparar rate limits.
Storage backends: Redis para scraping distribuido
Para fleets de scraping distribuido en múltiples contenedores o máquinas, Colly soporta backends de almacenamiento que comparten estado de visitación. El backend Redis evita que múltiples workers visiten la misma URL:
import "github.com/gocolly/colly/v2/storage"
s := &storage.RedisStorage{
Address: "redis-cluster.internal:6379",
DB: 0,
Prefix: "colly_scraper_",
}
if err := s.Init(); err != nil {
log.Fatal(err)
}
c.SetStorage(s)
Esto es esencial cuando escalas a 100+ workers: sin almacenamiento compartido, cada worker mantiene su propio set de URLs visitadas y duplica trabajo. Con Redis, el dedup es global y atómico.
SOCKS5 como alternativa
Cuando necesitas evadir inspección profunda de paquetes (DPI) o el tráfico HTTP CONNECT es bloqueado, SOCKS5 es una alternativa. Colly lo soporta nativamente cambiando el esquema y puerto:
proxyStr := "socks5://user-country-DE-session-abc123:PASSWORD@gate.proxyhat.com:1080"
SOCKS5 opera a nivel TCP sin interpretar HTTP, lo que lo hace más difícil de detectar en algunos firewalls corporativos.
Cuándo NO usar Colly y consideraciones éticas
Colly es excelente para sitios con HTML renderizado en servidor. Pero si el sitio es una SPA (Single Page Application) que renderiza todo con JavaScript —React, Vue, Angular— necesitas un navegador headless. Colly no ejecuta JavaScript. En ese caso, considera chromedp o Playwright para Go, y usa proxies a nivel del navegador.
Antes de scrape cualquier sitio, verifica:
- robots.txt: respeta las directivas
Disallow. El RFC 9309 define el protocolo formal. - Términos de servicio: algunos sitios prohíben explícitamente el scraping automatizado.
- Datos personales: el scraping de datos personales está sujeto al GDPR en la UE y al CCPA en California.
- Rate limits: no satures el servidor. Usa
Limit()con delays razonables.
El scraping de datos públicos es legal en muchas jurisdicciones, pero la línea entre "público" y "protegido" varía. Consulta con un abogado si tienes dudas sobre un caso específico.
Puntos clave (Key Takeaways)
Resumen de rotación de proxies en Colly:
- Usa
proxy.RoundRobinProxySwitcherpara pools estáticos ySetProxyFunccon función personalizada para rotación dinámica.- Los proxies residenciales reducen bloqueos en sitios protegidos; combina
-country-XXy-session-xxxen el username para control granular.- Protege generadores de números aleatorios con
sync.Mutexen el switcher personalizado —Colly invoca la función desde múltiples goroutines.- Configura
Limit()conParallelismyRandomDelaypara evitar rate limits; usa Redis storage para scraping distribuido.- Colly no ejecuta JavaScript; para SPAs usa chromedp o Playwright con proxies a nivel de navegador.
- Respeta robots.txt, ToS y leyes de protección de datos antes de scrape.
Para empezar con proxies residenciales en Colly, consulta nuestra página de precios, explora los casos de uso de web scraping y SERP tracking, o revisa las ubicaciones disponibles en ProxyHat.






