在 .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 池和灵活的定位选项。






