C# HTTP 代理完全指南:.NET 8+ HttpClient 与住宅代理实战

深入讲解 .NET 8+ 中使用 HttpClient、SocketsHttpHandler 配置 HTTP 代理的最佳实践,涵盖轮换代理池、Polly 重试策略、并发爬取和 TLS 证书固定,适合需要大规模数据采集的 .NET 开发者。

C# HTTP 代理完全指南:.NET 8+ HttpClient 与住宅代理实战

在 .NET 应用中正确配置 HTTP 代理并非简单地设置一个 URL 那么直接。无论是构建价格监控系统、SERP 爬虫还是自动化测试框架,你都需要理解 HttpClient 的生命周期、连接池行为以及如何优雅地处理代理失败。本文将从基础配置逐步深入到生产级的轮换代理池实现。

C# HTTP 代理基础:HttpClient 与 WebProxy

.NET 中使用代理的核心是 HttpClientHandler(或 .NET Core 3.1+ 的 SocketsHttpHandler)配合 WebProxy。许多开发者的第一个错误是每次请求都创建新的 HttpClient 实例——这会导致端口耗尽和连接堆积。

正确的模式是使用 IHttpClientFactory 或静态 HttpClient,同时在 Handler 中配置代理:

using System.Net;
using System.Net.Http;

// 1. 创建 WebProxy 实例,指定代理服务器地址
var proxy = new WebProxy("http://gate.proxyhat.com:8080", BypassOnLocal: false);

// 2. 配置代理认证(如果代理需要用户名密码)
proxy.Credentials = new NetworkCredential(
    userName: "user-country-US-session-mySession123",
    password: "your_password_here"
);

// 3. 配置绕过列表(某些请求不走代理)
proxy.BypassList = new string[]
{
    "localhost",
    "127\\.0\\.0\\.1",
    "192\\.168\\..*",  // 内网地址
    ".*\\.internal\\.company\\.com"
};

// 4. 创建 HttpClientHandler 并设置代理
var handler = new HttpClientHandler
{
    Proxy = proxy,
    UseProxy = true,
    UseDefaultCredentials = false,
    AllowAutoRedirect = true,
    MaxAutomaticRedirections = 10,
    AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
};

// 5. 使用静态 HttpClient(推荐)或通过 IHttpClientFactory 注入
var client = new HttpClient(handler)
{
    Timeout = TimeSpan.FromSeconds(30)
};

// 发送请求
try
{
    var response = await client.GetAsync("https://httpbin.org/ip");
    response.EnsureSuccessStatusCode();
    var content = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"Response: {content}");
}
catch (HttpRequestException ex)
{
    Console.WriteLine($"Request failed: {ex.Message}");
}
关键点:WebProxy 的 BypassList 使用正则表达式匹配。如果你发现某些内网请求仍然走了代理,检查正则是否正确转义了点号(\\.)。

使用 IHttpClientFactory 集成代理

在生产环境中,推荐使用 IHttpClientFactory 管理 HttpClient 生命周期:

// Program.cs - 依赖注入配置
using Microsoft.Extensions.DependencyInjection;
using System.Net;

var services = new ServiceCollection();

services.AddHttpClient("ProxiedClient")
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        var proxy = new WebProxy("http://gate.proxyhat.com:8080")
        {
            Credentials = new NetworkCredential("user-country-US", "your_password")
        };
        
        return new HttpClientHandler
        {
            Proxy = proxy,
            UseProxy = true
        };
    })
    .SetHandlerLifetime(TimeSpan.FromMinutes(5)); // 控制 handler 回收周期

var provider = services.BuildServiceProvider();
var factory = provider.GetRequiredService<IHttpClientFactory>();
var client = factory.CreateClient("ProxiedClient");

SocketsHttpHandler:现代 .NET 代理配置

.NET Core 3.0 引入的 SocketsHttpHandler 是 HttpClient 的默认底层实现,它提供了更细粒度的控制,包括连接池调优和按请求动态选择代理的能力。

using System.Net;
using System.Net.Http;

var handler = new SocketsHttpHandler
{
    // 连接池配置
    PooledConnectionLifetime = TimeSpan.FromMinutes(15),
    PooledConnectionIdleTimeout = TimeSpan.FromMinutes(5),
    MaxConnectionsPerServer = 100,
    
    // 启用 HTTP/2(如果目标服务器支持)
    EnableMultipleHttp2Connections = true,
    
    // 自动解压
    AutomaticDecompression = DecompressionMethods.All,
    
    // 代理配置
    Proxy = new WebProxy("http://gate.proxyhat.com:8080")
    {
        Credentials = new NetworkCredential("user-country-DE-city-berlin", "password")
    },
    UseProxy = true,
    
    // 连接超时
    ConnectTimeout = TimeSpan.FromSeconds(10)
};

