如何在 C# 中设置代理服务器

学习如何在C#中实现代理进行网页抓取,通过Visual Studio和.NET 7增强数据收集项目的隐私性和效率。
7 min read

直接访问互联网时,网站可以轻松地通过您的请求追踪到您的 IP 地址。这种信息泄露可能会导致定向广告和在线跟踪,并可能危及您的数字身份。

这就是代理的用武之地。它们在您的计算机和互联网之间充当中介的角色,帮助保护您的数字身份。当您使用代理服务器时,它会通过代理自身的 IP 地址代表您向网站发送请求。

在网页抓取方面,代理可以帮助您规避 IP 封禁,避免地域屏蔽并保护您的身份。在这篇文章中,您将学习如何为所有网页抓取项目在 C# 中实现代理。

前提

在开始学习本教程之前,请确保您已安装下列软件:

本文中的示例使用单独的 .NET 控制台应用程序。如需创建自己的控制台,可以参考以下任一指南:

首先,您需要创建两个控制台应用程序:WebScrapApp 和 WebScrapBrightData

如需进行网页抓取,尤其是在处理 HTML 内容时,您需要HtmlAgilityPack 等特定工具。此库简化了 HTML 解析和操作,因此从网页提取数据变得更加容易。

在两个项目( WebScrapApp 和 WebScrapBrightData)中,右键点击 NuGet 文件夹并右键选择“管理 NuGet 软件包”,以添加 NuGet 软件包 HtmlAgilityPack。跳出弹出窗口后,搜索 “HtmlAgilityPack” 并将其安装到两个项目中:

如需运行以下任何项目,您需要在命令提示符中导航至项目目录,先输入 cd path\to\your\project,然后运行 dotnet run 命令。或者,您可以按下 F5 在 Visual Studio 中创建并运行项目。这两种方法都能编译并执行您的应用程序,并显示相应的输出结果。

注意:如您未安装 Visual Studio 2022,也可使用其他支持 .NET 7 的集成开发环境 (IDE)。但需注意的是,本指南中的一些步骤可能存在差异。

如何设置本地代理

要进行网页抓取时,您首先要做的就是使用代理服务器。本教程使用开源代理 mitmproxy

首先,进入 mitmproxy 下载页面下载 10.1.6 版 ,记得根据您的操作系统选择对应的版本。如需更多帮助,请查看官方的 mitmproxy 安装指南

安装好 mitmproxy 后,打开终端并使用以下命令启动 mitmproxy:

mitmproxy

您会在 shell 或终端中看到下方窗口:

如需测试代理,请打开另一个终端或 shell 并运行以下 curl 请求:

 curl --proxy http://localhost:8080 "http://wttr.in/Dunedin?0"

输出结果应如下所示:

Weather report: Dunedin

                Cloudy
       .--.     +11(9) °C
    .-(    ).   ↙ 15 km/h
   (___.__)__)  10 km
                0.0 mm

在 mitmproxy 窗口中,您会看到它拦截了通过本地代理进行的调用:

C# 中的网页抓取

在下一节中,您将设置 C# 控制台应用程序 WebScrapApp 用于网页抓取。此应用程序使用代理服务器,可进行代理轮换以提高效率。

创建 HttpClient

名为 ProxyHttpClient 的类用于配置 HttpClient 实例,以通过指定的代理服务器路由请求。

在 WebScrapApp 项目中新建一个名为 ProxyHttpClient.cs 的类文件,并添加以下代码:

namespace WebScrapApp
{
    public class ProxyHttpClient
    {
        public static HttpClient CreateClient(string proxyUrl)
        {
            var httpClientHandler = new HttpClientHandler()
            {
                Proxy = new WebProxy(proxyUrl),
                UseProxy = true
            };
            return new HttpClient(httpClientHandler);
        }
    }
}


实现代理轮换

如需实现代理轮换,请在 WebScrapApp 解决方案中创建一个名为 ProxyRotator.cs 的类文件:

