推荐用 HttpClient 替代已过时的 WebClient,因其支持超时控制、连接池复用;解析 HTML 应用 HtmlAgilityPack 而非正则;采集需加随机延时、轮换 UA、处理编码与 Cookie;动态页需 PuppeteerSharp 等方案。

用 HttpClient 发起请求比 WebClient 更可靠
老项目里常见用 WebClient 抓网页,但它不支持超时精细控制、不能复用连接、在 .NET Core/.NET 5+ 中已标记为过时。实际跑起来容易卡死或抛出 WebException,尤其遇到重定向多、响应慢的站点。
推荐统一用 HttpClient,它线程安全、连接池内置、超时和头信息都好配:
- 全局只建一个
HttpClient实例(别每次请求 new 一个,否则会耗尽端口) - 设置
Timeout属性,比如client.Timeout = TimeSpan.FromSeconds(10); - 加
User-Agent头,很多网站直接拦截默认 UA,例如:client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0");
解析 HTML 别手写正则,用 HtmlAgilityPack
用 string.Contains 或正则去扒 <title></title> 这类标签,看着快,但只要网页结构稍有变动(比如换行、加空格、属性顺序变),就直接失效。而且正则根本没法处理嵌套、自闭合、命名空间等真实 HTML 场景。
HtmlAgilityPack 是 C# 里最稳的 HTML 解析库,NuGet 安装后就能用 XPath 或 LINQ 查元素:
- 加载页面:
var doc = new HtmlDocument(); doc.LoadHtml(htmlString); - 取标题:
doc.DocumentNode.SelectSingleNode("//title")?.InnerText - 取所有链接:
doc.DocumentNode.SelectNodes("//a[@href]")
注意:它默认不自动修正 malformed HTML,如果目标页严重缺闭合标签,可启用 doc.OptionFixNestedTags = true;,但会略拖慢速度。
循环采集要防封,别裸奔式 for 请求
连续发几十个请求,目标服务器基本秒识别为爬虫——返回 403、验证码、空内容,甚至 IP 拉黑。这不是代码写得不对,是没模拟人行为。
- 加随机延时,比如
await Task.Delay(new Random().Next(1000, 3000));,别用固定值 - 每次请求换
User-Agent(从列表里随机选),避免被 UA 指纹锁定 - 必要时加
CookieContainer持久化会话,有些站需要先 GET 首页拿 Cookie 再 POST 登录 - 别用
Thread.Sleep,阻塞线程;用await Task.Delay保持异步非阻塞
编码乱码?优先检查 HttpResponseMessage.Content.Headers.ContentType
中文网页抓下来全是问号或方块,90% 不是解码错了,而是没按响应头声明的编码读取。比如响应头带 Content-Type: text/html; charset=gb2312,你却用 UTF8 解,必然乱。
- 别硬写
Encoding.UTF8,从响应中提取:response.Content.Headers.ContentType.CharSet - 为空时再 fallback 到 UTF8 或检测 BOM,
HtmlAgilityPack自带DetectEncoding方法可用 - 如果服务器头写错(比如声明 UTF8 实际发 GBK),就得手动指定,这时建议先用 Fiddler 或 curl 看真实响应头确认
真正麻烦的是动态渲染页——这类根本不是靠 HttpClient 能解决的,得切到 PuppeteerSharp 或 Playwright,但那是另一层问题了。


















