httpclient 是获取网页源码的首选,需复用实例、手动处理编码、设置请求头、控制频率,并用 htmlagilitypack 解析html。

用 HttpClient 获取网页源码最直接,别碰 WebClient
现代 C# 里,HttpClient 是读取网页源码的首选,它支持异步、可复用、性能好。而 WebClient 已被标记为“过时”,在 .NET 6+ 中默认不推荐,且无法精细控制超时、头信息或重定向行为。
常见错误是直接 new 一个 HttpClient 每次都用——这会导致 socket 耗尽。正确做法是复用单例实例(比如注入到服务容器,或用 static readonly)。
-
HttpClient默认会自动处理 301/302 重定向;如需禁用,设AllowAutoRedirect = false - 中文网页常返回
gb2312或gbk编码,但HttpClient默认按 UTF-8 解析,结果乱码;必须手动指定编码(见下节) - 某些网站会校验
User-Agent,空头或默认头可能被 403 拒绝;加一句client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0")
中文乱码?关键在 HttpContent.ReadAsStringAsync() 的编码推导逻辑
HttpClient 调用 GetStringAsync() 时,会从响应头 Content-Type(如 text/html; charset=gb2312)提取编码;若响应头没写 charset,或写错了,就会 fallback 到 UTF-8,导致中文变问号。
稳妥做法是:先用 GetByteArrayAsync() 拿原始字节,再根据网页 <meta charset="..."> 或响应头手动选编码解码:
var bytes = await client.GetByteArrayAsync("https://example.com");
// 先查响应头
var encoding = Encoding.UTF8;
if (response.Content.Headers.ContentType?.CharSet != null)
encoding = Encoding.GetEncoding(response.Content.Headers.ContentType.CharSet);
else
encoding = DetectEncodingFromHtmlBytes(bytes); // 自行实现:扫描前 1024 字节找 <meta charset="gbk">
string html = encoding.GetString(bytes);
简单爬虫要绕开哪些反爬?基础三件事必须做
不是所有网站都欢迎爬虫。哪怕只是 GET 抓首页,以下三点不做,大概率返回空内容或 403:
- 设置合理的
User-Agent,模拟主流浏览器(如Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36) - 加
Accept和Accept-Language头,例如text/html,application/xhtml+xml和zh-CN,zh;q=0.9 - 控制请求频率:两次
GetAsync之间至少await Task.Delay(1000),避免被 IP 封禁
注意:HttpClient 本身不维护 Cookie,如果目标站依赖登录态或 Session,得自己配 HttpClientHandler.CookieContainer,否则每次都是“新访客”。
解析 HTML 别手写正则,用 HtmlAgilityPack 更稳
拿到源码字符串后,千万别用 string.IndexOf("<div class="title">") 这类方式提取数据——HTML 结构稍有变动就全崩。用 <code>HtmlAgilityPack(NuGet 包名同名)是行业惯例。
它把 HTML 当成 DOM 树处理,支持 XPath 和 LINQ 查询,容错强(能修复缺失闭合标签):
var htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(html);
var titles = htmlDoc.DocumentNode.SelectNodes("//h2[@class='post-title']")
?.Select(x => x.InnerText.Trim())
.ToList();
注意:SelectNodes 返回 null 如果没匹配到,务必判空;XPath 中属性值含单引号要用双引号包裹,反之亦然;对动态渲染的页面(如 React/Vue),HtmlAgilityPack 只能读到初始 HTML,拿不到 JS 渲染后的内容——这时候得换 PuppeteerSharp。
真正难的从来不是“怎么拿到源码”,而是怎么让每次请求都像真人、怎么应对编码嵌套、怎么从千变万化的 HTML 结构中稳定抽字段。这些细节不压着试几次,光看代码永远踩坑。











