c# 如何实现一个简单的爬虫

使用 HttpClient 发起 GET 请求获取网页内容是 C# 最轻量现代做法,需复用实例、设超时、检查状态码、用 ReadAsStringAsync 读取 HTML;再用 HtmlAgilityPack 解析,注意 XPath 属性加 @、优先 InnerHtml + HtmlDecode;基础反反爬需设 User-Agent 和延时。

HttpClient 发起 GET 请求获取网页内容

直接用 HttpClient 是 C# 最轻量、最现代的做法,比已过时的 WebClient 更可靠,也比 HttpWebRequest 更简洁。注意必须复用同一个 HttpClient 实例,否则容易触发端口耗尽(SocketException: Too many open files)。

  • 在类级别声明并复用:private static readonly HttpClient client = new HttpClient();
  • 设置超时避免卡死:client.Timeout = TimeSpan.FromSeconds(10);
  • 记得处理响应状态码:if (!response.IsSuccessStatusCode),不要只靠异常判断
  • await response.Content.ReadAsStringAsync() 读取 HTML 字符串,不是 ReadAsByteArrayAsync(除非你要解析二进制)

HtmlAgilityPack 解析 HTML 提取链接和文本

HtmlAgilityPack 是 .NET 生态里最稳定、兼容性最好的 HTML 解析库,能容忍 malformed HTML(比如缺少闭合标签),比正则匹配靠谱得多。它不依赖浏览器引擎,纯内存解析,速度快。

  • 安装包:dotnet add package HtmlAgilityPack
  • 加载 HTML:var doc = new HtmlDocument(); doc.LoadHtml(htmlString);
  • 查所有链接:doc.DocumentNode.SelectNodes("//a[@href]"),注意 XPath 中属性要带 @
  • 提取文本内容别用 InnerText(含换行/空白多),优先用 InnerHtml + WebUtility.HtmlDecode() 清理

如何避免被目标网站封禁(基础反反爬)

多数小站点没严格风控,但加几条基础头就能绕过最简单的 UA 拦截。别学某些教程发一堆无意义请求头,反而显得可疑。

  • 必设 User-Agent:用主流浏览器值,比如 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
  • 可选加 AcceptAccept-Language,但不是必须;Referer 只在需要模拟页面跳转时才设
  • 加延时是关键:await Task.Delay(1000)(1 秒),别用 Thread.Sleep 阻塞线程

  • 不要并发猛刷——单线程 + 延迟,比十个并发还安全
using System;
using System.Net.Http;
using System.Threading.Tasks;
using HtmlAgilityPack;

class SimpleCrawler { private static readonly HttpClient client = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };

static SimpleCrawler()
{
    client.DefaultRequestHeaders.UserAgent.ParseAdd(
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
}

public static async Task CrawlAsync(string url)
{
    try
    {
        var html = await client.GetStringAsync(url);
        var doc = new HtmlDocument();
        doc.LoadHtml(html);

        foreach (var link in doc.DocumentNode.SelectNodes("//a[@href]"))
        {
            var href = link.GetAttributeValue("href", "");
            if (Uri.IsWellFormedUriString(href, UriKind.Absolute))
                Console.WriteLine(href);
        }
    }
    catch (HttpRequestException ex)
    {
        Console.WriteLine($"Request failed: {ex.StatusCode}");
    }
}

}

真正难的不是发请求或提链接,而是判断哪些 URL 值得跟进、怎么去重、怎么处理跳转和相对路径、何时停——这些逻辑一加上,就不再是“简单”爬虫了。