var client = new HttpClient(handler);

// 并发请求示例
var urls = new[]
{
    "https://httpbin.org/ip",
    "https://httpbin.org/headers",
    "https://httpbin.org/user-agent"
};

var tasks = urls.Select(async url =>
{
    try
    {
        var response = await client.GetAsync(url);
        return await response.Content.ReadAsStringAsync();
    }
    catch (Exception ex)
    {
        return $"Error: {ex.Message}";
    }
});

var results = await Task.WhenAll(tasks);
foreach (var result in results)
{
    Console.WriteLine(result[..Math.Min(200, result.Length)]);
}

按请求动态切换代理

当你需要为每个请求使用不同的代理(例如轮换 IP),可以使用 ConnectCallback 自定义连接逻辑:

using System.Net;
using System.Net.Http;
using System.Net.Sockets;

public class DynamicProxyHandler
{
    private readonly Func<string, string> _proxySelector;
    
    public DynamicProxyHandler(Func<string, string> proxySelector)
    {
        _proxySelector = proxySelector;
    }
    
    public HttpClient CreateClient()
    {
        var handler = new SocketsHttpHandler
        {
            ConnectCallback = async (context, token) =>
            {
                // 根据目标 URL 选择代理
                var proxyUrl = _proxySelector(context.DnsEndPoint.Host);
                
                if (string.IsNullOrEmpty(proxyUrl))
                {
                    // 直连
                    var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                    await socket.ConnectAsync(context.DnsEndPoint, token);
                    return new NetworkStream(socket, ownsSocket: true);
                }
                
                // 通过代理连接
                var proxyUri = new Uri(proxyUrl);
                var proxyEndpoint = new DnsEndPoint(proxyUri.Host, proxyUri.Port);
                
                var proxySocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                await proxySocket.ConnectAsync(proxyEndpoint, token);
                
                var stream = new NetworkStream(proxySocket, ownsSocket: true);
                
                // HTTP CONNECT 方法建立隧道
                var connectRequest = $"CONNECT {context.DnsEndPoint.Host}:{context.DnsEndPoint.Port} HTTP/1.1\r\nHost: {context.DnsEndPoint.Host}\r\n\r\n";
                var connectBytes = Encoding.ASCII.GetBytes(connectRequest);
                await stream.WriteAsync(connectBytes, token);
                
                // 读取代理响应
                var buffer = new byte[1024];
                var bytesRead = await stream.ReadAsync(buffer, token);
                var response = Encoding.ASCII.GetString(buffer, 0, bytesRead);
                
                if (!response.Contains("200"))
                {
                    throw new HttpRequestException($"Proxy connection failed: {response}");
                }
                
                return stream;
            },
            
            PooledConnectionLifetime = TimeSpan.FromMinutes(2) // 短生命周期适应代理轮换
        };
        
        return new HttpClient(handler);
    }
}

// 使用示例
var proxyRotator = new DynamicProxyHandler(host =>
{
    // 简单轮换逻辑
    var proxies = new[]
    {
        "http://user-country-US:pass@gate.proxyhat.com:8080",
        "http://user-country-GB:pass@gate.proxyhat.com:8080",
        "http://user-country-DE:pass@gate.proxyhat.com:8080"
    };
    return proxies[Random.Shared.Next(proxies.Length)];
});

var client = proxyRotator.CreateClient();

Polly 集成:重试与熔断

代理请求失败是常态而非例外。网络波动、代理 IP 被封、目标服务器限流都会导致失败。Polly 提供了优雅的容错机制。

using Polly;
using Polly.Retry;
using Polly.CircuitBreaker;
using System.Net;

