La rotation de proxies dans Colly est la compétence technique la plus critique pour tout développeur Go qui construit des scrapers à grande échelle. Colly — le framework de scraping le plus adopté en Go, avec plus de 24 000 étoiles sur GitHub — offre des points d'extension natifs pour intégrer des proxys rotatifs sans bidouille. Ce guide couvre l'architecture du Collector, les middlewares de proxy idiomatiques, les stratégies de rotation résidentielle avec géo-ciblage, et les patterns de production pour des scrapers fiables dépassant 100 requêtes par seconde.
Avant de plonger dans le code, une note importante : le scraping de données publiques doit respecter le robots.txt, les conditions d'utilisation des sites cibles, et les réglementations en vigueur comme le RGPD en Europe ou le CFAA aux États-Unis. Ce guide présente des techniques d'ingénierie, pas des conseils juridiques.
Pourquoi la rotation de proxies dans Colly est indispensable
Tout scraper Go qui envoie plus de 50 requêtes par minute vers un même domaine finit par rencontrer des blocages. Les systèmes anti-bot modernes — Cloudflare, Datadome, PerimeterX — analysent le comportement au niveau de l'IP : taux de requêtes, entêtes, patterns de navigation. Un seul IP qui envoie 200 requêtes en 60 secondes vers un site e-commerce déclenche quasi-systématiquement un CAPTCHA ou un blocage HTTP 403.
La colly proxy rotation résout ce problème en distribuant les requêtes across un pool d'adresses IP. Au lieu que toutes les requêtes proviennent d'une seule IP de datacenter, elles arrivent depuis des dizaines ou centaines d'IPs résidentielles distinctes, chacune avec un comportement qui ressemble à celui d'un utilisateur réel.
Le défi technique n'est pas juste d'avoir des proxys — c'est de les intégrer proprement dans le flux de requêtes de Colly, avec rotation par requête, gestion de sessions persistantes, retry sur échec, et respect des limites de taux. C'est exactement ce que ce guide couvre.
Le modèle Collector de Colly : callbacks, async et goquery
Colly repose sur un modèle d'événements centré sur le Collector. Un Collector est l'objet principal qui orchestre les requêtes HTTP, le parsing HTML, et la gestion de file d'attente. Comprendre son architecture est essentiel pour intégrer la rotation de proxys de manière idiomatique.
Callbacks principaux
Colly expose plusieurs hooks que vous surchargez pour intercepter le cycle de vie d'une requête :
OnRequest(func(*colly.Request))— déclenché avant l'envoi. Idéal pour injecter des entêtes dynamiques, des User-Agents, ou des cookies.OnHTML(selector string, func(*colly.HTMLElement))— déclenché quand le sélecteur CSS correspond. Utilise goquery sous le capot pour traverser le DOM.OnError(func(*colly.Response, error))— déclenché sur erreur HTTP ou de parsing. Point d'ancrage pour la logique de retry.OnResponse(func(*colly.Response))— déclenché après réception de la réponse complète, avant le parsing HTML.OnScraped(func(*colly.Response))— déclenché après tous les callbacks OnHTML. Utile pour le post-traitement.
Mode asynchrone et parallélisme
Colly supporte un mode asynchrone via c.Async = true. En mode async, les requêtes sont envoyées en parallèle dans des goroutines internes. Vous devez appeler c.Wait() pour bloquer jusqu'à ce que toutes les requêtes en file d'attente soient terminées. Le parallélisme est contrôlé par c.WithTransport() et les règles Limit().
goquery et le parcours DOM
Sous le capot, Colly utilise goquery, un portage de l'API jQuery en Go. Le *colly.HTMLElement expose e.DOM qui est un *goquery.Selection. Vous pouvez chaîner Find(), Each(), Text(), Attr() exactement comme en JavaScript. Cette intégration est transparente — pas besoin d'importer goquery directement pour la plupart des cas d'usage.
La surface de proxy idiomatique dans Colly
Colly expose deux mécanismes pour injecter des proxys : le proxy.RoundRobinProxySwitcher intégré et la fonction SetProxyFunc personnalisée. Les deux sont conçus pour s'intégrer dans le pipeline de requêtes sans modifier la logique de scraping.
proxy.RoundRobinProxySwitcher
Le switcher round-robin est la solution la plus simple. Vous lui passez une liste d'URLs de proxy, et il les fait tourner séquentiellement à chaque requête :
package main
import (
"log"
"github.com/gocolly/colly/v2"
"github.com/gocolly/colly/v2/proxy"
)
func main() {
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",
"http://user-country-FR:pass@gate.proxyhat.com:8080",
}
rp, err := proxy.RoundRobinProxySwitcher(proxies...)
if err != nil {
log.Fatal(err)
}
c := colly.NewCollector()
c.SetProxyFunc(rp)
c.OnHTML("title", func(e *colly.HTMLElement) {
log.Printf("Titre: %s", e.Text)
})
c.Visit("https://example.com")
c.Wait()
}
Cette approche fonctionne pour les cas simples, mais elle a une limitation : le round-robin ne tient pas compte du géo-ciblage dynamique ni des sessions persistantes. Pour un contrôle fin, il faut un SetProxyFunc personnalisé.
SetProxyFunc avec une fonction personnalisée
La signature de SetProxyFunc accepte une func(*http.Request) (*url.URL, error). Vous avez accès à la requête complète — URL, entêtes, contexte — ce qui permet une rotation intelligente basée sur le domaine cible, le pays souhaité, ou un identifiant de session :
func customProxySwitcher(countries []string, password string) colly.ProxyFunc {
return func(req *http.Request) (*url.URL, error) {
// Sélection du pays en fonction du domaine cible
country := pickCountryForDomain(req.URL.Hostname(), countries)
// Construction dynamique de l'URL du proxy avec géo-ciblage
proxyURL := fmt.Sprintf(
"http://user-country-%s-session-%s:%s@gate.proxyhat.com:8080",
country,
generateSessionID(),
password,
)
return url.Parse(proxyURL)
}
}
// Utilisation
c := colly.NewCollector()
c.SetProxyFunc(customProxySwitcher([]string{"DE", "FR", "GB"}, "yourPassword"))
Cette approche est idiomatique : elle s'intègre dans le pipeline de Colly comme un middleware, sans modifier la logique de scraping. Chaque requête peut avoir un pays et une session différents, ce qui est crucial pour les cibles difficiles comme Google SERP ou les sites e-commerce protégés.
Support HTTP et SOCKS5
Colly supporte nativement les schémas http://, https:// et socks5:// dans les URLs de proxy. Pour SOCKS5 avec ProxyHat :
proxyURL := "socks5://user-country-DE-session-abc123:pass@gate.proxyhat.com:1080"
SOCKS5 est utile quand vous avez besoin d'un tunnel TCP de bas niveau, par exemple pour des protocoles non-HTTP ou pour contourner des pare-feu restrictifs. Pour la majorité des cas de scraping web, HTTP sur le port 8080 est suffisant et légèrement plus rapide.
Pourquoi les proxys résidentiels sont nécessaires pour les cibles difficiles
Tous les proxys ne sont pas égaux face aux systèmes anti-bot. Voici une comparaison des trois types principaux :
| Critère | Résidentiel | Datacenter | Mobile |
|---|---|---|---|
| Adresse IP | FAI réel (AT&T, Orange, Vodafone) | Plage d'IPs hébergeur (AWS, OVH) | Réseaux mobiles (4G/5G) |
| Détection anti-bot | Faible — ressemble à un utilisateur réel | Élevée — flagged instantanément par Cloudflare | Très faible — confiance maximale |
| Vitesse moyenne | ~200ms à 800ms | ~50ms à 150ms | ~300ms à 1200ms |
| Coût relatif | Moyen | Bas | Élevé |
| Idéal pour | SERP, e-commerce, social | Scraping simple, APIs publiques | Cibles ultra-protégées |
Les proxys datacenter sont détectés par la plupart des services anti-bot modernes car leurs plages d'IP sont publiquement connues et cataloguées. Cloudflare, par exemple, maintient une base de données d'IPs datacenter et les challenge systématiquement. Les proxys résidentiels, en revanche, proviennent d'ISP réels — leur ASN correspond à un fournisseur d'accès grand public, pas à un hébergeur. C'est pourquoi la colly proxy rotation avec des proxys résidentiels est la configuration recommandée pour les cibles difficiles.
Géo-ciblage et sessions avec ProxyHat
ProxyHat permet d'encoder le pays, la ville et l'ID de session directement dans le nom d'utilisateur du proxy. C'est particulièrement puissant combiné avec un SetProxyFunc personnalisé dans Colly :
user-country-DE:pass@gate.proxyhat.com:8080— IP résidentielle en Allemagneuser-country-DE-city-berlin:pass@gate.proxyhat.com:8080— IP à Berlin spécifiquementuser-country-DE-session-abc123:pass@gate.proxyhat.com:8080— session persistante : la même IP est conservée tant que l'ID de session ne change pas
Les sessions persistantes sont cruciales pour les sites qui nécessitent une authentification progressive ou un panier d'achat. Si vous scrapez un site e-commerce qui suit votre session via des cookies IP-bound, changer d'IP à chaque requête casse la session. Avec -session-abc123, ProxyHat vous attribue une IP fixe pour la durée de la session (jusqu'à 30 minutes d'inactivité), puis vous pouvez changer d'ID pour forcer une nouvelle IP.
Exemple Go complet : Collector avec rotation résidentielle
Voici un exemple complet et runnable qui combine un RoundRobinProxySwitcher résidentiel, des règles Limit(), et un switcher personnalisé lisant la liste d'endpoints ProxyHat :
package main
import (
"crypto/rand"
"encoding/hex"
"fmt"
"log"
"net/http"
"net/url"
"time"
"github.com/gocolly/colly/v2"
"github.com/gocolly/colly/v2/proxy"
)
func main() {
// Pool de proxys résidentiels avec géo-ciblage
proxies := []string{
"http://user-country-DE-city-berlin:pass@gate.proxyhat.com:8080",
"http://user-country-FR-city-paris:pass@gate.proxyhat.com:8080",
"http://user-country-GB-city-london:pass@gate.proxyhat.com:8080",
"http://user-country-NL-city-amsterdam:pass@gate.proxyhat.com:8080",
"http://user-country-ES-city-madrid:pass@gate.proxyhat.com:8080",
}
rp, err := proxy.RoundRobinProxySwitcher(proxies...)
if err != nil {
log.Fatalf("Erreur switcher: %v", err)
}
c := colly.NewCollector(
colly.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"),
colly.MaxDepth(2),
colly.Async(true),
)
// Application du switcher de proxy
c.SetProxyFunc(rp)
// Limites de taux : 2 requêtes simultanées par domaine, délai aléatoire 1-3s
c.Limit(&colly.LimitRule{
DomainGlob: "*",
Parallelism: 2,
Delay: 2 * time.Second,
RandomDelay: 1 * time.Second,
})
// Callback avant envoi : injection d'entêtes
c.OnRequest(func(r *colly.Request) {
r.Headers.Set("Accept-Language", "en-US,en;q=0.9,de;q=0.8")
r.Headers.Set("Accept", "text/html,application/xhtml+xml")
log.Printf("Visite: %s via proxy", r.URL)
})
// Callback HTML : extraction des liens
c.OnHTML("a[href]", func(e *colly.HTMLElement) {
link := e.Attr("href")
absoluteURL := e.Request.AbsoluteURL(link)
if e.Request.Depth < 2 {
e.Request.Visit(absoluteURL)
}
})
// Callback d'erreur : retry avec proxy différent
c.OnError(func(r *colly.Response, err error) {
log.Printf("Erreur sur %s: %v (status %d)", r.Request.URL, err, r.StatusCode)
if r.StatusCode == 403 || r.StatusCode == 429 {
// Retry après délai avec un nouveau proxy (nouvelle session)
time.Sleep(5 * time.Second)
r.Request.Retry()
}
})
c.OnScraped(func(r *colly.Response) {
log.Printf("Scraping terminé pour %s", r.Request.URL)
})
// Démarrage
c.Visit("https://example.com")
c.Wait()
}
Cet exemple illustre plusieurs patterns clés : le mode Async(true) pour le parallélisme, les règles Limit() pour contrôler le taux, le callback OnError pour les retries, et le switcher round-robin pour la rotation.
Switcher personnalisé avec sessions dynamiques
Pour un contrôle plus fin — notamment la rotation de sessions par requête avec géo-ciblage dynamique — voici un switcher personnalisé :
func residentialProxySwitcher(password string, countries []string) colly.ProxyFunc {
return func(req *http.Request) (*url.URL, error) {
// Pays aléatoire dans la liste
idx := rand.Intn(len(countries))
country := countries[idx]
// ID de session unique par requête pour rotation d'IP
sessionBytes := make([]byte, 8)
rand.Read(sessionBytes)
sessionID := hex.EncodeToString(sessionBytes)
proxyStr := fmt.Sprintf(
"http://user-country-%s-session-%s:%s@gate.proxyhat.com:8080",
country, sessionID, password,
)
return url.Parse(proxyStr)
}
}
// Utilisation
c := colly.NewCollector(colly.Async(true))
c.SetProxyFunc(residentialProxySwitcher("yourPassword", []string{"DE", "FR", "GB", "NL"}))
Avec ce switcher, chaque requête obtient une IP résidentielle différente dans un pays différent, avec un ID de session unique. C'est la configuration idéale pour le suivi de SERP à grande échelle ou le scraping web multi-zone.
Patterns de production pour Colly avec proxys rotatifs
Retries avec c.Clone()
Colly ne propose pas de retry automatique natif. Le pattern idiomatique consiste à cloner le collector dans OnError pour relancer la requête avec une configuration différente (nouveau proxy, nouveau User-Agent) :
var retryCount = make(map[string]int)
const maxRetries = 3
c.OnError(func(r *colly.Response, err error) {
urlStr := r.Request.URL.String()
retryCount[urlStr]++
if retryCount[urlStr] <= maxRetries {
log.Printf("Retry %d/%d pour %s", retryCount[urlStr], maxRetries, urlStr)
// Clone avec un nouveau proxy
cClone := r.Request.C.Clone()
cClone.SetProxyFunc(residentialProxySwitcher("pass", []string{"DE", "FR"}))
time.Sleep(time.Duration(retryCount[urlStr]*2) * time.Second)
cClone.Visit(urlStr)
}
})
Le Clone() duplique la configuration du collector (callbacks, limites, transport) tout en permettant de modifier la fonction de proxy. C'est plus propre que de modifier le collector original à chaud.
RandomDelay et limites de parallélisme
Les règles Limit() sont votre premier outil contre les blocages. La combinaison de Delay et RandomDelay ajoute de la variance humaine :
c.Limit(&colly.LimitRule{
DomainGlob: "*.example.com",
Parallelism: 5,
Delay: 3 * time.Second,
RandomDelay: 2 * time.Second,
})
Avec cette configuration, Colly attend 3 à 5 secondes entre chaque requête vers le même domaine, avec au maximum 5 requêtes simultanées. Le RandomDelay est crucial : un délai fixe est détectable par les systèmes anti-bot, alors qu'un délai aléatoire ressemble à un comportement humain.
Transport TLS personnalisé
Pour éviter le fingerprinting TLS (JA3), vous pouvez personnaliser le http.Transport sous-jacent :
transport := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: false,
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_AES_128_GCM_SHA256,
tls.TLS_AES_256_GCM_SHA384,
tls.TLS_CHACHA20_POLY1305_SHA256,
},
},
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 30 * time.Second,
}
c := colly.NewCollector()
c.WithTransport(transport)
Le fingerprinting JA3 est utilisé par Cloudflare et d'autres WAF pour identifier les clients non-navigateurs. En personnalisant les cipher suites et les versions TLS, vous réduisez le risque de détection, surtout combiné avec des proxys résidentiels.
Scraping distribué avec stockage Redis
Colly supporte plusieurs backends de stockage pour la file d'attente des URLs visitées. Pour le scraping distribué sur plusieurs instances, le backend Redis est essentiel :
import "github.com/gocolly/colly/v2/storage"
// Stockage Redis pour coordination distribuée
redisStorage := &storage.RedisStorage{
Address: "redis://localhost:6379",
Prefix: "colly_scraper:",
}
if err := redisStorage.Init(); err != nil {
log.Fatal(err)
}
c := colly.NewCollector()
c.SetStorage(redisStorage)
Avec Redis, plusieurs instances de votre scraper Go peuvent partager la file d'attente et le tracking des URLs visitées. Chaque instance prend une URL de la file Redis, la scrape, et ajoute les nouveaux liens trouvés. C'est le pattern recommandé pour dépasser 1 000 requêtes par seconde avec Colly.
Consultez la documentation officielle de Colly pour les détails complets du backend de stockage, et la documentation ProxyHat pour les paramètres de géo-ciblage avancés.
Quand NE PAS utiliser Colly
Colly est excellent pour le scraping de pages HTML statiques et rendues côté serveur. Mais il ne gère pas le JavaScript. Si votre cible est une Single Page Application (React, Vue, Angular) qui charge son contenu via des appels API XHR/fetch après le rendu initial, Colly ne verra qu'un shell HTML vide.
Dans ce cas, vous avez besoin d'un navigateur headless. Les options en Go sont limitées — chromedp (basé sur Chrome DevTools Protocol) est le plus mature. Vous pouvez combiner Colly pour la découverte d'URLs et chromedp pour le rendu des pages JS-heavy, mais c'est une architecture plus complexe.
Une alternative hybride : utilisez Colly pour intercepter les appels API sous-jacents. Beaucoup de SPAs exposent des endpoints JSON que vous pouvez appeler directement avec Colly, sans avoir besoin de rendre le JavaScript. Inspectez le trafic réseau avec les DevTools du navigateur pour identifier ces endpoints.
Considérations éthiques et légales
Le scraping doit être pratiqué de manière responsable :
- Respectez
robots.txt— Colly peut le faire automatiquement aveccolly.IgnoreRobotsTxt(false)(par défaut, Colly respecte robots.txt). - Limitez votre taux — même avec des proxys rotatifs, un taux excessif peut surcharger l'infrastructure cible. Restez sous 5 requêtes par seconde par domaine.
- Scrapez uniquement des données publiques — les données derrière un login ou paywall sont soumises à des conditions d'utilisation spécifiques.
- RGPD et CCPA — le scraping de données personnelles (noms, emails, téléphones) est soumis aux réglementations sur la protection des données. Consultez le texte du RGPD pour les obligations.
- CFAA — aux États-Unis, le Computer Fraud and Abuse Act peut s'appliquer au scraping non autorisé. La jurisprudence (Van Buren v. United States, 2021) a clarifié certains aspects, mais la prudence reste de mise.
Tableau récapitulatif : stratégies de rotation dans Colly
| Stratégie | Quand l'utiliser | Avantages | Inconvénients |
|---|---|---|---|
| RoundRobinProxySwitcher | Pool fixe, cibles simples | Simple, natif Colly | Pas de géo-ciblage dynamique |
| SetProxyFunc personnalisé | Géo-ciblage, sessions | Contrôle total par requête | Plus de code à maintenir |
| Sessions persistantes | Sites avec auth/panier | IP stable pendant la session | Moins de rotation d'IP |
| Rotation par requête | SERP, e-commerce massif | Max de diversité d'IP | Risque de patterns détectables |
Points clés à retenir
La rotation de proxies dans Colly n'est pas un hack — c'est un middleware idiomatique. Utilisez
SetProxyFuncavec une fonction personnalisée pour un contrôle total, ouRoundRobinProxySwitcherpour les cas simples.
- Préférez les proxys résidentiels pour les cibles difficiles. Les IPs datacenter sont flaggées par Cloudflare et similires en moins de 5 requêtes.
- Géo-ciblez avec ProxyHat via le nom d'utilisateur :
user-country-DE-city-berlin-session-abc123:pass@gate.proxyhat.com:8080. - Combinez Limit() + RandomDelay pour simuler un comportement humain. Un délai aléatoire de 1 à 3 secondes par domaine réduit drastiquement les blocages.
- Utilisez c.Clone() pour les retries — ne modifiez jamais un collector en cours d'exécution.
- Passez au stockage Redis pour le scraping distribué au-delà de 1 000 requêtes par seconde.
- Colly ne gère pas le JS — pour les SPAs, utilisez chromedp ou interceptez les API calls directement.
- Respectez robots.txt et la loi — RGPD, CFAA, et conditions d'utilisation s'appliquent au scraping automatisé.
Prêt à mettre en place votre infrastructure de scraping avec proxys résidentiels ? Consultez notre page de tarification pour les plans résidentiels, explorez nos localisations disponibles, ou consultez la documentation ProxyHat pour les détails techniques complets.






