在C#汽车广告采集应用中配置多代理轮询访问的实现方案
给你的C#汽车广告采集工具添加代理轮询功能
嘿,MariuszKK!我看了你这段采集otomoto.pl汽车信息的代码,要实现交替使用代理避免被封禁的需求其实很 straightforward。下面我来一步步帮你改造代码:
核心思路
- 维护一个代理服务器列表,每次请求时按顺序轮切换代理
- 为每个代理配置
HttpClientHandler,让HttpClient通过指定代理发送请求 - 合理管理
HttpClient的生命周期,避免资源泄漏
改造后的完整代码
using System; using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Threading.Tasks; using HtmlAgilityPack; class Program { static async Task Main(string[] args) { // 1. 定义你的代理服务器列表,格式:http://ip:port 或带认证的 http://user:pass@ip:port var proxies = new List<string> { "http://proxy1.example.com:8080", "http://proxy2.example.com:8080", "http://proxy3.example.com:8080" // 可以继续添加更多代理 }; if (proxies.Count == 0) { Console.WriteLine("请至少配置一个代理服务器!"); return; } int currentProxyIndex = 0; for (int i = 1; i < 10; i++) { string url = $"https://www.otomoto.pl/osobowe/audi/a3/?page={i}"; HttpResponseMessage response = null; string html = string.Empty; try { // 2. 获取当前轮询的代理,并更新索引(循环复用) string currentProxy = proxies[currentProxyIndex]; currentProxyIndex = (currentProxyIndex + 1) % proxies.Count; // 3. 配置HttpClientHandler使用指定代理 var handler = new HttpClientHandler(); handler.Proxy = new WebProxy(currentProxy); // 如果代理需要用户名密码认证,取消下面注释并填写信息 // handler.Proxy.Credentials = new NetworkCredential("your_proxy_username", "your_proxy_password"); // 4. 使用带代理的Handler创建HttpClient(用using自动释放资源) using var httpClient = new HttpClient(handler); // 可选:添加请求头模拟浏览器,降低被识别为爬虫的概率 httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"); Console.WriteLine($"正在使用代理 {currentProxy} 访问第 {i} 页..."); response = await httpClient.GetAsync(url); response.EnsureSuccessStatusCode(); // 检查请求是否成功 html = await response.Content.ReadAsStringAsync(); } catch (Exception ex) { Console.WriteLine($"请求第 {i} 页失败:{ex.Message}"); // 可选:如果当前代理失效,可以跳过并尝试下一个,或者重试 continue; } // 5. 原有的HTML解析逻辑保持不变 var htmlDocument = new HtmlDocument(); htmlDocument.LoadHtml(html); var cars = htmlDocument.DocumentNode.Descendants("div") .Where(node => node.GetAttributeValue("class", "") == "offers list") .ToList(); if (cars.Count == 0) { Console.WriteLine("未找到车辆列表元素"); continue; } var carItems = cars[0].Descendants("article") .Where(node => node.GetAttributeValue("data-test", "").Contains("search-result-item")) .ToList(); foreach (var carItem in carItems) { try { var marka = carItem.Descendants("a") .Where(node => node.GetAttributeValue("class", "") == "offer-title__link") .First().InnerText.Trim('\n', ' '); Console.WriteLine($"品牌型号:{marka}"); var year = carItem.Descendants("li") .Where(node => node.GetAttributeValue("data-code", "") == "year") .First().InnerText.Trim('\n', ' '); Console.WriteLine("年份:" + year); var mileage = carItem.Descendants("li") .Where(node => node.GetAttributeValue("data-code", "") == "mileage") .First().InnerText.Trim('\n', ' '); Console.WriteLine("里程:" + mileage); try { var engine = carItem.Descendants("li") .Where(node => node.GetAttributeValue("data-code", "") == "engine_capacity") .First().InnerText.Trim('\n', ' '); Console.WriteLine("发动机排量:" + engine); } catch { Console.WriteLine("发动机排量:无信息"); } var fuel = carItem.Descendants("li") .Where(node => node.GetAttributeValue("data-code", "").Contains("fuel_type")) .First().InnerText.Trim('\n', ' '); Console.WriteLine("燃油类型:" + fuel); } catch (Exception ex) { Console.WriteLine($"解析车辆信息失败:{ex.Message}"); } Console.WriteLine("---------------------------------------------------"); } // 可选:添加延迟,避免请求过于频繁触发反爬 await Task.Delay(1000); } } }
关键改进点说明
- 代理轮询:通过
currentProxyIndex计数器循环复用代理列表,实现交替使用不同代理 - 资源管理:用
using关键字自动释放HttpClient和HttpClientHandler,避免资源泄漏 - 异常处理:添加了请求和解析过程的异常捕获,避免单个请求失败导致整个程序崩溃
- 反爬优化:添加了浏览器UA头和请求延迟,降低被网站识别为爬虫的概率
额外建议
- 确保代理服务器的可用性,定期清理失效代理
- 如果代理需要认证,记得正确配置
NetworkCredential - 可以考虑添加重试机制:当某个代理请求失败时,自动切换到下一个代理重试当前页面
- 不要一次性请求太多页面,合理控制请求频率
内容的提问来源于stack exchange,提问作者MariuszKK
相关产品推荐
相关产品推荐