// 定义重试策略:处理代理常见错误
var retryPolicy = Policy
    .Handle<HttpRequestException>()
    .Or<TaskCanceledException>()
    .OrResult<HttpResponseMessage>(r => 
        r.StatusCode == HttpStatusCode.TooManyRequests || 
        r.StatusCode == HttpStatusCode.RequestTimeout ||
        r.StatusCode == HttpStatusCode.BadGateway ||
        r.StatusCode == HttpStatusCode.GatewayTimeout ||
        r.StatusCode == HttpStatusCode.ServiceUnavailable)
    .WaitAndRetryAsync(
        retryCount: 5,
        sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
        onRetry: (outcome, timeSpan, retryCount, context) =>
        {
            Console.WriteLine($"[Retry {retryCount}] Waiting {timeSpan.TotalSeconds}s before retry. Status: {outcome.Result?.StatusCode}");
        });

// 熔断策略:连续失败后暂停请求
var circuitBreakerPolicy = Policy
    .Handle<HttpRequestException>()
    .OrResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
    .CircuitBreakerAsync(
        exceptionsAllowedBeforeBreaking: 3,
        durationOfBreak: TimeSpan.FromSeconds(30),
        onBreak: (outcome, timeSpan) =>
        {
            Console.WriteLine($"[Circuit Breaker] Opened for {timeSpan.TotalSeconds}s due to: {outcome.Exception?.Message ?? outcome.Result.StatusCode.ToString()}");
        },
        onReset: () =>
        {
            Console.WriteLine("[Circuit Breaker] Reset, accepting requests again");
        });

// 组合策略
var resilientPolicy = Policy.WrapAsync(retryPolicy, circuitBreakerPolicy);

// 配置 HttpClient
var handler = new SocketsHttpHandler
{
    Proxy = new WebProxy("http://gate.proxyhat.com:8080")
    {
        Credentials = new NetworkCredential("user-country-US", "password")
    },
    UseProxy = true
};

var innerClient = new HttpClient(handler);

// 使用策略执行请求
var response = await resilientPolicy.ExecuteAsync(async () =>
{
    var result = await innerClient.GetAsync("https://httpbin.org/status/429");
    return result;
});

Console.WriteLine($"Final status: {response.StatusCode}");

结合 IHttpClientFactory 的 Polly 配置

// Program.cs
using Polly;
using Polly.Extensions.Http;

var retryPolicy = HttpPolicyExtensions
    .HandleTransientHttpError()
    .OrResult(msg => msg.StatusCode == HttpStatusCode.TooManyRequests)
    .WaitAndRetryAsync(3, retryAttempt => 
        TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));

services.AddHttpClient("ResilientProxyClient")
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        var proxy = new WebProxy("http://gate.proxyhat.com:8080")
        {
            Credentials = new NetworkCredential("user-country-US", "password")
        };
        return new SocketsHttpHandler { Proxy = proxy, UseProxy = true };
    })
    .AddPolicyHandler(retryPolicy)
    .SetHandlerLifetime(TimeSpan.FromMinutes(5));

Parallel.ForEachAsync:并发爬取实战

.NET 6 引入的 Parallel.ForEachAsync 是并发 I/O 操作的理想选择,它自动管理任务调度和限流:

using System.Collections.Concurrent;
using System.Diagnostics;

public class ConcurrentScraper
{
    private readonly HttpClient _client;
    private readonly int _maxConcurrency;
    
    public ConcurrentScraper(int maxConcurrency = 20)
    {
        _maxConcurrency = maxConcurrency;
        
        var handler = new SocketsHttpHandler
        {
            Proxy = new WebProxy("http://gate.proxyhat.com:8080")
            {
                Credentials = new NetworkCredential("user-country-US", "password")
            },
            UseProxy = true,
            PooledConnectionLifetime = TimeSpan.FromMinutes(5),
            MaxConnectionsPerServer = maxConcurrency
        };
        
        _client = new HttpClient(handler)
        {
            Timeout = TimeSpan.FromSeconds(30)
        };
    }
    
    public async Task<Dictionary<string, string>> ScrapeAsync(IEnumerable<string> urls)
    {
        var results = new ConcurrentDictionary<string, string>();
        var errors = new ConcurrentBag<Exception>();
        var successCount = 0;
        var sw = Stopwatch.StartNew();
        
        var options = new ParallelOptions
        {
            MaxDegreeOfParallelism = _maxConcurrency
        };
        
        await Parallel.ForEachAsync(urls, options, async (url, token) =>
        {
            try
            {
                var response = await _client.GetAsync(url, token);
                response.EnsureSuccessStatusCode();
                var content = await response.Content.ReadAsStringAsync(token);
                
                results[url] = content;
                Interlocked.Increment(ref successCount);
            }
            catch (Exception ex)
            {
                errors.Add(ex);
                results[url] = $"ERROR: {ex.Message}";
            }
        });
        
        sw.Stop();
        
        Console.WriteLine($"Completed: {successCount}/{urls.Count()} successful");
        Console.WriteLine($"Errors: {errors.Count}");
        Console.WriteLine($"Duration: {sw.Elapsed.TotalSeconds:F2}s");
        Console.WriteLine($"Rate: {successCount / sw.Elapsed.TotalSeconds:F2} req/s");
        
        return results.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
    }
}

