Retour au blog

Configuration de proxy dans Go (Golang) : http.Client, SOCKS5 et rotation d'IP pour le scraping et l'automatisation

Comment configurer correctement un serveur proxy en Go via http.Client - de la configuration de base à la rotation des IP et à l'utilisation de SOCKS5. Un guide complet avec des exemples de code.

📅11 août 2026
```html

Si vous écrivez un parser, automatisez la collecte de données ou testez une API depuis différentes régions, vous serez tôt ou tard confronté à des blocages par IP. Go est l'un des langages les plus populaires pour écrire des outils réseau hautes performances, et la configuration des proxies a ses spécificités. Dans ce guide, nous examinerons tout, de la configuration de base de http.Client à la rotation avancée des IP avec prise en charge de SOCKS5.

Pourquoi utiliser des proxies dans les applications Go

Go est devenu le standard de facto pour écrire des outils réseau à forte charge : parsers, crawlers, bots pour surveiller les prix, outils d'automatisation API. C'est ici que le besoin de proxies se fait souvent sentir — et c'est ici qu'il est crucial de tout configurer correctement dès le départ.

Les principaux scénarios où un proxy est indispensable :

  • Parsing de places de marché — Wildberries, Ozon, AliExpress bloquent les IP après 50 à 200 requêtes depuis une seule adresse. Sans changement d'IP, le parser s'arrête.
  • Tests géographiques — vérification des résultats des moteurs de recherche, des annonces publicitaires ou des prix provenant de différents pays et régions.
  • Contourner le rate-limit — répartir la charge sur plusieurs IP permet de contourner les restrictions sur le nombre de requêtes par minute.
  • Anonymat — masquer l'IP réelle du serveur lors de l'interaction avec des API externes.
  • Tests de CDN et de géolocalisation — vérification de la livraison correcte du contenu pour différentes régions.

La bibliothèque standard de Go fournit des outils flexibles pour travailler avec des proxies — via http.Transport et http.Client. Nous examinerons chaque méthode en détail.

Configuration de base d'un proxy HTTP via http.Client

En Go, le proxy est configuré au niveau de http.Transport — la couche de transport du client HTTP. Cela donne un contrôle total sur le proxy par lequel passent les requêtes et permet de créer plusieurs clients avec différents proxies simultanément.

Un exemple de configuration minimale d'un proxy HTTP :

package main

import (
    "fmt"
    "io"
    "net/http"
    "net/url"
)

func main() {
    // Indiquez l'adresse du serveur proxy
    proxyURL, err := url.Parse("http://123.45.67.89:8080")
    if err != nil {
        panic(err)
    }

    // Créez un transport avec le proxy
    transport := &http.Transport{
        Proxy: http.ProxyURL(proxyURL),
    }

    // Créez un client avec notre transport
    client := &http.Client{
        Transport: transport,
    }

    // Faites une requête via le proxy
    resp, err := client.Get("https://httpbin.org/ip")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Println(string(body))
}

Que se passe-t-il dans ce code :

  1. url.Parse — analyse la chaîne d'adresse du proxy en une structure *url.URL
  2. http.Transport{Proxy: http.ProxyURL(proxyURL)} — crée un transport qui dirige toutes les requêtes via le proxy spécifié
  3. http.Client{Transport: transport} — crée un client avec notre transport

Un point important : si vous utilisez http.DefaultClient (c'est-à-dire simplement http.Get() sans client explicite), le proxy ne sera pas appliqué. Créez toujours votre propre http.Client pour travailler avec des proxies.

💡 Conseil

Ajoutez un délai d'attente dans http.Client : Timeout: 30 * time.Second. Les serveurs proxy peuvent répondre plus lentement qu'une connexion directe, et sans délai d'attente, les goroutines peuvent rester bloquées indéfiniment.

Proxy via des variables d'environnement

Go prend en charge les variables d'environnement standard pour les proxies — HTTP_PROXY, HTTPS_PROXY et NO_PROXY. Le transport standard http.DefaultTransport les lit automatiquement via la fonction http.ProxyFromEnvironment.

Configuration des variables d'environnement avant de lancer l'application :

# Dans le terminal (Linux/macOS)
export HTTP_PROXY="http://123.45.67.89:8080"
export HTTPS_PROXY="http://123.45.67.89:8080"
export NO_PROXY="localhost,127.0.0.1"

# Lancer l'application
go run main.go

Si vous souhaitez utiliser ProxyFromEnvironment dans votre propre transport (par exemple, avec des configurations TLS supplémentaires), faites comme suit :

transport := &http.Transport{
    Proxy: http.ProxyFromEnvironment,
    // Configurations supplémentaires...
    TLSHandshakeTimeout:   10 * time.Second,
    ResponseHeaderTimeout: 30 * time.Second,
}

client := &http.Client{
    Transport: transport,
    Timeout:   60 * time.Second,
}

Cette approche est pratique pour les environnements de production où l'adresse du proxy est définie via la configuration de déploiement (Docker, Kubernetes, systemd), plutôt que d'être codée en dur dans le code. Cela facilite également le changement de proxy sans recompilation de l'application.

Connexion d'un proxy SOCKS5 en Golang

SOCKS5 est un protocole plus polyvalent par rapport aux proxies HTTP. Il fonctionne à un niveau plus bas et prend en charge toutes les connexions TCP/UDP, et pas seulement HTTP/HTTPS. Pour le travail avec des parsers et l'automatisation, SOCKS5 est souvent préférable — surtout lors de l'utilisation de proxies résidentiels, qui sont souvent fournis via ce protocole.

Pour travailler avec SOCKS5 en Go, vous avez besoin du package golang.org/x/net/proxy :

go get golang.org/x/net/proxy

Exemple de connexion à un proxy SOCKS5 sans authentification :

package main

import (
    "fmt"
    "io"
    "net/http"
    "golang.org/x/net/proxy"
)

func main() {
    // Créez un dialer SOCKS5
    dialer, err := proxy.SOCKS5("tcp", "123.45.67.89:1080", nil, proxy.Direct)
    if err != nil {
        panic(err)
    }

    // Créez un transport avec le dialer personnalisé
    transport := &http.Transport{
        Dial: dialer.Dial,
    }

    client := &http.Client{
        Transport: transport,
    }

    resp, err := client.Get("https://httpbin.org/ip")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Println(string(body))
}

À partir de Go 1.20+, vous pouvez également utiliser le support intégré de SOCKS5 via url.Parse avec le préfixe socks5:// :

proxyURL, _ := url.Parse("socks5://123.45.67.89:1080")

transport := &http.Transport{
    Proxy: http.ProxyURL(proxyURL),
}

client := &http.Client{Transport: transport}

Cette syntaxe est plus simple et ne nécessite pas de dépendances supplémentaires. Cependant, pour des scénarios plus complexes (par exemple, SOCKS5 avec authentification dans les anciennes versions de Go), le package golang.org/x/net/proxy reste pertinent.

Authentification : identifiant et mot de passe dans le proxy

La plupart des fournisseurs de proxies commerciaux utilisent l'authentification par identifiant et mot de passe. Il existe deux façons de transmettre les informations d'identification en Go.

Méthode 1 : Intégrer dans l'URL

// Format : protocol://username:password@host:port
proxyURL, err := url.Parse("http://myuser:[email protected]:8080")
if err != nil {
    panic(err)
}

transport := &http.Transport{
    Proxy: http.ProxyURL(proxyURL),
}

client := &http.Client{
    Transport: transport,
    Timeout:   30 * time.Second,
}

Méthode 2 : Via la structure url.URL (plus sécurisé)

proxyURL := &url.URL{
    Scheme: "http",
    Host:   "proxy.example.com:8080",
    User:   url.UserPassword("myuser", "mypassword"),
}

transport := &http.Transport{
    Proxy: http.ProxyURL(proxyURL),
}

client := &http.Client{
    Transport: transport,
    Timeout:   30 * time.Second,
}

La deuxième méthode est préférable — elle gère correctement les caractères spéciaux dans le mot de passe (par exemple, @, #, :), qui peuvent casser l'analyse de l'URL dans la première variante.

Pour SOCKS5 avec authentification via le package golang.org/x/net/proxy :

auth := &proxy.Auth{
    User:     "myuser",
    Password: "mypassword",
}

dialer, err := proxy.SOCKS5("tcp", "proxy.example.com:1080", auth, proxy.Direct)
if err != nil {
    panic(err)
}

transport := &http.Transport{
    Dial: dialer.Dial,
}

client := &http.Client{
    Transport: transport,
    Timeout:   30 * time.Second,
}

⚠️ Sécurité

Ne codez jamais en dur l'identifiant et le mot de passe du proxy directement dans le code. Utilisez des variables d'environnement ou des fichiers de configuration qui ne vont pas dans le dépôt. Ajoutez .env à .gitignore.

Rotation des proxies : changement d'IP entre les requêtes

Un proxy — c'est bien pour les tests, mais pour le parsing réel, une rotation des IP est nécessaire. Les sites suivent la fréquence des requêtes depuis une seule adresse, et même un bon proxy sera bloqué si vous envoyez des milliers de requêtes consécutives.

Il existe deux approches pour la rotation en Go :

Approche 1 : Liste de proxies avec round-robin

package main

import (
    "fmt"
    "io"
    "net/http"
    "net/url"
    "sync/atomic"
    "time"
)

type ProxyRotator struct {
    proxies []*url.URL
    counter uint64
}

func NewProxyRotator(proxyURLs []string) (*ProxyRotator, error) {
    proxies := make([]*url.URL, 0, len(proxyURLs))
    for _, p := range proxyURLs {
        u, err := url.Parse(p)
        if err != nil {
            return nil, err
        }
        proxies = append(proxies, u)
    }
    return &ProxyRotator{proxies: proxies}, nil
}

// GetClient retourne un client avec le prochain proxy en rotation
func (r *ProxyRotator) GetClient() *http.Client {
    idx := atomic.AddUint64(&r.counter, 1) % uint64(len(r.proxies))
    proxyURL := r.proxies[idx]

    transport := &http.Transport{
        Proxy: http.ProxyURL(proxyURL),
    }

    return &http.Client{
        Transport: transport,
        Timeout:   30 * time.Second,
    }
}

func main() {
    proxyList := []string{
        "http://user:[email protected]:8080",
        "http://user:[email protected]:8080",
        "http://user:[email protected]:8080",
    }

    rotator, err := NewProxyRotator(proxyList)
    if err != nil {
        panic(err)
    }

    // Chaque requête passe par le prochain proxy
    for i := 0; i < 10; i++ {
        client := rotator.GetClient()
        resp, err := client.Get("https://httpbin.org/ip")
        if err != nil {
            fmt.Printf("Erreur de requête %d: %v\n", i, err)
            continue
        }
        body, _ := io.ReadAll(resp.Body)
        resp.Body.Close()
        fmt.Printf("Requête %d: %s\n", i, string(body))
    }
}

Approche 2 : Sélection dynamique du proxy via une fonction

Le champ Proxy dans http.Transport accepte une fonction avec la signature func(*http.Request) (*url.URL, error). Cela permet de choisir le proxy dynamiquement pour chaque requête :

proxies := []*url.URL{
    mustParseURL("http://proxy1.example.com:8080"),
    mustParseURL("http://proxy2.example.com:8080"),
    mustParseURL("http://proxy3.example.com:8080"),
}

var counter uint64

transport := &http.Transport{
    Proxy: func(req *http.Request) (*url.URL, error) {
        // Choisissez le proxy en fonction de la requête (round-robin)
        idx := atomic.AddUint64(&counter, 1) % uint64(len(proxies))
        return proxies[idx], nil
    },
}

client := &http.Client{
    Transport: transport,
    Timeout:   30 * time.Second,
}

func mustParseURL(s string) *url.URL {
    u, err := url.Parse(s)
    if err != nil {
        panic(err)
    }
    return u
}

La deuxième approche est plus élégante — un client fait automatiquement la rotation des proxies pour chaque requête. C'est particulièrement pratique pour les requêtes concurrentes via des goroutines.

Quel type de proxy choisir pour un projet Go

Le choix du type de proxy influence directement le succès de votre projet. Différents types conviennent à différentes tâches — examinons les principales options :

Type de proxy Vitesse Anonymat Meilleur pour
Datacenter ⚡ Élevée Moyenne Parsing de données ouvertes, tests d'API, tests de charge
Résidentiels 🔄 Moyenne Élevée Parsing de places de marché (Wildberries, Ozon), contournement de la protection anti-bot
Mobiles 🔄 Moyenne Maximale Travail avec les réseaux sociaux, services avec une protection anti-bot stricte

Pour le parsing de places de marché (Wildberries, Ozon, Avito) : utilisez des proxies résidentiels. Ces plateformes utilisent activement des systèmes de détection de bots (Cloudflare, solutions propriétaires) et bloquent facilement les IP des datacenters. Les IP résidentielles ressemblent à celles des utilisateurs domestiques ordinaires.

Pour les tests d'API et les tests de charge : les proxies de datacenter sont le choix optimal. Vitesse élevée, connexion stable, latence prévisible.

Pour les tests géographiques : proxies résidentiels avec sélection de pays/ville. Ils permettent de vérifier comment votre service ou votre publicité apparaît pour les utilisateurs d'une région spécifique.

Erreurs fréquentes et meilleures pratiques

Au fil des années de travail avec des proxies dans des projets Go, une liste d'erreurs typiques s'est cristallisée, qui ralentissent le développement et réduisent la fiabilité des parsers. Examinons chacune d'elles.

Erreur 1 : Réutilisation du transport sans réinitialisation des connexions

http.Transport met en cache les connexions TCP (keep-alive). Si vous créez un nouveau transport pour chaque requête, vous perdez cet avantage. Mais si vous réutilisez un transport fixe avec un proxy lors de la rotation, les requêtes peuvent passer par une ancienne connexion, contournant le nouveau proxy.

Solution : lors du changement de proxy, appelez transport.CloseIdleConnections() ou utilisez une approche fonctionnelle avec sélection dynamique de proxy (décrite dans la section sur la rotation).

Erreur 2 : Pas de gestion des erreurs de proxy

Les proxies peuvent être indisponibles, surchargés ou bloqués par le site cible. Sans logique de retry, votre parser s'arrêtera à la première erreur.

func fetchWithRetry(client *http.Client, targetURL string, maxRetries int) ([]byte, error) {
    var lastErr error
    for i := 0; i < maxRetries; i++ {
        resp, err := client.Get(targetURL)
        if err != nil {
            lastErr = err
            time.Sleep(time.Duration(i+1) * time.Second) // Délai exponentiel
            continue
        }
        defer resp.Body.Close()

        if resp.StatusCode == 429 || resp.StatusCode == 403 {
            lastErr = fmt.Errorf("bloqué : statut %d", resp.StatusCode)
            time.Sleep(time.Duration(i+1) * 2 * time.Second)
            continue
        }

        return io.ReadAll(resp.Body)
    }
    return nil, fmt.Errorf("toutes les tentatives épuisées : %w", lastErr)
}

Erreur 3 : Ignorer la vérification TLS

Certains développeurs désactivent la vérification des certificats TLS pour les proxies (InsecureSkipVerify: true). Cela crée une vulnérabilité aux attaques MITM. Si le serveur proxy utilise un certificat auto-signé, ajoutez-le à la liste des certificats de confiance, plutôt que de désactiver complètement la vérification.

Erreur 4 : Identiques en-têtes pour toutes les requêtes

Même avec la rotation des proxies, vous pouvez être bloqué en fonction des modèles d'en-têtes. Changez User-Agent avec le proxy, ajoutez des en-têtes de navigateur réalistes :

userAgents := []string{
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/119.0.0.0 Safari/537.36",
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/118.0.0.0 Safari/537.36",
}

req, _ := http.NewRequest("GET", targetURL, nil)
req.Header.Set("User-Agent", userAgents[rand.Intn(len(userAgents))])
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
req.Header.Set("Accept-Language", "fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7")
req.Header.Set("Accept-Encoding", "gzip, deflate, br")

Liste de contrôle pour un client proxy fiable en Go

  • ✅ Délai d'attente sur le client (Timeout: 30 * time.Second)
  • ✅ Logique de retry avec délai exponentiel
  • ✅ Rotation des proxies lors des erreurs 403/429
  • ✅ Rotation de User-Agent avec le proxy
  • ✅ Journalisation des erreurs avec indication du proxy utilisé
  • ✅ Informations d'identification via des variables d'environnement, pas en dur
  • ✅ Fermeture de resp.Body via defer
  • ✅ Limitation de la concurrence via un sémaphore ou un worker pool

Exemple complet d'un worker pool avec rotation des proxies

package main

import (
    "fmt"
    "io"
    "net/http"
    "net/url"
    "sync"
    "sync/atomic"
    "time"
)

type Scraper struct {
    proxies []*url.URL
    counter uint64
    workers int
}

func (s *Scraper) getClient() *http.Client {
    idx := atomic.AddUint64(&s.counter, 1) % uint64(len(s.proxies))
    return &http.Client{
        Transport: &http.Transport{
            Proxy: http.ProxyURL(s.proxies[idx]),
        },
        Timeout: 30 * time.Second,
    }
}

func (s *Scraper) Scrape(urls []string) map[string]string {
    results := make(map[string]string)
    var mu sync.Mutex
    var wg sync.WaitGroup

    sem := make(chan struct{}, s.workers) // Sémaphore pour limiter la concurrence

    for _, u := range urls {
        wg.Add(1)
        go func(targetURL string) {
            defer wg.Done()
            sem <- struct{}{}
            defer func() { <-sem }()

            client := s.getClient()
            resp, err := client.Get(targetURL)
            if err != nil {
                fmt.Printf("Erreur : %v\n", err)
                return
            }
            defer resp.Body.Close()

            body, _ := io.ReadAll(resp.Body)

            mu.Lock()
            results[targetURL] = string(body)
            mu.Unlock()
        }(u)
    }

    wg.Wait()
    return results
}

Conclusion

La configuration des proxies en Go est une tâche qui peut être réalisée en quelques lignes de code, mais qui nécessite une compréhension des nuances : utilisation correcte de http.Transport, gestion correcte de l'authentification, rotation intelligente des IP et gestion des erreurs. La bibliothèque standard fournit tout le nécessaire pour les proxies HTTP, et le package golang.org/x/net/proxy répond au besoin de SOCKS5.

Principales conclusions de l'article :

  • Créez toujours votre propre http.Clienthttp.DefaultClient ne prend pas en charge les proxies directement
  • Utilisez une approche fonctionnelle (Proxy: func(r *Request) (*url.URL, error)) pour la rotation dynamique
  • Conservez les informations d'identification dans des variables d'environnement, pas dans le code
  • Ajoutez une logique de retry — les proxies ne sont pas fiables à 100%
  • Changez User-Agent avec le proxy pour une efficacité maximale

Si vous écrivez un parser pour des places de marché comme Wildberries ou Ozon, nous vous recommandons d'utiliser des proxies résidentiels — ils possèdent de véritables IP d'utilisateurs domestiques et sont beaucoup moins souvent bloqués par les systèmes anti-bots. Pour des scénarios à forte charge avec des milliers de requêtes par minute, les proxies de datacenter seront le choix optimal — ils offrent la vitesse maximale et la stabilité de la connexion.

```