namespace WebScrapApp
{
    public class ProxyRotator
    {
        private List<string> _validProxies = new List<string>();
        private readonly Random _random = new();

        public ProxyRotator(string[] proxies, bool isLocal)
        { 
            if (isLocal)
            {
                _validProxies.Add("http://localhost:8080/");
            }
            else
            {
                _validProxies = ProxyChecker.GetWorkingProxies(proxies.ToList()).Result;
            }
            if (_validProxies.Count == 0)
                throw new InvalidOperationException();
        }

        public HttpClient ScrapeDataWithRandomProxy(string url)
        {
            if (_validProxies.Count == 0)
                throw new InvalidOperationException();

            var proxyUrl = _validProxies[_random.Next(_validProxies.Count)];
            return ProxyHttpClient.CreateClient(proxyUrl);
        }
    }
}

该类管理代理列表,提供一种为每个 Web 请求随机选择代理的方法。这种随机方法是降低网页抓取期间被发现及 IP 可能被封禁等风险的关键。

isLocal 设置为 True 时将使用 mitmproxy 中的本地代理。如果设置为 False,则使用代理的公共 IP。

ProxyChecker 用于验证代理服务器列表。

接下来,新建一个名为 ProxyChecker.cs 的类文件并添加以下代码:

using WebScrapApp;

namespace WebScrapApp
{
    public class ProxyChecker
    {
        private static SemaphoreSlim consoleSemaphore = new SemaphoreSlim(1, 1);
        private static int currentProxyNumber = 0;

        public static async Task<List<string>> GetWorkingProxies(List<string> proxies)
        {
            var tasks = new List<Task<Tuple<string, bool>>>();

            foreach (var proxyUrl in proxies)
            {
                tasks.Add(CheckProxy(proxyUrl, proxies.Count));
            }

            var results = await Task.WhenAll(tasks);

            var workingProxies = new List<string>();
            foreach (var result in results)
            {
                if (result.Item2)
                {
                    workingProxies.Add(result.Item1);
                }
            }

            return workingProxies;
        }

        private static async Task<Tuple<string, bool>> CheckProxy(string proxyUrl, int totalProxies)
        {
            var client = ProxyHttpClient.CreateClient(proxyUrl);
            bool isWorking = await IsProxyWorking(client);

            await consoleSemaphore.WaitAsync();
            try
            {
                currentProxyNumber++;
                Console.WriteLine($"Proxy: {currentProxyNumber} de {totalProxies}");
            }
            finally
            {
                consoleSemaphore.Release();
            }

            return new Tuple<string, bool>(proxyUrl, isWorking);
        }

        private static async Task<bool> IsProxyWorking(HttpClient client)
        {
            try
            {
                var testUrl = "http://www.google.com";
                var response = await client.GetAsync(testUrl);
                return response.IsSuccessStatusCode;
            }
            catch
            {
                return false;
            }
        }
    }

}

此代码定义了 ProxyChecker 用于验证代理服务器列表。当您将 GetWorkingProxies 方法与代理 URL 列表一起使用时,它会通过 CheckProxy 方法异步检查每个代理的状态,同时在 WorkingProxies 列表中收集可运行的代理。在 CheckProxy中,使用代理 URL 建立 HttpClient,向 http://www.google.com 发出测试请求,然后使用信号量安全地记录进度。

isProxyWorking方法可通过检查响应状态码来确认代理是否正常可用;如果代理可用,则会返回 true。该类有助于从给定列表中识别可用的代理。

抓取网页数据

如需抓取数据,请在 WebScrapApp 解决方案中新建一个名为 WebScraper.cs 的类文件,并添加以下代码:

using HtmlAgilityPack;