// 使用示例
var scraper = new ConcurrentScraper(maxConcurrency: 50);

var urls = Enumerable.Range(1, 100)
    .Select(i => $"https://httpbin.org/delay/{i % 3}")
    .ToList();

var results = await scraper.ScrapeAsync(urls);
Console.WriteLine($"Scraped {results.Count} pages");

.NET 轮换代理池服务(依赖注入)

对于需要大规模爬取的应用,硬编码代理 URL 不够灵活。下面是一个完整的轮换代理池服务实现:

using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;

public interface IProxyPool
{
    string GetProxyUrl(string? country = null);
    void MarkProxyFailed(string proxyUrl);
    void RefreshPool();
}

public class ProxyPoolOptions
{
    public string GatewayHost { get; set; } = "gate.proxyhat.com";
    public int HttpPort { get; set; } = 8080;
    public int SocksPort { get; set; } = 1080;
    public string Username { get; set; } = string.Empty;
    public string Password { get; set; } = string.Empty;
    public string[] Countries { get; set; } = Array.Empty<string>();
    public bool EnableRotation { get; set; } = true;
    public TimeSpan StickySessionDuration { get; set; } = TimeSpan.FromMinutes(5);
}

public class RotatingProxyPool : IProxyPool, IDisposable
{
    private readonly ProxyPoolOptions _options;
    private readonly ILogger<RotatingProxyPool> _logger;
    private readonly ConcurrentDictionary<string, DateTime> _failedProxies = new();
    private readonly ConcurrentDictionary<string, string> _stickySessions = new();
    private readonly Timer? _refreshTimer;
    
    public RotatingProxyPool(ProxyPoolOptions options, ILogger<RotatingProxyPool> logger)
    {
        _options = options;
        _logger = logger;
        
        // 定期清理失败的代理记录
        _refreshTimer = new Timer(_ => RefreshPool(), null, 
            TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
    }
    
    public string GetProxyUrl(string? country = null)
    {
        // 检查是否有粘性会话
        var sessionKey = country ?? "default";
        if (_stickySessions.TryGetValue(sessionKey, out var stickyProxy))
        {
            return stickyProxy;
        }
        
        // 构建代理 URL
        var username = BuildUsername(country);
        var proxyUrl = $"http://{username}:{_options.Password}@{_options.GatewayHost}:{_options.HttpPort}";
        
        // 如果启用粘性会话,缓存一段时间
        if (_options.EnableRotation)
        {
            _stickySessions[sessionKey] = proxyUrl;
            _ = Task.Run(async () =>
            {
                await Task.Delay(_options.StickySessionDuration);
                _stickySessions.TryRemove(sessionKey, out _);
            });
        }
        
        _logger.LogDebug("Allocated proxy: {ProxyUrl}", proxyUrl.Replace(_options.Password, "***"));
        return proxyUrl;
    }
    
    private string BuildUsername(string? country)
    {
        var parts = new List<string> { _options.Username };
        
        if (!string.IsNullOrEmpty(country))
        {
            parts.Add($"country-{country}");
        }
        
        // 添加会话 ID 以保持 IP 一致性(可选)
        if (_options.EnableRotation)
        {
            var sessionId = Guid.NewGuid().ToString("N")[..8];
            parts.Add($"session-{sessionId}");
        }
        
        return string.Join("-", parts);
    }
    
    public void MarkProxyFailed(string proxyUrl)
    {
        _failedProxies[proxyUrl] = DateTime.UtcNow;
        _logger.LogWarning("Marked proxy as failed: {ProxyUrl}", proxyUrl);
        
        // 清除粘性会话
        foreach (var kvp in _stickySessions)
        {
            if (kvp.Value == proxyUrl)
            {
                _stickySessions.TryRemove(kvp.Key, out _);
            }
        }
    }
    
    public void RefreshPool()
    {
        var threshold = DateTime.UtcNow.AddMinutes(-10);
        var keysToRemove = _failedProxies
            .Where(kvp => kvp.Value < threshold)
            .Select(kvp => kvp.Key)
            .ToList();
        
        foreach (var key in keysToRemove)
        {
            _failedProxies.TryRemove(key, out _);
        }
        
        _logger.LogInformation("Proxy pool refreshed. Active proxies: {Count}", 
            _stickySessions.Count);
    }
    
    public void Dispose()
    {
        _refreshTimer?.Dispose();
    }
}

// DI 注册扩展
public static class ProxyPoolExtensions
{
    public static IServiceCollection AddRotatingProxyPool(
        this IServiceCollection services,
        Action<ProxyPoolOptions> configure)
    {
        services.Configure<ProxyPoolOptions>(configure);
        services.AddSingleton<IProxyPool, RotatingProxyPool>();
        services.AddHttpClient("ProxiedClient")
            .ConfigurePrimaryHttpMessageHandler(sp =>
            {
                var pool = sp.GetRequiredService<IProxyPool>();
                var options = sp.GetRequiredService<IOptions<ProxyPoolOptions>>().Value;
                
                var proxy = new WebProxy(pool.GetProxyUrl());
                return new SocketsHttpHandler
                {
                    Proxy = proxy,
                    UseProxy = true,
                    PooledConnectionLifetime = TimeSpan.FromMinutes(5)
                };
            });
        
        return services;
    }
}

// Program.cs 使用
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRotatingProxyPool(options =>
{
    options.Username = "your_username";
    options.Password = "your_password";
    options.Countries = new[] { "US", "GB", "DE", "JP" };
    options.EnableRotation = true;
    options.StickySessionDuration = TimeSpan.FromMinutes(10);
});

TLS 配置:证书固定与自定义 CA

某些企业环境或安全敏感场景需要自定义 TLS 验证。SocketsHttpHandler 提供了完整的 SSL/TLS 控制能力:

using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;

public class TlsProxyClient
{
    private readonly HttpClient _client;
    private readonly HashSet<string> _allowedCertificates;
    
