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

7 min read

当直接访问互联网时,网站很容易根据您的请求追踪到您的 IP 地址。这样的信息泄露会导致定向广告和在线跟踪,并可能危及您的数字身份安全。

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

在进行网页抓取时,代理可以帮您绕过 IP 禁令,避免地理封锁并保护您的身份。在本文中,您将学习如何在 C# 中使用代理执行各种网页抓取项目。

前提

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

本文中的示例都使用单独的 .NET 控制台应用程序。要创建您自己的 .NET 控制台应用程序,请参阅下列其中一项指南:

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

在 VS 上创建 WebScrapApp 和 WebScrapBrightData

在进行网页抓取时,特别是处理 HTML 内容时,您需要诸如 HtmlAgilityPack 这样的专用工具。该库简化了 HTML 解析和操作流程,使网页数据提取过程变得更加容易。

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

在 VS 上安装 HtmlAgilityPack

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

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

如何设置本地代理

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

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

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

mitmproxy

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

启动 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 窗口中,您会看到它通过本地代理拦截了调用:

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);
        }
    }
}

该类用于管理代理列表,提供为每个网络请求随机选择代理的函数。这种随机选择代理的方式是网页抓取过程中防止被发现和绕开潜在的 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…

当调用类 WebScraperScrapeData 函数后,它就会从维基百科网页提取数据,然后在控制台中显示文章标题及其对应的链接。此代码使用公开可用的代理,每次运行应用程序时,它都会从列出的 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 窗口时,您会看到它拦截了调用:

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;

        }
    }
}

在这段代码中,您定义了包含 ConfigureHttpClient 函数的类 BrightDataProxyConfigurator。调用此函数后,其会配置并返回一个被设置为使用代理服务器的 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…

该程序使用 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 存储库中找到。