Rotación de proxies en Colly: guía completa para scraping en Go

Aprende a integrar rotación de proxies residenciales en Colly con RoundRobinProxySwitcher, switchers personalizados y patrones de producción para scraping en Go.

Rotating Proxies in Colly: A Framework-Idiomatic Guide for Go Developers
En este artículo

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:

  1. robots.txt: respeta las directivas Disallow. El RFC 9309 define el protocolo formal.
  2. Términos de servicio: algunos sitios prohíben explícitamente el scraping automatizado.
  3. Datos personales: el scraping de datos personales está sujeto al GDPR en la UE y al CCPA en California.
  4. 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.RoundRobinProxySwitcher para pools estáticos y SetProxyFunc con función personalizada para rotación dinámica.
  • Los proxies residenciales reducen bloqueos en sitios protegidos; combina -country-XX y -session-xxx en el username para control granular.
  • Protege generadores de números aleatorios con sync.Mutex en el switcher personalizado —Colly invoca la función desde múltiples goroutines.
  • Configura Limit() con Parallelism y RandomDelay para 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.

Preguntas frecuentes

¿Qué es la rotación de proxies en Colly?

La rotación de proxies en Colly es la técnica de asignar una dirección IP de proxy diferente a cada petición HTTP que realiza el Collector. Colly lo soporta mediante proxy.RoundRobinProxySwitcher para pools estáticos o mediante c.SetProxyFunc con una función personalizada func(*http.Request) (*url.URL, error) para rotación dinámica basada en criterios como país, ciudad o sesión.

¿Por qué importa la rotación de proxies en Colly para usuarios de proxies?

Sin rotación de proxies, todas las peticiones de tu scraper salen desde la misma IP, lo que permite a los sitios objetivo detectar y bloquear tu tráfico rápidamente mediante rate limiting o bans por IP. La rotación distribuye las peticiones entre múltiples IPs residenciales, reduciendo la probabilidad de bloqueo y permitiendo mantener throughput sostenido en sitios con protección anti-bot.

¿Qué tipo de proxy funciona mejor para la rotación de proxies en Colly?

Los proxies residenciales funcionan mejor para la mayoría de casos de uso en Colly, especialmente en sitios protegidos con Cloudflare o Datadome. Las IPs residenciales provienen de ISPs legítimos, lo que reduce la detección. Los proxies datacenter son más rápidos y económicos pero se detectan fácilmente. Los proxies móviles ofrecen la menor detección pero tienen mayor latencia y coste.

¿Cómo evitas bloqueos al implementar rotación de proxies en Colly?

Para evitar bloqueos: usa proxies residenciales con rotación por país y sesión, configura c.Limit() con Parallelism y RandomDelay para simular tráfico humano, añade headers realistas con extensions.RandomUserAgent, implementa reintentos con c.Clone() para códigos 429 y 503, y usa sync.Mutex para proteger el generador de proxies en entornos concurrentes. También respeta robots.txt y mantén delays razonables.

¿Puedo usar SOCKS5 con Colly y ProxyHat?

Sí, Colly soporta SOCKS5 nativamente. Solo necesitas cambiar el esquema de la URL del proxy a socks5:// y usar el puerto 1080 en lugar de 8080. Por ejemplo: socks5://user-country-DE-session-abc123:PASSWORD@gate.proxyhat.com:1080. SOCKS5 opera a nivel TCP sin interpretar HTTP, lo que puede ser útil cuando el tráfico HTTP CONNECT es bloqueado por firewalls.

¿Listo para empezar?

Proxies residenciales, ISP y móviles en más de 148 países. Crea una cuenta gratis.

Crear cuenta gratis
← Volver al Blog