    public TlsProxyClient(string proxyUrl, string? certPath = null)
    {
        _allowedCertificates = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        
        // 加载允许的证书指纹(证书固定)
        if (certPath != null && File.Exists(certPath))
        {
            var cert = new X509Certificate2(certPath);
            _allowedCertificates.Add(cert.Thumbprint);
            Console.WriteLine($"Pinned certificate: {cert.Thumbprint}");
        }
        
        var handler = new SocketsHttpHandler
        {
            Proxy = new WebProxy(proxyUrl),
            UseProxy = true,
            
            // TLS 配置
            SslOptions = new SslClientAuthenticationOptions
            {
                EnabledSslProtocols = SslProtocols.Tls13 | SslProtocols.Tls12,
                AllowRenegotiation = false,
                
                // 自定义证书验证
                RemoteCertificateValidationCallback = ValidateCertificate,
                
                // 客户端证书(双向 TLS)
                // ClientCertificates = new X509CertificateCollection { clientCert }
            },
            
            // 连接配置
            PooledConnectionLifetime = TimeSpan.FromMinutes(5),
            ConnectTimeout = TimeSpan.FromSeconds(15)
        };
        
        _client = new HttpClient(handler);
    }
    
    private bool ValidateCertificate(object sender, 
        X509Certificate? certificate, 
        X509Chain? chain, 
        SslPolicyErrors policyErrors)
    {
        // 如果没有证书,拒绝连接
        if (certificate == null)
        {
            Console.WriteLine("[TLS] No certificate provided");
            return false;
        }
        
        var cert2 = new X509Certificate2(certificate);
        
        // 证书固定:检查指纹
        if (_allowedCertificates.Count > 0)
        {
            if (!_allowedCertificates.Contains(cert2.Thumbprint))
            {
                Console.WriteLine($"[TLS] Certificate thumbprint mismatch: {cert2.Thumbprint}");
                return false;
            }
            Console.WriteLine($"[TLS] Certificate validated: {cert2.Thumbprint}");
            return true;
        }
        
        // 标准验证(允许自签名证书用于测试)
        if (policyErrors == SslPolicyErrors.None)
        {
            return true;
        }
        
        // 允许自签名证书(仅用于开发/测试环境!)
        if (policyErrors.HasFlag(SslPolicyErrors.RemoteCertificateChainErrors))
        {
            Console.WriteLine($"[TLS] Chain errors for: {cert2.Subject}");
            
            // 检查是否是预期的自签名证书
            chain ??= new X509Chain();
            chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
            chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
            
            if (chain.Build(cert2))
            {
                Console.WriteLine("[TLS] Self-signed certificate accepted");
                return true;
            }
        }
        
        Console.WriteLine($"[TLS] Validation failed: {policyErrors}");
        return false;
    }
    