namespace WebScrapApp
{
    public class WebScraper
    {
        public static async Task ScrapeData(ProxyRotator proxyRotator, string url)
        {
            try
            {
                var client = proxyRotator.ScrapeDataWithRandomProxy(url);

                // Use HttpClient to make an asynchronous GET request
                var response = await client.GetAsync(url);
                var content = await response.Content.ReadAsStringAsync();

                // Load the HTML content into an HtmlDocument
                HtmlDocument doc = new();
                doc.LoadHtml(content);

                // Use XPath to find all <a> tags that are direct children of <li>, <p>, or <td>
                var nodes = doc.DocumentNode.SelectNodes("//li/a[@href] | //p/a[@href] | //td/a[@href]");

                if (nodes != null)
                {
                    foreach (var node in nodes)
                    {
                        string hrefValue = node.GetAttributeValue("href", string.Empty);
                        string title = node.InnerText; // This gets the text content of the <a> tag, which is usually the title

                        // Since Wikipedia URLs are relative, we need to convert them to absolute
                        Uri baseUri = new(url);
                        Uri fullUri = new(baseUri, hrefValue);

                        Console.WriteLine($"Title: {title}, Link: {fullUri.AbsoluteUri}");
                        // You can process each title and link as required
                    }
                }
                else
                {
                    Console.WriteLine("No article links found on the page.");
                }

                // Add additional logic for other data extraction as needed
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
    }
}

在这段代码中,您定义了用于封装网页抓取功能 WebScraper。当您调用 ScrapeData 方法时,您为其提供了 ProxyRotator 实例和目标 URL。在这个方法中,您可以使用 HttpClient 向 URL 发出异步 GET 请求,检索 HTML 内容,然后通过 HtmlAgilityPack 库将其解析。接着,您可以使用 XPath 查询从特定 HTML 元素中查找并提取链接和相应的标题。如果找到任何文章链接,则打印其标题和绝对 URL;否则将打印一条消息,提示未找到链接。

配置了代理轮换机制并实现网页抓取功能后,您需要将这些组件无缝集成至应用程序的主入口点,该入口点通常位于 Program.cs 中。该集成操作使应用程序能够在轮换使用代理的同时执行网页抓取任务,重点关注从维基百科主页抓取数据:

namespace WebScrapApp {
    public class Program
    {
        static async Task Main(string[] args)
        {
            string[] proxies = {
            "http://162.223.89.84:80",
            "http://203.80.189.33:8080",
            "http://94.45.74.60:8080",
            "http://162.248.225.8:80",
            "http://167.71.5.83:3128"
        };

            var proxyRotator = new ProxyRotator(proxies, false);
            string urlToScrape = "https://www.wikipedia.org/";
            await WebScraper.ScrapeData(proxyRotator, urlToScrape);
        }
    }

}

在此段代码中,应用程序初始化了一个代理 URL 列表,创建了一个 ProxyRotator 实例,指定了用于抓取内容的目标 URL(在本例中为 https://www.wikipedia.org/),并调用了 WebScraper.ScrapeData 函数来启动网页抓取过程。

该应用程序使用代理数组中指定的免费代理 IP 列表来路由网页抓取请求,掩盖可信来源,并将被维基百科服务器屏蔽的风险降至最低。ScrapeData 方法设置用于抓取维基百科主页,以提取文章标题和链接并显示在控制台上。ProxyRotator 类负责轮换这些代理,从而增强了抓取的隐匿性。

运行 WebScrapApp

如需运行 WebScrapApp,需在 WebScrapApp 应用程序的根目录中打开新的终端或 shell,然后运行以下命令:

dotnet build
dotnet run 

输出结果应如下所示:

…output omitted…
Title: Latina, Link: https://la.wikipedia.org/
Title: Latviešu, Link: https://lv.wikipedia.org/
Title: Lietuvių, Link: https://lt.wikipedia.org/
Title: Magyar, Link: https://hu.wikipedia.org/
Title: Македонски, Link: https://mk.wikipedia.org/
Title: Bahasa Melayu, Link: https://ms.wikipedia.org/
Title: Bahaso Minangkabau, Link: https://min.wikipedia.org/
Title: bokmål, Link: https://no.wikipedia.org/
Title: nynorsk, Link: https://nn.wikipedia.org/
Title: Oʻzbekcha / Ўзбекча, Link: https://uz.wikipedia.org/
Title: Қазақша / Qazaqşa / قازاقشا, Link: https://kk.wikipedia.org/
Title: Română, Link: https://ro.wikipedia.org/
Title: Simple English, Link: https://simple.wikipedia.org/
Title: Slovenčina, Link: https://sk.wikipedia.org/
Title: Slovenščina, Link: https://sl.wikipedia.org/
Title: Српски / Srpski, Link: https://sr.wikipedia.org/
Title: Srpskohrvatski / Српскохрватски, Link: https://sh.wikipedia.org/
Title: Suomi, Link: https://fi.wikipedia.org/
Title: தமிழ், Link: https://ta.wikipedia.org/
…output omitted…

调用了 WebScraper 类的 ScrapeData 函数后,它就会从维基百科网页提取数据,然后在控制台中显示文章标题及其对应的链接。此段代码使用公开可用的代理,每次运行应用程序时,它都会从所列的 IP 地址中选择一个作为代理。

如需使用 mitmproxy 中的本地代理进行测试,请使用以下代码更新 Program 文件中的 ProxyRotator 方法:

var proxyRotator = new ProxyRotator(proxies, true)
 string urlToScrape = "http://toscrape.com/";

将其值设置为 true 后,它将使用在本地主机端口 8080( mitmproxy 服务器)上运行的本地代理服务器。

为简化在计算机上设置证书时的配置过程,请将 URL 更改为 http://toscrape.com/

验证 mitmproxy 服务器是否正在运行;然后再次运行相同的命令:

dotnet build
dotnet run 

输出结果应如下所示:

…output omitted…
Title: fictional bookstore, Link: http://books.toscrape.com/
Title: books.toscrape.com, Link: http://books.toscrape.com/
Title: A website, Link: http://quotes.toscrape.com/
Title: Default, Link: http://quotes.toscrape.com/
Title: Scroll, Link: http://quotes.toscrape.com/scroll
Title: JavaScript, Link: http://quotes.toscrape.com/js
Title: Delayed, Link: http://quotes.toscrape.com/js-delayed
Title: Tableful, Link: http://quotes.toscrape.com/tableful
Title: Login, Link: http://quotes.toscrape.com/login
Title: ViewState, Link: http://quotes.toscrape.com/search.aspx
Title: Random, Link: http://quotes.toscrape.com/random
…output omitted…

如果从终端或 shell 中查看 mitmproxy 窗口,您会看到它拦截了调用:

如您所见,设置本地代理或在不同代理之间切换可能是一项复杂而耗时的任务。值得庆幸的是,Bright Data 等工具可以助您一臂之力。在下一节中,您将学习如何使用 Bright Data 代理服务器简化抓取过程。

Bright Data 代理

Bright Data 的代理服务网络遍布 195 个地区,其中集成了 Bright Data 的代理轮换功能,可在服务器之间有序切换,从而提高了网页抓取的效率和安全性。

使用这种轮换代理系统可降低网页抓取任务中经常遇到的 IP 封禁或屏蔽的风险。每个请求都使用不同的代理,从而隐藏了抓取者的身份,使网站难以检测和限制访问。这种方法增强了数据收集的可靠性,提高了匿名性和安全性。

Bright Data 平台经过精心设计,方便易用、设置简单,非常适合您将在下一节中看到的 C# 网页抓取项目

创建住宅代理

在项目中使用 Bright Data 代理之前,您需要设置账户。如需设置账户,请访问 Bright Data 网站并注册以获取免费试用。

账户设置完毕后,先登录账户,然后点击左侧的位置图标,进入“代理和抓取基础设施 (Proxies & Scraping Infrastructure)” 页面。然后点击“添加”并选择“住宅代理”

保留默认名称并再次点击“添加”以创建住宅代理:

代理创建完毕后,您会看到相关凭据,例如主机、端口、用户名和密码。将这些凭据保存在安全的地方,以便稍后使用:

从 IDE 或终端/ shell 导航到 WebScrapingBrightData 项目。然后创建一个 BrightDataProxyConfigurator.cs 类文件并添加以下代码:

using System.Net;

namespace WebScrapBrightData
{
    public class BrightDataProxyConfigurator
    {
        public static HttpClient ConfigureHttpClient(string proxyHost, string proxyUsername, string proxyPassword)
        {

            var proxy = new WebProxy(proxyHost) {
                Credentials = new NetworkCredential(proxyUsername, proxyPassword)
            };
            var httpClientHandler = new HttpClientHandler() {
                Proxy = proxy,
                UseProxy = true
            };
            var client = new HttpClient(httpClientHandler);
            client.DefaultRequestHeaders.Add("User-Agent", "YourUserAgent");
            client.DefaultRequestHeaders.Add("Accept", "application/json");

            client.DefaultRequestHeaders.TryAddWithoutValidation("Proxy-Authorization", Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes($"{proxyUsername}:{proxyPassword}")));
            return client;

        }
    }
}

在这段代码中,您定义了一个 BrightDataProxyConfigurator 类,其中包括 ConfigureHttpClient 函数。调用时,此函数会配置并返回设置用于使用代理服务器的 HttpClient。为实现此目的,您可以使用提供的用户名密码主机端口创建代理 URL,然后使用此代理配置 HttpClientHandler 。该函数最终会返回一个 HttpClient 实例,该实例通过指定的代理路由其所有请求。

接下来,在 WebScrapingBrightData 项目中创建一个名为 WebContentScraper.cs 的类文件,并添加以下代码:

using HtmlAgilityPack;

namespace WebScrapBrightData
{
    public class WebContentScraper
    {
        public static async Task ScrapeContent(string url, HttpClient client)
        {
            var response = await client.GetAsync(url);
            var content = await response.Content.ReadAsStringAsync();

            HtmlDocument doc = new();
            doc.LoadHtml(content);
            var nodes = doc.DocumentNode.SelectNodes("//li/a[@href] | //p/a[@href] | //td/a[@href]");

            if (nodes != null)
            {
                foreach (var node in nodes)
                {
                    string hrefValue = node.GetAttributeValue("href", string.Empty);
                    string title = node.InnerText;

                    Uri baseUri = new(url);
                    Uri fullUri = new(baseUri, hrefValue);

                    Console.WriteLine($"Title: {title}, Link: {fullUri.AbsoluteUri}");
                }
            }
            else
            {
                Console.WriteLine("No article links found on the page.");
            }
        }
    }
}

此代码使用静态异步方法 ScrapeContent定义一个 WebContentScraper 类。此方法采用 URL 和 HttpClient,获取网页的内容,将其作为 HTML 解析,并从特定 HTML 元素(列表项、段落和表格单元格)中提取链接。然后,它会打印这些链接的标题和绝对 URL 至控制台。

Program 类

现在,再次抓取维基百科网页上的内容,看看 Bright Data 如何增强便捷性和隐匿性。

使用以下代码更新类文件 Program.cs

namespace WebScrapBrightData
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            // Bright Data Proxy Configuration
            string host = "your_brightdata_proxy_host";
            string username = "your_brightdata_proxy_username";
            string password = "your_brightdata_proxy_password";
            var client = BrightDataProxyConfigurator.ConfigureHttpClient(host, username, password);