    public async Task<string> GetAsync(string url)
    {
        try
        {
            var response = await _client.GetAsync(url);
            response.EnsureSuccessStatusCode();
            return await response.Content.ReadAsStringAsync();
        }
        catch (HttpRequestException ex)
        {
            Console.WriteLine($"[HTTP] Request failed: {ex.Message}");
            throw;
        }
    }
}

// 自定义根 CA(用于企业代理或测试环境)
public class CustomRootCaHandler
{
    public static HttpClientHandler CreateWithCustomCa(string caCertPath)
    {
        var caCert = new X509Certificate2(caCertPath);
        
        var handler = new HttpClientHandler
        {
            Proxy = new WebProxy("http://gate.proxyhat.com:8080")
            {
                Credentials = new NetworkCredential("user-country-US", "password")
            },
            UseProxy = true,
            
            // 加载自定义 CA 到信任链
            ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
            {
                if (errors == SslPolicyErrors.None)
                    return true;
                
                if (chain == null || cert == null)
                    return false;
                
                // 添加自定义 CA
                chain.ChainPolicy.ExtraStore.Add(caCert);
                chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
                
                return chain.Build(new X509Certificate2(cert));
            }
        };
        
        return handler;
    }
}

// 使用示例
var tlsClient = new TlsProxyClient(
    proxyUrl: "http://user-country-US:password@gate.proxyhat.com:8080",
    certPath: "./allowed-cert.pem"
);

var content = await tlsClient.GetAsync("https://example.com");
Console.WriteLine(content[..100]);

SOCKS5 代理支持

对于需要 SOCKS5 的场景(如某些住宅代理服务),.NET 8 原生支持 SOCKS5:

using System.Net;

var socksHandler = new SocketsHttpHandler
{
    Proxy = new WebProxy("socks5://gate.proxyhat.com:1080")
    {
        Credentials = new NetworkCredential("user-country-US", "password")
    },
    UseProxy = true
};

var socksClient = new HttpClient(socksHandler);
var response = await socksClient.GetAsync("https://httpbin.org/ip");
Console.WriteLine(await response.Content.ReadAsStringAsync());

最佳实践与性能调优

配置项 推荐值 说明
PooledConnectionLifetime 5-15 分钟 轮换代理场景建议较短,避免 IP 失效后连接池仍持有旧连接
MaxConnectionsPerServer 50-200 根据代理服务商限制调整,过高可能触发限流
Timeout 30-60 秒 代理请求通常比直连慢,需要更长超时
HandlerLifetime (DI) 5-10 分钟 与 PooledConnectionLifetime 协调,定期刷新 DNS
MaxDegreeOfParallelism 20-100 并发请求上限,需平衡目标服务器压力与采集效率
ProxyHat 配置提示:使用 user-country-{CC} 格式指定国家,user-city-{city} 指定城市,user-session-{id} 保持会话一致性。完整格式:user-country-US-city-newyork-session-abc123:password@gate.proxyhat.com:8080

关键要点总结

  • HttpClient 生命周期管理:使用 IHttpClientFactory 或静态实例,避免端口耗尽;Handler 生命周期应与代理轮换周期协调。
  • SocketsHttpHandler 是现代选择:提供连接池控制、按请求代理切换、TLS 定制等高级功能。
  • Polly 提升可靠性:组合重试与熔断策略,处理代理常见的 429、502、504 错误。
  • 并发控制:Parallel.ForEachAsync 简化并发代码,配合 MaxConnectionsPerServer 限流。
  • 代理池服务化:封装轮换逻辑,集成 DI,支持国家/城市定位和会话保持。
  • TLS 安全:证书固定防止中间人攻击,自定义 CA 支持企业代理场景。

正确配置代理后,你的 .NET 应用将能够稳定、高效地进行大规模数据采集。如需高质量的 住宅代理服务,ProxyHat 提供覆盖全球的 IP 池和灵活的定位选项。

准备开始了吗?

通过AI过滤访问148多个国家的5000多万个住宅IP。

查看价格住宅代理
← 返回博客