            // Scrape content from the target URL
            string urlToScrape = "https://www.wikipedia.org/";
            await WebContentScraper.ScrapeContent(urlToScrape, client);
        }
    }
}

注意:请确保您已将 Bright Data 代理凭据替换为之前保存的凭据。

接下来,请从 WebScrapBrightData 项目的根目录中打开 shell 或终端,并运行以下命令,以测试和运行您的应用程序:

dotnet build
dotnet run

所得到的输出结果应与您之前使用公共代理时获得的输出结果相同:

…output omitted…
Title: Latina, Link: https://la.wikipedia.org/
Title: Latviešu, Link: https://lv.wikipedia.org/
Title: Lietuvių, Link: https://lt.wikipedia.org/
Title: Magyar, Link: https://hu.wikipedia.org/
Title: Македонски, Link: https://mk.wikipedia.org/
Title: Bahasa Melayu, Link: https://ms.wikipedia.org/
Title: Bahaso Minangkabau, Link: https://min.wikipedia.org/
Title: bokmål, Link: https://no.wikipedia.org/
Title: nynorsk, Link: https://nn.wikipedia.org/
Title: Oʻzbekcha / Ўзбекча, Link: https://uz.wikipedia.org/
Title: Қазақша / Qazaqşa / قازاقشا, Link: https://kk.wikipedia.org/
Title: Română, Link: https://ro.wikipedia.org/
Title: Simple English, Link: https://simple.wikipedia.org/
Title: Slovenčina, Link: https://sk.wikipedia.org/
Title: Slovenščina, Link: https://sl.wikipedia.org/
Title: Српски / Srpski, Link: https://sr.wikipedia.org/
Title: Srpskohrvatski / Српскохрватски, Link: https://sh.wikipedia.org/
Title: Suomi, Link: https://fi.wikipedia.org/
Title: தமிழ், Link: https://ta.wikipedia.org/
…output omitted…

该 program 使用 Bright Data 代理来抓取维基百科主页,并在控制台中显示提取的标题和链接。这表明将 Bright Data 代理集成到 C# 网页抓取项目中隐匿、可靠地提取数据的有效性和易用性。

如果您想直观了解 Bright Data 代理的使用效果,可尝试向 http://lumtest.com/myip.json 发送 GET 请求。该网站将返回当前尝试访问网站的客户的位置和其他与网络相关的详细信息。如果您想自行尝试一下,请在新的浏览器选项卡中打开链接。您会看到公开可见的您的网络相关详细信息。

想要使用 Bright Data 代理进行上述操作,请更新 WebContentScraper.cs 中的代码,使其与以下代码相匹配:

using HtmlAgilityPack;


public class WebContentScraper
{
    public static async Task ScrapeContent(string url, HttpClient client)
    {
        var response = await client.GetAsync(url);
        var content = await response.Content.ReadAsStringAsync();

        HtmlDocument doc = new();
        doc.LoadHtml(content);

        Console.Write(content);
    }
}

然后更新 Program.cs 文件中的 urlToScrape 变量,以抓取此网站上的内容:

            string urlToScrape = "http://lumtest.com/myip.json";

现在,请再次运行应用程序。您应该会在终端看到这样的输出结果:

{"ip":"79.221.123.68","country":"DE","asn":{"asnum":3320,"org_name":"Deutsche Telekom AG"},"geo":{"city":"Koenigs Wusterhausen","region":"BB","region_name":"Brandenburg","postal_code":"15711","latitude":52.3014,"longitude":13.633,"tz":"Europe/Berlin","lum_city":"koenigswusterhausen","lum_region":"bb"}}

这证实该请求正由某个 Bright Data 代理服务器代理。

结语

在本文中,您学习了如何在 C# 中使用代理服务器进行网页抓取。

尽管本地代理服务器在某些情况下可能很有用,但对网页抓取项目来说,它们通常存在局限性。值得庆幸的是, 代理服务器可以解决这一问题。凭借庞大的全球网络和多种代理选择(包括住宅代理ISP 代理数据中心代理移动代理),Bright Data 让网页抓取变得高度灵活且可靠。特别是代理轮换功能,对大规模抓取任务来说尤其有用,它有助于保持匿名性并降低 IP 被禁风险。

在您继续网页抓取之旅前,请考虑使用 Bright Data 的解决方案,它强大、可扩展,遵守网络抓取最佳实践,让您可以高效收集所需数据。

本教程中的所有代码都可在此 GitHub 存储库中找到。